import { createAdapterHandler } from "@vaultgraph/sdk/adapter";
const handler = createAdapterHandler({
signingSecret: process.env.VAULTGRAPH_SIGNING_SECRET!,
// Runs once per request, before dispatch, and is the only place the raw
// `context.session_token` is read. Verify it and return a typed identity (any
// shape you like) or `null` for an anonymous caller. The result is handed to
// every method as its final `session` argument — already authenticated, so
// owner-scoped reads gate on it; the handler strips the token from the
// context methods see. Throw to reject the request outright.
verifySession: (context) => verifySessionToken(context?.session_token),
async searchProducts({ query, filters, pagination } = {}) {
// Return a ProductPage: `{ products, pagination? }`. Set
// `pagination: { has_next_page: true, cursor }` while more rows remain;
// omit `pagination` on the last page.
return myDb.products.search({ query, filters, pagination });
},
async getProduct(id) {
return myDb.products.find(id);
},
async listCategories() {
// Return your store's whole category vocabulary: `{ value, count? }[]`.
// The labels are what agents pass to `searchProducts`' filters.categories,
// so this is how they discover categories and recover from an empty search.
return myDb.products.categories();
},
async getOrder(id, _context, session) {
// Owner-scoped read: return the order only when the verified caller owns
// it. Throw the same `order_not_found` for a missing OR non-owned id so
// existence isn't leaked. Key orders on an opaque id, not an enumerable
// sequential number.
const order = session ? await myDb.orders.find(id) : null;
if (!order || order.customerId !== session.customerId)
throw { code: "order_not_found", status: 404 };
return order;
},
async listOrders(input, _context, session) {
// The verified `session` from `verifySession` is the final argument of
// every method; scope the read to it. Reject an anonymous caller with
// `authentication_required` (401) so the agent prompts the user to sign in,
// rather than an empty page that reads as "you have no orders".
// `input.pagination` carries the optional cursor/limit (limit must be ≤ 100
// — the gateway rejects a larger value), and you return `OrderPage` —
// `{ orders }` plus an optional `pagination` cursor for the next page.
if (!session)
throw {
code: "authentication_required",
status: 401,
detail: "Sign in to view your orders.",
};
return myDb.orders.forCustomer(session.customerId, input?.pagination);
},
// …add `createCheckout`, `addLineItems`, `completeCheckout`, etc. as you
// implement them. The protocol reference lists every method with its
// inputs, return shape, preconditions, and which error to throw when.
});