-
Notifications
You must be signed in to change notification settings - Fork 0
Options
createFetchAdapter(adapter, options?) accepts an optional options object with the following shape:
interface FetchAdapterOptions<TItem extends Record<string, unknown> = Record<string, unknown>> {
policy?: Partial<RestPolicy>;
sortableIndices?: Record<string, string>;
keyFromPath?: (rawKey: string, adapter: Adapter<TItem>) => Record<string, unknown>;
exampleFromContext?: (query: Record<string, string>, body: unknown, request: Request) => Record<string, unknown>;
maxBodyBytes?: number;
mountPath?: string;
onMiss?: (request: Request) => Response | null | Promise<Response | null> | undefined | void;
}All options are optional. The factory is generic in TItem so that typed Adapter<Planet, PlanetKey> flows through to keyFromPath's second argument without casts.
Partial overrides for the REST policy. Merged shallowly with defaultPolicy from dynamodb-toolkit/rest-core — only the fields you pass are touched.
createFetchAdapter(adapter, {
policy: {
defaultLimit: 25,
maxLimit: 500,
maxOffset: 10_000, // DoS cap on `?offset=` (since toolkit 3.1.1)
needTotal: false,
envelope: {items: 'rows', total: 'count', offset: 'skip', limit: 'take', links: 'links'},
metaPrefix: '$',
methodPrefix: '!',
statusCodes: {
miss: 410,
validation: 422,
consistency: 409,
throttle: 429,
transient: 503,
internal: 500
}
}
});See the parent wiki's REST core page for the full RestPolicy reference.
Maps the public sort-field name (?sort=field or ?sort=-field) to the GSI that provides that ordering. Without an entry, ?sort=… is ignored (no index set, no error).
createFetchAdapter(adapter, {
sortableIndices: {
name: 'by-name-index',
createdAt: 'by-created-index',
mass: 'by-mass-index'
}
});?sort=-createdAt then resolves to {index: 'by-created-index', descending: true} and is passed to the Adapter's list call.
Convert the URL :key path segment into a key object. Runs on every keyed route. Default:
(raw, adapter) => ({[adapter.keyFields[0]]: raw})Overrides are the idiomatic place for composite keys, type coercion, and key-format validation:
// Composite "partition:sort" keys
createFetchAdapter(adapter, {
keyFromPath: raw => {
const [pk, sk] = raw.split(':');
if (!pk || !sk) {
throw Object.assign(new Error('Expected key of shape pk:sk'), {status: 400, code: 'BadKey'});
}
return {pk, sk};
}
});
// Numeric partition keys
createFetchAdapter(adapter, {
keyFromPath: (raw, a) => {
const n = Number(raw);
if (!Number.isFinite(n)) throw Object.assign(new Error('Numeric key expected'), {status: 400});
return {[a.keyFields[0]]: n};
}
});The adapter is passed through so you can read adapter.keyFields when writing generic callbacks. See Composite keys for more patterns.
Build the example object passed to Adapter.prepareListInput(example, index). Runs on the collection-level routes that call the Adapter's list machinery: GET /, DELETE /, PUT /-clone, PUT /-move.
Signature:
(query, body, request) => exampleObjectDefault: () => ({}) — no example, prepareListInput derives everything from index alone.
createFetchAdapter(adapter, {
exampleFromContext: (query, _body, request) => ({
tenant: request.headers.get('x-tenant-id') || 'default',
status: query.status || 'active'
})
});The three args:
-
query— parsed URL query asRecord<string, string>. URLSearchParams duplicates collapse to the first value. -
body— parsed request body,unknown. Only defined forPUT /-clone/PUT /-move, which have a body;nullonGET //DELETE /. -
request— the full FetchRequest. Inspectrequest.headers,request.url,request.method, and host-specific context (e.g. Cloudflare Workers'request.cf).request.bodyis already consumed by the time this runs — don't try to re-read it.
See Per-tenant filtering for a complete cookbook.
Byte cap for request bodies. Enforced on every body-reading route (POST, PUT, PATCH, DELETE with body) via two mechanisms:
-
Content-Length fast-path reject. If the declared
Content-Lengthexceeds the cap, the handler rejects413 PayloadTooLargebefore reading any bytes. -
Streaming byte counter. Otherwise the body is read through
request.body.getReader()with a running byte total; the handler rejects413as soon as the counter crosses the cap — catches chunked uploads and liar-Content-Length requests.
Default: 1048576 (1 MiB), matching dynamodb-toolkit/handler + the koa / express adapters.
// Low-power device endpoint — cap at 64 KiB
createFetchAdapter(adapter, {maxBodyBytes: 64 * 1024});
// Dedicated bulk-load endpoint — allow larger bodies
createFetchAdapter(bulkAdapter, {maxBodyBytes: 16 * 1024 * 1024});Over-cap requests get 413 PayloadTooLarge with {code: 'PayloadTooLarge'}. Malformed JSON gives 400 BadJsonBody.
See Body reading for the full flow.
Path prefix the adapter owns. Stripped from the incoming URL.pathname before route matching.
Default: unset — the adapter sees the full pathname and treats it as rooted at /.
// Terminal Workers handler — adapter owns the entire URL.
const handler = createFetchAdapter(planets);
// Mounted under /planets — GET /planets/earth → adapter sees GET /earth.
const handler = createFetchAdapter(planets, {mountPath: '/planets'});Match rules:
- Exact match (
pathname === mountPath) → route is/. - Prefix match (
pathname.startsWith(mountPath + '/')) → route ispathname.slice(mountPath.length). - Anything else → miss path (see
onMiss).
Matching is case-sensitive. No trailing-slash normalization beyond what URL parsing does.
Hook invoked when the request can't be dispatched to a known route:
- Pathname is outside
mountPath, or -
matchRoutereturns'unknown'for this method + path.
Without onMiss, unknown / off-mount requests resolve to an empty 404 Response — the handler is terminal. Suitable for single-collection Workers and Deno.serve(handler).
With onMiss supplied, the handler's return type widens from Promise<Response> to Promise<Response | null>, and the hook decides:
- Returns
Response→ handler returns it. - Returns
null→ handler returnsnull, so the caller can try the next handler in a composed router. - Returns
undefined/void→ fall back to the default404 Response. - Returns
Promise<…>→ the above semantics applied to the resolved value.
// Router composition — yield control back to Hono / itty-router on a miss.
const planets = createFetchAdapter(adapter, {mountPath: '/planets', onMiss: () => null});
// Custom miss response — e.g. send something friendlier than an empty 404.
const planets = createFetchAdapter(adapter, {
mountPath: '/planets',
onMiss: request => new Response(JSON.stringify({
error: 'not found',
path: new URL(request.url).pathname
}), {status: 404, headers: {'content-type': 'application/json'}})
});See Composition for full integration patterns.
The options surface is a direct superset of dynamodb-toolkit/handler's HandlerOptions:
-
policy,sortableIndices,keyFromPath,maxBodyBytes— identical shape and semantics. -
exampleFromContext— extended from the core's(query, body)signature with a thirdrequestargument. Callbacks written against the core handler still work (extra args ignored). -
mountPathandonMiss— Fetch-specific. No equivalents in the core handler becausenode:httpservers don't compose on the handler surface.