@fragno-dev/core 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +131 -64
- package/CHANGELOG.md +19 -0
- package/dist/api/api.d.ts +38 -2
- package/dist/api/api.d.ts.map +1 -0
- package/dist/api/api.js +9 -2
- package/dist/api/api.js.map +1 -0
- package/dist/api/bind-services.d.ts +6 -0
- package/dist/api/bind-services.d.ts.map +1 -0
- package/dist/api/bind-services.js +20 -0
- package/dist/api/bind-services.js.map +1 -0
- package/dist/api/error.d.ts +26 -0
- package/dist/api/error.d.ts.map +1 -0
- package/dist/{api-DngJDcmO.js → api/error.js} +2 -8
- package/dist/api/error.js.map +1 -0
- package/dist/api/fragment-definition-builder.d.ts +313 -0
- package/dist/api/fragment-definition-builder.d.ts.map +1 -0
- package/dist/api/fragment-definition-builder.js +326 -0
- package/dist/api/fragment-definition-builder.js.map +1 -0
- package/dist/api/fragment-instantiator.d.ts +216 -0
- package/dist/api/fragment-instantiator.d.ts.map +1 -0
- package/dist/api/fragment-instantiator.js +487 -0
- package/dist/api/fragment-instantiator.js.map +1 -0
- package/dist/api/fragno-response.d.ts +30 -0
- package/dist/api/fragno-response.d.ts.map +1 -0
- package/dist/api/fragno-response.js +73 -0
- package/dist/api/fragno-response.js.map +1 -0
- package/dist/api/internal/path.d.ts +50 -0
- package/dist/api/internal/path.d.ts.map +1 -0
- package/dist/api/internal/path.js +76 -0
- package/dist/api/internal/path.js.map +1 -0
- package/dist/api/internal/response-stream.d.ts +43 -0
- package/dist/api/internal/response-stream.d.ts.map +1 -0
- package/dist/api/internal/response-stream.js +81 -0
- package/dist/api/internal/response-stream.js.map +1 -0
- package/dist/api/internal/route.js +10 -0
- package/dist/api/internal/route.js.map +1 -0
- package/dist/api/mutable-request-state.d.ts +82 -0
- package/dist/api/mutable-request-state.d.ts.map +1 -0
- package/dist/api/mutable-request-state.js +97 -0
- package/dist/api/mutable-request-state.js.map +1 -0
- package/dist/api/request-context-storage.d.ts +42 -0
- package/dist/api/request-context-storage.d.ts.map +1 -0
- package/dist/api/request-context-storage.js +43 -0
- package/dist/api/request-context-storage.js.map +1 -0
- package/dist/api/request-input-context.d.ts +89 -0
- package/dist/api/request-input-context.d.ts.map +1 -0
- package/dist/api/request-input-context.js +118 -0
- package/dist/api/request-input-context.js.map +1 -0
- package/dist/api/request-middleware.d.ts +50 -0
- package/dist/api/request-middleware.d.ts.map +1 -0
- package/dist/api/request-middleware.js +83 -0
- package/dist/api/request-middleware.js.map +1 -0
- package/dist/api/request-output-context.d.ts +41 -0
- package/dist/api/request-output-context.d.ts.map +1 -0
- package/dist/api/request-output-context.js +119 -0
- package/dist/api/request-output-context.js.map +1 -0
- package/dist/api/route-handler-input-options.d.ts +21 -0
- package/dist/api/route-handler-input-options.d.ts.map +1 -0
- package/dist/api/route.d.ts +54 -3
- package/dist/api/route.d.ts.map +1 -0
- package/dist/api/route.js +29 -2
- package/dist/api/route.js.map +1 -0
- package/dist/api/shared-types.d.ts +47 -0
- package/dist/api/shared-types.d.ts.map +1 -0
- package/dist/api/shared-types.js +1 -0
- package/dist/client/client-error.d.ts +60 -0
- package/dist/client/client-error.d.ts.map +1 -0
- package/dist/client/client-error.js +92 -0
- package/dist/client/client-error.js.map +1 -0
- package/dist/client/client.d.ts +210 -4
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +397 -6
- package/dist/client/client.js.map +1 -0
- package/dist/client/client.svelte.d.ts +5 -3
- package/dist/client/client.svelte.d.ts.map +1 -1
- package/dist/client/client.svelte.js +1 -5
- package/dist/client/client.svelte.js.map +1 -1
- package/dist/client/internal/fetcher-merge.js +36 -0
- package/dist/client/internal/fetcher-merge.js.map +1 -0
- package/dist/client/internal/ndjson-streaming.js +139 -0
- package/dist/client/internal/ndjson-streaming.js.map +1 -0
- package/dist/client/react.d.ts +5 -3
- package/dist/client/react.d.ts.map +1 -1
- package/dist/client/react.js +3 -5
- package/dist/client/react.js.map +1 -1
- package/dist/client/solid.d.ts +5 -3
- package/dist/client/solid.d.ts.map +1 -1
- package/dist/client/solid.js +2 -5
- package/dist/client/solid.js.map +1 -1
- package/dist/client/vanilla.d.ts +5 -3
- package/dist/client/vanilla.d.ts.map +1 -1
- package/dist/client/vanilla.js +2 -43
- package/dist/client/vanilla.js.map +1 -1
- package/dist/client/vue.d.ts +5 -3
- package/dist/client/vue.d.ts.map +1 -1
- package/dist/client/vue.js +1 -5
- package/dist/client/vue.js.map +1 -1
- package/dist/http/http-status.d.ts +26 -0
- package/dist/http/http-status.d.ts.map +1 -0
- package/dist/integrations/react-ssr.js +1 -1
- package/dist/internal/symbols.d.ts +9 -0
- package/dist/internal/symbols.d.ts.map +1 -0
- package/dist/internal/symbols.js +10 -0
- package/dist/internal/symbols.js.map +1 -0
- package/dist/mod-client.d.ts +36 -0
- package/dist/mod-client.d.ts.map +1 -0
- package/dist/mod-client.js +21 -0
- package/dist/mod-client.js.map +1 -0
- package/dist/mod.d.ts +7 -4
- package/dist/mod.js +4 -6
- package/dist/request/request.d.ts +4 -0
- package/dist/request/request.js +5 -0
- package/dist/test/test.d.ts +62 -35
- package/dist/test/test.d.ts.map +1 -1
- package/dist/test/test.js +75 -40
- package/dist/test/test.js.map +1 -1
- package/dist/util/async.js +40 -0
- package/dist/util/async.js.map +1 -0
- package/dist/util/content-type.js +49 -0
- package/dist/util/content-type.js.map +1 -0
- package/dist/util/nanostores.js +31 -0
- package/dist/util/nanostores.js.map +1 -0
- package/dist/{ssr-BByDVfFD.js → util/ssr.js} +2 -2
- package/dist/util/ssr.js.map +1 -0
- package/dist/util/types-util.d.ts +8 -0
- package/dist/util/types-util.d.ts.map +1 -0
- package/package.json +19 -12
- package/src/api/api.ts +41 -6
- package/src/api/bind-services.ts +42 -0
- package/src/api/fragment-definition-builder.extend.test.ts +810 -0
- package/src/api/fragment-definition-builder.test.ts +499 -0
- package/src/api/fragment-definition-builder.ts +1088 -0
- package/src/api/fragment-instantiator.test.ts +1488 -0
- package/src/api/fragment-instantiator.ts +1053 -0
- package/src/api/fragment-services.test.ts +727 -0
- package/src/api/request-context-storage.ts +64 -0
- package/src/api/request-middleware.test.ts +301 -225
- package/src/api/route.test.ts +87 -1
- package/src/api/route.ts +345 -24
- package/src/api/shared-types.ts +43 -0
- package/src/client/client-builder.test.ts +23 -23
- package/src/client/client.ssr.test.ts +3 -3
- package/src/client/client.svelte.test.ts +15 -15
- package/src/client/client.test.ts +22 -22
- package/src/client/client.ts +72 -12
- package/src/client/internal/fetcher-merge.ts +1 -1
- package/src/client/react.test.ts +2 -2
- package/src/client/solid.test.ts +2 -2
- package/src/client/vanilla.test.ts +2 -2
- package/src/client/vue.test.ts +2 -2
- package/src/internal/symbols.ts +5 -0
- package/src/mod-client.ts +59 -0
- package/src/mod.ts +26 -9
- package/src/request/request.ts +8 -0
- package/src/test/test.test.ts +200 -381
- package/src/test/test.ts +190 -117
- package/tsdown.config.ts +8 -5
- package/dist/api/fragment-builder.d.ts +0 -4
- package/dist/api/fragment-builder.js +0 -3
- package/dist/api/fragment-instantiation.d.ts +0 -4
- package/dist/api/fragment-instantiation.js +0 -6
- package/dist/api-BWN97TOr.d.ts +0 -377
- package/dist/api-BWN97TOr.d.ts.map +0 -1
- package/dist/api-DngJDcmO.js.map +0 -1
- package/dist/client-C5LsYHEI.js +0 -782
- package/dist/client-C5LsYHEI.js.map +0 -1
- package/dist/fragment-builder-DOnCVBqc.js +0 -47
- package/dist/fragment-builder-DOnCVBqc.js.map +0 -1
- package/dist/fragment-builder-MGr68GNb.d.ts +0 -409
- package/dist/fragment-builder-MGr68GNb.d.ts.map +0 -1
- package/dist/fragment-instantiation-C4wvwl6V.js +0 -446
- package/dist/fragment-instantiation-C4wvwl6V.js.map +0 -1
- package/dist/request-output-context-CdIjwmEN.js +0 -320
- package/dist/request-output-context-CdIjwmEN.js.map +0 -1
- package/dist/route-Bl9Zr1Yv.d.ts +0 -26
- package/dist/route-Bl9Zr1Yv.d.ts.map +0 -1
- package/dist/route-C5Uryylh.js +0 -21
- package/dist/route-C5Uryylh.js.map +0 -1
- package/dist/ssr-BByDVfFD.js.map +0 -1
- package/src/api/fragment-builder.ts +0 -80
- package/src/api/fragment-instantiation.test.ts +0 -460
- package/src/api/fragment-instantiation.ts +0 -499
- package/src/api/fragment.test.ts +0 -537
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
//#region src/api/request-context-storage.ts
|
|
4
|
+
/**
|
|
5
|
+
* Typed wrapper around AsyncLocalStorage for managing per-request data storage.
|
|
6
|
+
* Each fragment instance has its own storage to ensure proper isolation.
|
|
7
|
+
*
|
|
8
|
+
* TRequestStorage represents the type of data stored per-request (e.g., { userId: string, requestId: number })
|
|
9
|
+
*
|
|
10
|
+
* Note: The data stored should be an object that can be mutated during the request lifecycle.
|
|
11
|
+
* For example, you can store { uow: UnitOfWork } and modify properties on that object.
|
|
12
|
+
*
|
|
13
|
+
* @internal - Used by @fragno-dev/db, not part of public API
|
|
14
|
+
*/
|
|
15
|
+
var RequestContextStorage = class {
|
|
16
|
+
#storage;
|
|
17
|
+
constructor() {
|
|
18
|
+
this.#storage = new AsyncLocalStorage();
|
|
19
|
+
}
|
|
20
|
+
run(data, callback) {
|
|
21
|
+
return this.#storage.run(data, callback);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the current stored data from AsyncLocalStorage.
|
|
25
|
+
* @throws an error if called outside of a run() callback.
|
|
26
|
+
*
|
|
27
|
+
* Note: The returned object can be mutated. Changes will be visible to all code
|
|
28
|
+
* running within the same async context.
|
|
29
|
+
*/
|
|
30
|
+
getStore() {
|
|
31
|
+
const store = this.#storage.getStore();
|
|
32
|
+
if (!store) throw new Error("No storage found in RequestContextStorage. Service must be called within a route handler OR using `inContext`.");
|
|
33
|
+
return store;
|
|
34
|
+
}
|
|
35
|
+
runWithInitializer(initializer, callback) {
|
|
36
|
+
const data = initializer();
|
|
37
|
+
return this.#storage.run(data, callback);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
export { RequestContextStorage };
|
|
43
|
+
//# sourceMappingURL=request-context-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context-storage.js","names":["#storage"],"sources":["../../src/api/request-context-storage.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Typed wrapper around AsyncLocalStorage for managing per-request data storage.\n * Each fragment instance has its own storage to ensure proper isolation.\n *\n * TRequestStorage represents the type of data stored per-request (e.g., { userId: string, requestId: number })\n *\n * Note: The data stored should be an object that can be mutated during the request lifecycle.\n * For example, you can store { uow: UnitOfWork } and modify properties on that object.\n *\n * @internal - Used by @fragno-dev/db, not part of public API\n */\nexport class RequestContextStorage<TRequestStorage> {\n #storage: AsyncLocalStorage<TRequestStorage>;\n\n constructor() {\n this.#storage = new AsyncLocalStorage<TRequestStorage>();\n }\n\n /**\n * Run a callback with the given data available via getStore().\n * This establishes the async context for the duration of the callback.\n */\n run<T>(data: TRequestStorage, callback: () => T): T;\n run<T>(data: TRequestStorage, callback: () => Promise<T>): Promise<T>;\n run<T>(data: TRequestStorage, callback: () => T | Promise<T>): T | Promise<T> {\n return this.#storage.run(data, callback);\n }\n\n /**\n * Get the current stored data from AsyncLocalStorage.\n * @throws an error if called outside of a run() callback.\n *\n * Note: The returned object can be mutated. Changes will be visible to all code\n * running within the same async context.\n */\n getStore(): TRequestStorage {\n const store = this.#storage.getStore();\n if (!store) {\n throw new Error(\n \"No storage found in RequestContextStorage. Service must be called within a route handler OR using `inContext`.\",\n );\n }\n return store;\n }\n\n /**\n * Enter a new async context with fresh storage.\n * This is typically called at the start of a request handler.\n *\n * @param initializer Function that returns the initial storage data\n * @param callback The request handler to execute with the storage\n */\n runWithInitializer<T>(initializer: () => TRequestStorage, callback: () => T): T;\n runWithInitializer<T>(initializer: () => TRequestStorage, callback: () => Promise<T>): Promise<T>;\n runWithInitializer<T>(\n initializer: () => TRequestStorage,\n callback: () => T | Promise<T>,\n ): T | Promise<T> {\n const data = initializer();\n return this.#storage.run(data, callback);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,IAAa,wBAAb,MAAoD;CAClD;CAEA,cAAc;AACZ,QAAKA,UAAW,IAAI,mBAAoC;;CAS1D,IAAO,MAAuB,UAAgD;AAC5E,SAAO,MAAKA,QAAS,IAAI,MAAM,SAAS;;;;;;;;;CAU1C,WAA4B;EAC1B,MAAM,QAAQ,MAAKA,QAAS,UAAU;AACtC,MAAI,CAAC,MACH,OAAM,IAAI,MACR,iHACD;AAEH,SAAO;;CAYT,mBACE,aACA,UACgB;EAChB,MAAM,OAAO,aAAa;AAC1B,SAAO,MAAKA,QAAS,IAAI,MAAM,SAAS"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ExtractPathParams } from "./internal/path.js";
|
|
2
|
+
import { MutableRequestState } from "./mutable-request-state.js";
|
|
3
|
+
import { HTTPMethod } from "./api.js";
|
|
4
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
|
+
|
|
6
|
+
//#region src/api/request-input-context.d.ts
|
|
7
|
+
type RequestBodyType = unknown | FormData | Blob | null | undefined;
|
|
8
|
+
declare class RequestInputContext<TPath extends string = string, TInputSchema extends StandardSchemaV1 | undefined = undefined> {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(config: {
|
|
11
|
+
path: TPath;
|
|
12
|
+
method: string;
|
|
13
|
+
pathParams: ExtractPathParams<TPath>;
|
|
14
|
+
searchParams: URLSearchParams;
|
|
15
|
+
parsedBody: RequestBodyType;
|
|
16
|
+
rawBody?: string;
|
|
17
|
+
headers: Headers;
|
|
18
|
+
request?: Request;
|
|
19
|
+
inputSchema?: TInputSchema;
|
|
20
|
+
shouldValidateInput?: boolean;
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Create a RequestContext from a Request object for server-side handling
|
|
24
|
+
*/
|
|
25
|
+
static fromRequest<TPath extends string, TInputSchema extends StandardSchemaV1 | undefined = undefined>(config: {
|
|
26
|
+
request: Request;
|
|
27
|
+
method: string;
|
|
28
|
+
path: TPath;
|
|
29
|
+
pathParams: ExtractPathParams<TPath>;
|
|
30
|
+
inputSchema?: TInputSchema;
|
|
31
|
+
shouldValidateInput?: boolean;
|
|
32
|
+
state: MutableRequestState;
|
|
33
|
+
rawBody?: string;
|
|
34
|
+
}): Promise<RequestInputContext<TPath, TInputSchema>>;
|
|
35
|
+
/**
|
|
36
|
+
* Create a RequestContext for server-side rendering contexts (no Request object)
|
|
37
|
+
*/
|
|
38
|
+
static fromSSRContext<TPath extends string, TInputSchema extends StandardSchemaV1 | undefined = undefined>(config: {
|
|
39
|
+
method: "GET";
|
|
40
|
+
path: TPath;
|
|
41
|
+
pathParams: ExtractPathParams<TPath>;
|
|
42
|
+
searchParams?: URLSearchParams;
|
|
43
|
+
headers?: Headers;
|
|
44
|
+
} | {
|
|
45
|
+
method: Exclude<HTTPMethod, "GET">;
|
|
46
|
+
path: TPath;
|
|
47
|
+
pathParams: ExtractPathParams<TPath>;
|
|
48
|
+
searchParams?: URLSearchParams;
|
|
49
|
+
headers?: Headers;
|
|
50
|
+
body: RequestBodyType;
|
|
51
|
+
inputSchema?: TInputSchema;
|
|
52
|
+
}): RequestInputContext<TPath, TInputSchema>;
|
|
53
|
+
/**
|
|
54
|
+
* The HTTP method as string (e.g., `GET`, `POST`)
|
|
55
|
+
*/
|
|
56
|
+
get method(): string;
|
|
57
|
+
/**
|
|
58
|
+
* The matched route path (e.g., `/users/:id`)
|
|
59
|
+
* @remarks `string`
|
|
60
|
+
*/
|
|
61
|
+
get path(): TPath;
|
|
62
|
+
/**
|
|
63
|
+
* Extracted path parameters as object (e.g., `{ id: '123' }`)
|
|
64
|
+
* @remarks `Record<string, string>`
|
|
65
|
+
*/
|
|
66
|
+
get pathParams(): ExtractPathParams<TPath>;
|
|
67
|
+
/**
|
|
68
|
+
* [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object for query parameters
|
|
69
|
+
* @remarks `URLSearchParams`
|
|
70
|
+
*/
|
|
71
|
+
get query(): URLSearchParams;
|
|
72
|
+
/**
|
|
73
|
+
* [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object for request headers
|
|
74
|
+
* @remarks `Headers`
|
|
75
|
+
*/
|
|
76
|
+
get headers(): Headers;
|
|
77
|
+
get rawBody(): string | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Input validation context (only if inputSchema is defined)
|
|
80
|
+
* @remarks `InputContext`
|
|
81
|
+
*/
|
|
82
|
+
get input(): TInputSchema extends undefined ? undefined : {
|
|
83
|
+
schema: TInputSchema;
|
|
84
|
+
valid: () => Promise<TInputSchema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TInputSchema> : unknown>;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { RequestBodyType, RequestInputContext };
|
|
89
|
+
//# sourceMappingURL=request-input-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-input-context.d.ts","names":[],"sources":["../../src/api/request-input-context.ts"],"sourcesContent":[],"mappings":";;;;;;KAKY,eAAA,aAER,WACA;cAIS,wEAEU;EATX,CAAA,OAAA;EAOC,WAAA,CAAA,MAAA,EAAA;IAEU,IAAA,EAab,KAba;IAab,MAAA,EAAA,MAAA;IAEwB,UAAA,EAAlB,iBAAkB,CAAA,KAAA,CAAA;IAAlB,YAAA,EACE,eADF;IACE,UAAA,EACF,eADE;IACF,OAAA,CAAA,EAAA,MAAA;IAEH,OAAA,EAAA,OAAA;IACC,OAAA,CAAA,EAAA,OAAA;IACI,WAAA,CAAA,EAAA,YAAA;IAmBO,mBAAA,CAAA,EAAA,OAAA;EAEZ,CAAA;EAEH;;;EAEQ,OAAA,WAAA,CAAA,cAAA,MAAA,EAAA,qBANO,gBAMP,GAAA,SAAA,GAAA,SAAA,CAAA,CAAA,MAAA,EAAA;IAEP,OAAA,EANE,OAMF;IAEuB,MAAA,EAAA,MAAA;IAAO,IAAA,EAN/B,KAM+B;IAA3B,UAAA,EALE,iBAKF,CALoB,KAKpB,CAAA;IAAR,WAAA,CAAA,EAJY,YAIZ;IAoBmB,mBAAA,CAAA,EAAA,OAAA;IAKT,KAAA,EA3BL,mBA2BK;IACwB,OAAA,CAAA,EAAA,MAAA;EAAlB,CAAA,CAAA,EA1BhB,OA0BgB,CA1BR,mBA0BQ,CA1BY,KA0BZ,EA1BmB,YA0BnB,CAAA,CAAA;EACG;;;EAIP,OAAA,cAAA,CAAA,cAAA,MAAA,EAAA,qBAXO,gBAWP,GAAA,SAAA,GAAA,SAAA,CAAA,CAAA,MAAA,EAAA;IACF,MAAA,EAAA,KAAA;IACwB,IAAA,EARxB,KAQwB;IAAlB,UAAA,EAPA,iBAOA,CAPkB,KAOlB,CAAA;IACG,YAAA,CAAA,EAPA,eAOA;IACL,OAAA,CAAA,EAPA,OAOA;EACJ,CAAA,GAAA;IACQ,MAAA,EANN,OAMM,CANE,UAMF,EAAA,KAAA,CAAA;IAEC,IAAA,EAPT,KAOS;IAAO,UAAA,EANV,iBAMU,CANQ,KAMR,CAAA;IAA3B,YAAA,CAAA,EALoB,eAKpB;IAuBS,OAAA,CAAA,EA3BM,OA2BN;IAOwB,IAAA,EAjCtB,eAiCsB;IAAlB,WAAA,CAAA,EAhCI,YAgCJ;EAOL,CAAA,CAAA,EArCV,mBAqCU,CArCU,KAqCV,EArCiB,YAqCjB,CAAA;EAOE;;;EAiBP,IAAA,MAAA,CAAA,CAAA,EAAA,MAAA;EAAqB;;;;EADH,IAAA,IAAA,CAAA,CAAA,EArCd,KAqCc;;;;;oBA9BR,kBAAkB;;;;;eAOvB;;;;;iBAOE;;;;;;eAYF;YAGC;iBACK,QACX,qBAAqB,mBACjB,gBAAA,CAAiB,YAAY"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { FragnoApiValidationError } from "./error.js";
|
|
2
|
+
|
|
3
|
+
//#region src/api/request-input-context.ts
|
|
4
|
+
var RequestInputContext = class RequestInputContext {
|
|
5
|
+
#path;
|
|
6
|
+
#method;
|
|
7
|
+
#pathParams;
|
|
8
|
+
#searchParams;
|
|
9
|
+
#headers;
|
|
10
|
+
#body;
|
|
11
|
+
#parsedBody;
|
|
12
|
+
#inputSchema;
|
|
13
|
+
#shouldValidateInput;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.#path = config.path;
|
|
16
|
+
this.#method = config.method;
|
|
17
|
+
this.#pathParams = config.pathParams;
|
|
18
|
+
this.#searchParams = config.searchParams;
|
|
19
|
+
this.#headers = config.headers;
|
|
20
|
+
this.#body = config.rawBody;
|
|
21
|
+
this.#parsedBody = config.parsedBody;
|
|
22
|
+
this.#inputSchema = config.inputSchema;
|
|
23
|
+
this.#shouldValidateInput = config.shouldValidateInput ?? true;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a RequestContext from a Request object for server-side handling
|
|
27
|
+
*/
|
|
28
|
+
static async fromRequest(config) {
|
|
29
|
+
return new RequestInputContext({
|
|
30
|
+
method: config.method,
|
|
31
|
+
path: config.path,
|
|
32
|
+
pathParams: config.state.pathParams,
|
|
33
|
+
searchParams: config.state.searchParams,
|
|
34
|
+
headers: config.state.headers,
|
|
35
|
+
parsedBody: config.state.body,
|
|
36
|
+
rawBody: config.rawBody,
|
|
37
|
+
inputSchema: config.inputSchema,
|
|
38
|
+
shouldValidateInput: config.shouldValidateInput
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a RequestContext for server-side rendering contexts (no Request object)
|
|
43
|
+
*/
|
|
44
|
+
static fromSSRContext(config) {
|
|
45
|
+
return new RequestInputContext({
|
|
46
|
+
method: config.method,
|
|
47
|
+
path: config.path,
|
|
48
|
+
pathParams: config.pathParams,
|
|
49
|
+
searchParams: config.searchParams ?? new URLSearchParams(),
|
|
50
|
+
headers: config.headers ?? new Headers(),
|
|
51
|
+
parsedBody: "body" in config ? config.body : void 0,
|
|
52
|
+
inputSchema: "inputSchema" in config ? config.inputSchema : void 0,
|
|
53
|
+
shouldValidateInput: false
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The HTTP method as string (e.g., `GET`, `POST`)
|
|
58
|
+
*/
|
|
59
|
+
get method() {
|
|
60
|
+
return this.#method;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The matched route path (e.g., `/users/:id`)
|
|
64
|
+
* @remarks `string`
|
|
65
|
+
*/
|
|
66
|
+
get path() {
|
|
67
|
+
return this.#path;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Extracted path parameters as object (e.g., `{ id: '123' }`)
|
|
71
|
+
* @remarks `Record<string, string>`
|
|
72
|
+
*/
|
|
73
|
+
get pathParams() {
|
|
74
|
+
return this.#pathParams;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object for query parameters
|
|
78
|
+
* @remarks `URLSearchParams`
|
|
79
|
+
*/
|
|
80
|
+
get query() {
|
|
81
|
+
return this.#searchParams;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object for request headers
|
|
85
|
+
* @remarks `Headers`
|
|
86
|
+
*/
|
|
87
|
+
get headers() {
|
|
88
|
+
return this.#headers;
|
|
89
|
+
}
|
|
90
|
+
get rawBody() {
|
|
91
|
+
return this.#body;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Input validation context (only if inputSchema is defined)
|
|
95
|
+
* @remarks `InputContext`
|
|
96
|
+
*/
|
|
97
|
+
get input() {
|
|
98
|
+
if (!this.#inputSchema) return;
|
|
99
|
+
return {
|
|
100
|
+
schema: this.#inputSchema,
|
|
101
|
+
valid: async () => {
|
|
102
|
+
if (!this.#shouldValidateInput) return this.#parsedBody;
|
|
103
|
+
return this.#validateInput();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async #validateInput() {
|
|
108
|
+
if (!this.#inputSchema) throw new Error("No input schema defined for this route");
|
|
109
|
+
if (this.#parsedBody instanceof FormData || this.#parsedBody instanceof Blob) throw new Error("Schema validation is only supported for JSON data, not FormData or Blob");
|
|
110
|
+
const result = await this.#inputSchema["~standard"].validate(this.#parsedBody);
|
|
111
|
+
if (result.issues) throw new FragnoApiValidationError("Validation failed", result.issues);
|
|
112
|
+
return result.value;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
export { RequestInputContext };
|
|
118
|
+
//# sourceMappingURL=request-input-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-input-context.js","names":["#path","#method","#pathParams","#searchParams","#headers","#body","#parsedBody","#inputSchema","#shouldValidateInput","#validateInput"],"sources":["../../src/api/request-input-context.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ExtractPathParams } from \"./internal/path\";\nimport { FragnoApiValidationError, type HTTPMethod } from \"./api\";\nimport type { MutableRequestState } from \"./mutable-request-state\";\n\nexport type RequestBodyType =\n | unknown // JSON\n | FormData\n | Blob\n | null\n | undefined;\n\nexport class RequestInputContext<\n TPath extends string = string,\n TInputSchema extends StandardSchemaV1 | undefined = undefined,\n> {\n readonly #path: TPath;\n readonly #method: string;\n readonly #pathParams: ExtractPathParams<TPath>;\n readonly #searchParams: URLSearchParams;\n readonly #headers: Headers;\n readonly #body: string | undefined;\n readonly #parsedBody: RequestBodyType;\n readonly #inputSchema: TInputSchema | undefined;\n readonly #shouldValidateInput: boolean;\n\n constructor(config: {\n path: TPath;\n method: string;\n pathParams: ExtractPathParams<TPath>;\n searchParams: URLSearchParams;\n parsedBody: RequestBodyType;\n rawBody?: string;\n headers: Headers;\n request?: Request;\n inputSchema?: TInputSchema;\n shouldValidateInput?: boolean;\n }) {\n this.#path = config.path;\n this.#method = config.method;\n this.#pathParams = config.pathParams;\n this.#searchParams = config.searchParams;\n this.#headers = config.headers;\n this.#body = config.rawBody;\n this.#parsedBody = config.parsedBody;\n this.#inputSchema = config.inputSchema;\n this.#shouldValidateInput = config.shouldValidateInput ?? true;\n }\n\n /**\n * Create a RequestContext from a Request object for server-side handling\n */\n static async fromRequest<\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined = undefined,\n >(config: {\n request: Request;\n method: string;\n path: TPath;\n pathParams: ExtractPathParams<TPath>;\n inputSchema?: TInputSchema;\n shouldValidateInput?: boolean;\n state: MutableRequestState;\n rawBody?: string;\n }): Promise<RequestInputContext<TPath, TInputSchema>> {\n // Use the mutable state (potentially modified by middleware)\n return new RequestInputContext({\n method: config.method,\n path: config.path,\n pathParams: config.state.pathParams as ExtractPathParams<TPath>,\n searchParams: config.state.searchParams,\n headers: config.state.headers,\n parsedBody: config.state.body,\n rawBody: config.rawBody,\n inputSchema: config.inputSchema,\n shouldValidateInput: config.shouldValidateInput,\n });\n }\n\n /**\n * Create a RequestContext for server-side rendering contexts (no Request object)\n */\n static fromSSRContext<\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined = undefined,\n >(\n config:\n | {\n method: \"GET\";\n path: TPath;\n pathParams: ExtractPathParams<TPath>;\n searchParams?: URLSearchParams;\n headers?: Headers;\n }\n | {\n method: Exclude<HTTPMethod, \"GET\">;\n path: TPath;\n pathParams: ExtractPathParams<TPath>;\n searchParams?: URLSearchParams;\n headers?: Headers;\n body: RequestBodyType;\n inputSchema?: TInputSchema;\n },\n ): RequestInputContext<TPath, TInputSchema> {\n return new RequestInputContext({\n method: config.method,\n path: config.path,\n pathParams: config.pathParams,\n searchParams: config.searchParams ?? new URLSearchParams(),\n headers: config.headers ?? new Headers(),\n parsedBody: \"body\" in config ? config.body : undefined,\n inputSchema: \"inputSchema\" in config ? config.inputSchema : undefined,\n shouldValidateInput: false, // No input validation in SSR context\n });\n }\n\n /**\n * The HTTP method as string (e.g., `GET`, `POST`)\n */\n get method(): string {\n return this.#method;\n }\n /**\n * The matched route path (e.g., `/users/:id`)\n * @remarks `string`\n */\n get path(): TPath {\n return this.#path;\n }\n /**\n * Extracted path parameters as object (e.g., `{ id: '123' }`)\n * @remarks `Record<string, string>`\n */\n get pathParams(): ExtractPathParams<TPath> {\n return this.#pathParams;\n }\n /**\n * [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object for query parameters\n * @remarks `URLSearchParams`\n */\n get query(): URLSearchParams {\n return this.#searchParams;\n }\n /**\n * [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object for request headers\n * @remarks `Headers`\n */\n get headers(): Headers {\n return this.#headers;\n }\n\n get rawBody(): string | undefined {\n return this.#body;\n }\n\n /**\n * Input validation context (only if inputSchema is defined)\n * @remarks `InputContext`\n */\n get input(): TInputSchema extends undefined\n ? undefined\n : {\n schema: TInputSchema;\n valid: () => Promise<\n TInputSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TInputSchema>\n : unknown\n >;\n } {\n if (!this.#inputSchema) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return undefined as any;\n }\n\n return {\n schema: this.#inputSchema,\n valid: async () => {\n if (!this.#shouldValidateInput) {\n // In SSR context, return the body directly without validation\n return this.#parsedBody;\n }\n\n return this.#validateInput();\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any;\n }\n\n async #validateInput(): Promise<\n TInputSchema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TInputSchema> : never\n > {\n if (!this.#inputSchema) {\n throw new Error(\"No input schema defined for this route\");\n }\n\n if (this.#parsedBody instanceof FormData || this.#parsedBody instanceof Blob) {\n throw new Error(\"Schema validation is only supported for JSON data, not FormData or Blob\");\n }\n\n const result = await this.#inputSchema[\"~standard\"].validate(this.#parsedBody);\n\n if (result.issues) {\n throw new FragnoApiValidationError(\"Validation failed\", result.issues);\n }\n\n return result.value as TInputSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TInputSchema>\n : never;\n }\n}\n"],"mappings":";;;AAYA,IAAa,sBAAb,MAAa,oBAGX;CACA,CAASA;CACT,CAASC;CACT,CAASC;CACT,CAASC;CACT,CAASC;CACT,CAASC;CACT,CAASC;CACT,CAASC;CACT,CAASC;CAET,YAAY,QAWT;AACD,QAAKR,OAAQ,OAAO;AACpB,QAAKC,SAAU,OAAO;AACtB,QAAKC,aAAc,OAAO;AAC1B,QAAKC,eAAgB,OAAO;AAC5B,QAAKC,UAAW,OAAO;AACvB,QAAKC,OAAQ,OAAO;AACpB,QAAKC,aAAc,OAAO;AAC1B,QAAKC,cAAe,OAAO;AAC3B,QAAKC,sBAAuB,OAAO,uBAAuB;;;;;CAM5D,aAAa,YAGX,QASoD;AAEpD,SAAO,IAAI,oBAAoB;GAC7B,QAAQ,OAAO;GACf,MAAM,OAAO;GACb,YAAY,OAAO,MAAM;GACzB,cAAc,OAAO,MAAM;GAC3B,SAAS,OAAO,MAAM;GACtB,YAAY,OAAO,MAAM;GACzB,SAAS,OAAO;GAChB,aAAa,OAAO;GACpB,qBAAqB,OAAO;GAC7B,CAAC;;;;;CAMJ,OAAO,eAIL,QAiB0C;AAC1C,SAAO,IAAI,oBAAoB;GAC7B,QAAQ,OAAO;GACf,MAAM,OAAO;GACb,YAAY,OAAO;GACnB,cAAc,OAAO,gBAAgB,IAAI,iBAAiB;GAC1D,SAAS,OAAO,WAAW,IAAI,SAAS;GACxC,YAAY,UAAU,SAAS,OAAO,OAAO;GAC7C,aAAa,iBAAiB,SAAS,OAAO,cAAc;GAC5D,qBAAqB;GACtB,CAAC;;;;;CAMJ,IAAI,SAAiB;AACnB,SAAO,MAAKP;;;;;;CAMd,IAAI,OAAc;AAChB,SAAO,MAAKD;;;;;;CAMd,IAAI,aAAuC;AACzC,SAAO,MAAKE;;;;;;CAMd,IAAI,QAAyB;AAC3B,SAAO,MAAKC;;;;;;CAMd,IAAI,UAAmB;AACrB,SAAO,MAAKC;;CAGd,IAAI,UAA8B;AAChC,SAAO,MAAKC;;;;;;CAOd,IAAI,QASE;AACJ,MAAI,CAAC,MAAKE,YAER;AAGF,SAAO;GACL,QAAQ,MAAKA;GACb,OAAO,YAAY;AACjB,QAAI,CAAC,MAAKC,oBAER,QAAO,MAAKF;AAGd,WAAO,MAAKG,eAAgB;;GAG/B;;CAGH,OAAMA,gBAEJ;AACA,MAAI,CAAC,MAAKF,YACR,OAAM,IAAI,MAAM,yCAAyC;AAG3D,MAAI,MAAKD,sBAAuB,YAAY,MAAKA,sBAAuB,KACtE,OAAM,IAAI,MAAM,0EAA0E;EAG5F,MAAM,SAAS,MAAM,MAAKC,YAAa,aAAa,SAAS,MAAKD,WAAY;AAE9E,MAAI,OAAO,OACT,OAAM,IAAI,yBAAyB,qBAAqB,OAAO,OAAO;AAGxE,SAAO,OAAO"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { MutableRequestState } from "./mutable-request-state.js";
|
|
2
|
+
import { OutputContext } from "./request-output-context.js";
|
|
3
|
+
import { HTTPMethod } from "./api.js";
|
|
4
|
+
import { AnyFragnoRouteConfig } from "./route.js";
|
|
5
|
+
import { ExtractRouteByPath, ExtractRoutePath } from "../client/client.js";
|
|
6
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
7
|
+
|
|
8
|
+
//#region src/api/request-middleware.d.ts
|
|
9
|
+
type FragnoMiddlewareCallback<TRoutes extends readonly AnyFragnoRouteConfig[], TDeps, TServices extends Record<string, unknown>> = (inputContext: RequestMiddlewareInputContext<TRoutes>, outputContext: RequestMiddlewareOutputContext<TDeps, TServices>) => Promise<Response | undefined> | Response | undefined;
|
|
10
|
+
interface RequestMiddlewareOptions {
|
|
11
|
+
path: string;
|
|
12
|
+
method: HTTPMethod;
|
|
13
|
+
request: Request;
|
|
14
|
+
state: MutableRequestState;
|
|
15
|
+
}
|
|
16
|
+
declare class RequestMiddlewareOutputContext<const TDeps, const TServices extends Record<string, unknown>> extends OutputContext<unknown, string> {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(deps: TDeps, services: TServices);
|
|
19
|
+
get deps(): TDeps;
|
|
20
|
+
get services(): TServices;
|
|
21
|
+
}
|
|
22
|
+
declare class RequestMiddlewareInputContext<const TRoutes extends readonly AnyFragnoRouteConfig[]> {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(routes: TRoutes, options: RequestMiddlewareOptions);
|
|
25
|
+
get path(): string;
|
|
26
|
+
get method(): HTTPMethod;
|
|
27
|
+
get pathParams(): Record<string, string>;
|
|
28
|
+
get queryParams(): URLSearchParams;
|
|
29
|
+
get headers(): Headers;
|
|
30
|
+
get inputSchema(): StandardSchemaV1 | undefined;
|
|
31
|
+
get outputSchema(): StandardSchemaV1 | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Access to the mutable request state.
|
|
34
|
+
* Use this to modify query parameters, path parameters, or request body.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* // Modify body
|
|
39
|
+
* requestState.setBody({ modified: true });
|
|
40
|
+
*
|
|
41
|
+
* // Query params are already accessible via queryParams getter
|
|
42
|
+
* // Path params are already accessible via pathParams getter
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
get requestState(): MutableRequestState;
|
|
46
|
+
ifMatchesRoute: <const TMethod extends HTTPMethod, const TPath extends ExtractRoutePath<TRoutes>, const TRoute extends ExtractRouteByPath<TRoutes, TPath, TMethod> = ExtractRouteByPath<TRoutes, TPath, TMethod>>(method: TMethod, path: TPath, handler: (...args: Parameters<TRoute["handler"]>) => Promise<Response | undefined | void> | Response | undefined | void) => Promise<Response | undefined>;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { FragnoMiddlewareCallback, RequestMiddlewareInputContext, RequestMiddlewareOptions, RequestMiddlewareOutputContext };
|
|
50
|
+
//# sourceMappingURL=request-middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-middleware.d.ts","names":[],"sources":["../../src/api/request-middleware.ts"],"sourcesContent":[],"mappings":";;;;;;;;KASY,kDACe,iDAEP,0CAEJ,8BAA8B,yBAC7B,+BAA+B,OAAO,eAClD,QAAQ,wBAAwB;UAEpB,wBAAA;EATL,IAAA,EAAA,MAAA;EACe,MAAA,EAUjB,UAViB;EAEP,OAAA,EAST,OATS;EAE0B,KAAA,EAQrC,mBARqC;;AACE,cAUnC,8BAVmC,CAAA,WAAA,EAAA,wBAYtB,MAZsB,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,SAatC,aAbsC,CAAA,OAAA,EAAA,MAAA,CAAA,CAAA;EAAO,CAAA,OAAA;EAAtC,WAAA,CAAA,IAAA,EAiBG,KAjBH,EAAA,QAAA,EAiBoB,SAjBpB;EACJ,IAAA,IAAA,CAAA,CAAA,EAsBC,KAtBD;EAAR,IAAA,QAAA,CAAA,CAAA,EA0Ba,SA1Bb;;AAAwC,cA+BhC,6BA/BgC,CAAA,sBAAA,SA+B6B,oBA/B7B,EAAA,CAAA,CAAA;EAE5B,CAAA,OAAA;EAEP,WAAA,CAAA,MAAA,EAgCY,OAhCZ,EAAA,OAAA,EAgC8B,wBAhC9B;EACC,IAAA,IAAA,CAAA,CAAA,EAAA,MAAA;EACF,IAAA,MAAA,CAAA,CAAA,EAiDO,UAjDP;EAAmB,IAAA,UAAA,CAAA,CAAA,EAqDR,MArDQ,CAAA,MAAA,EAAA,MAAA,CAAA;EAGf,IAAA,WAAA,CAAA,CAAA,EAsDQ,eAtDsB;EAEjB,IAAA,OAAA,CAAA,CAAA,EAwDT,OAxDS;EAKN,IAAA,WAAA,CAAA,CAAA,EAuDC,gBAvDD,GAAA,SAAA;EAAiB,IAAA,YAAA,CAAA,CAAA,EA2Df,gBA3De,GAAA,SAAA;EAMvB;;;;AASd;;;;;;;;;EA4CsB,IAAA,YAAA,CAAA,CAAA,EAiBA,mBAjBA;EAiBA,cAAA,EAAA,CAAA,sBAMI,UANJ,EAAA,oBAOE,gBAPF,CAOmB,OAPnB,CAAA,EAAA,qBAQG,kBARH,CAQsB,OARtB,EAQ+B,KAR/B,EAQsC,OARtC,CAAA,GAQiD,kBARjD,CAShB,OATgB,EAUhB,KAVgB,EAWhB,OAXgB,CAAA,CAAA,CAAA,MAAA,EAcV,OAdU,EAAA,IAAA,EAeZ,KAfY,EAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EAiBP,UAjBO,CAiBI,MAjBJ,CAAA,SAAA,CAAA,CAAA,EAAA,GAkBb,OAlBa,CAkBL,QAlBK,GAAA,SAAA,GAAA,IAAA,CAAA,GAkB0B,QAlB1B,GAAA,SAAA,GAAA,IAAA,EAAA,GAmBjB,OAnBiB,CAmBT,QAnBS,GAAA,SAAA,CAAA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { RequestInputContext } from "./request-input-context.js";
|
|
2
|
+
import { OutputContext, RequestOutputContext } from "./request-output-context.js";
|
|
3
|
+
|
|
4
|
+
//#region src/api/request-middleware.ts
|
|
5
|
+
var RequestMiddlewareOutputContext = class extends OutputContext {
|
|
6
|
+
#deps;
|
|
7
|
+
#services;
|
|
8
|
+
constructor(deps, services) {
|
|
9
|
+
super();
|
|
10
|
+
this.#deps = deps;
|
|
11
|
+
this.#services = services;
|
|
12
|
+
}
|
|
13
|
+
get deps() {
|
|
14
|
+
return this.#deps;
|
|
15
|
+
}
|
|
16
|
+
get services() {
|
|
17
|
+
return this.#services;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var RequestMiddlewareInputContext = class {
|
|
21
|
+
#options;
|
|
22
|
+
#route;
|
|
23
|
+
#state;
|
|
24
|
+
constructor(routes, options) {
|
|
25
|
+
this.#options = options;
|
|
26
|
+
this.#state = options.state;
|
|
27
|
+
const route = routes.find((route$1) => route$1.path === options.path && route$1.method === options.method);
|
|
28
|
+
if (!route) throw new Error(`Route not found: ${options.path} ${options.method}`);
|
|
29
|
+
this.#route = route;
|
|
30
|
+
}
|
|
31
|
+
get path() {
|
|
32
|
+
return this.#options.path;
|
|
33
|
+
}
|
|
34
|
+
get method() {
|
|
35
|
+
return this.#options.method;
|
|
36
|
+
}
|
|
37
|
+
get pathParams() {
|
|
38
|
+
return this.#state.pathParams;
|
|
39
|
+
}
|
|
40
|
+
get queryParams() {
|
|
41
|
+
return this.#state.searchParams;
|
|
42
|
+
}
|
|
43
|
+
get headers() {
|
|
44
|
+
return this.#state.headers;
|
|
45
|
+
}
|
|
46
|
+
get inputSchema() {
|
|
47
|
+
return this.#route.inputSchema;
|
|
48
|
+
}
|
|
49
|
+
get outputSchema() {
|
|
50
|
+
return this.#route.outputSchema;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Access to the mutable request state.
|
|
54
|
+
* Use this to modify query parameters, path parameters, or request body.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // Modify body
|
|
59
|
+
* requestState.setBody({ modified: true });
|
|
60
|
+
*
|
|
61
|
+
* // Query params are already accessible via queryParams getter
|
|
62
|
+
* // Path params are already accessible via pathParams getter
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
get requestState() {
|
|
66
|
+
return this.#state;
|
|
67
|
+
}
|
|
68
|
+
ifMatchesRoute = async (method, path, handler) => {
|
|
69
|
+
if (this.path !== path || this.method !== method) return;
|
|
70
|
+
return await handler(await RequestInputContext.fromRequest({
|
|
71
|
+
request: this.#options.request,
|
|
72
|
+
method: this.#options.method,
|
|
73
|
+
path,
|
|
74
|
+
pathParams: this.pathParams,
|
|
75
|
+
inputSchema: this.#route.inputSchema,
|
|
76
|
+
state: this.#state
|
|
77
|
+
}), new RequestOutputContext(this.#route.outputSchema));
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
export { RequestMiddlewareInputContext, RequestMiddlewareOutputContext };
|
|
83
|
+
//# sourceMappingURL=request-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-middleware.js","names":["#deps","#services","#options","#route","#state","route"],"sources":["../../src/api/request-middleware.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ExtractRouteByPath, ExtractRoutePath } from \"../client/client\";\nimport type { HTTPMethod } from \"./api\";\nimport type { ExtractPathParams } from \"./internal/path\";\nimport type { AnyFragnoRouteConfig } from \"./route\";\nimport { RequestInputContext } from \"./request-input-context\";\nimport { OutputContext, RequestOutputContext } from \"./request-output-context\";\nimport { MutableRequestState } from \"./mutable-request-state\";\n\nexport type FragnoMiddlewareCallback<\n TRoutes extends readonly AnyFragnoRouteConfig[],\n TDeps,\n TServices extends Record<string, unknown>,\n> = (\n inputContext: RequestMiddlewareInputContext<TRoutes>,\n outputContext: RequestMiddlewareOutputContext<TDeps, TServices>,\n) => Promise<Response | undefined> | Response | undefined;\n\nexport interface RequestMiddlewareOptions {\n path: string;\n method: HTTPMethod;\n request: Request;\n state: MutableRequestState;\n}\n\nexport class RequestMiddlewareOutputContext<\n const TDeps,\n const TServices extends Record<string, unknown>,\n> extends OutputContext<unknown, string> {\n readonly #deps: TDeps;\n readonly #services: TServices;\n\n constructor(deps: TDeps, services: TServices) {\n super();\n this.#deps = deps;\n this.#services = services;\n }\n\n get deps(): TDeps {\n return this.#deps;\n }\n\n get services(): TServices {\n return this.#services;\n }\n}\n\nexport class RequestMiddlewareInputContext<const TRoutes extends readonly AnyFragnoRouteConfig[]> {\n readonly #options: RequestMiddlewareOptions;\n readonly #route: TRoutes[number];\n readonly #state: MutableRequestState;\n\n constructor(routes: TRoutes, options: RequestMiddlewareOptions) {\n this.#options = options;\n this.#state = options.state;\n\n const route = routes.find(\n (route) => route.path === options.path && route.method === options.method,\n );\n\n if (!route) {\n throw new Error(`Route not found: ${options.path} ${options.method}`);\n }\n\n this.#route = route;\n }\n\n get path(): string {\n return this.#options.path;\n }\n\n get method(): HTTPMethod {\n return this.#options.method;\n }\n\n get pathParams(): Record<string, string> {\n return this.#state.pathParams;\n }\n\n get queryParams(): URLSearchParams {\n return this.#state.searchParams;\n }\n\n get headers(): Headers {\n return this.#state.headers;\n }\n\n get inputSchema(): StandardSchemaV1 | undefined {\n return this.#route.inputSchema;\n }\n\n get outputSchema(): StandardSchemaV1 | undefined {\n return this.#route.outputSchema;\n }\n\n /**\n * Access to the mutable request state.\n * Use this to modify query parameters, path parameters, or request body.\n *\n * @example\n * ```typescript\n * // Modify body\n * requestState.setBody({ modified: true });\n *\n * // Query params are already accessible via queryParams getter\n * // Path params are already accessible via pathParams getter\n * ```\n */\n get requestState(): MutableRequestState {\n return this.#state;\n }\n\n // Defined as a field so that `this` reference stays in tact when destructuring\n ifMatchesRoute = async <\n const TMethod extends HTTPMethod,\n const TPath extends ExtractRoutePath<TRoutes>,\n const TRoute extends ExtractRouteByPath<TRoutes, TPath, TMethod> = ExtractRouteByPath<\n TRoutes,\n TPath,\n TMethod\n >,\n >(\n method: TMethod,\n path: TPath,\n handler: (\n ...args: Parameters<TRoute[\"handler\"]>\n ) => Promise<Response | undefined | void> | Response | undefined | void,\n ): Promise<Response | undefined> => {\n if (this.path !== path || this.method !== method) {\n return undefined;\n }\n\n // TODO(Wilco): We should support reading/modifying headers here.\n const inputContext = await RequestInputContext.fromRequest({\n request: this.#options.request,\n method: this.#options.method,\n path: path,\n pathParams: this.pathParams as ExtractPathParams<TPath>,\n inputSchema: this.#route.inputSchema,\n state: this.#state,\n });\n\n const outputContext = new RequestOutputContext(this.#route.outputSchema);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return await (handler as any)(inputContext, outputContext);\n };\n}\n"],"mappings":";;;;AAyBA,IAAa,iCAAb,cAGU,cAA+B;CACvC,CAASA;CACT,CAASC;CAET,YAAY,MAAa,UAAqB;AAC5C,SAAO;AACP,QAAKD,OAAQ;AACb,QAAKC,WAAY;;CAGnB,IAAI,OAAc;AAChB,SAAO,MAAKD;;CAGd,IAAI,WAAsB;AACxB,SAAO,MAAKC;;;AAIhB,IAAa,gCAAb,MAAkG;CAChG,CAASC;CACT,CAASC;CACT,CAASC;CAET,YAAY,QAAiB,SAAmC;AAC9D,QAAKF,UAAW;AAChB,QAAKE,QAAS,QAAQ;EAEtB,MAAM,QAAQ,OAAO,MAClB,YAAUC,QAAM,SAAS,QAAQ,QAAQA,QAAM,WAAW,QAAQ,OACpE;AAED,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,oBAAoB,QAAQ,KAAK,GAAG,QAAQ,SAAS;AAGvE,QAAKF,QAAS;;CAGhB,IAAI,OAAe;AACjB,SAAO,MAAKD,QAAS;;CAGvB,IAAI,SAAqB;AACvB,SAAO,MAAKA,QAAS;;CAGvB,IAAI,aAAqC;AACvC,SAAO,MAAKE,MAAO;;CAGrB,IAAI,cAA+B;AACjC,SAAO,MAAKA,MAAO;;CAGrB,IAAI,UAAmB;AACrB,SAAO,MAAKA,MAAO;;CAGrB,IAAI,cAA4C;AAC9C,SAAO,MAAKD,MAAO;;CAGrB,IAAI,eAA6C;AAC/C,SAAO,MAAKA,MAAO;;;;;;;;;;;;;;;CAgBrB,IAAI,eAAoC;AACtC,SAAO,MAAKC;;CAId,iBAAiB,OASf,QACA,MACA,YAGkC;AAClC,MAAI,KAAK,SAAS,QAAQ,KAAK,WAAW,OACxC;AAgBF,SAAO,MAAO,QAZO,MAAM,oBAAoB,YAAY;GACzD,SAAS,MAAKF,QAAS;GACvB,QAAQ,MAAKA,QAAS;GAChB;GACN,YAAY,KAAK;GACjB,aAAa,MAAKC,MAAO;GACzB,OAAO,MAAKC;GACb,CAAC,EAEoB,IAAI,qBAAqB,MAAKD,MAAO,aAAa,CAGd"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ContentlessStatusCode, StatusCode } from "../http/http-status.js";
|
|
2
|
+
import { ResponseStream } from "./internal/response-stream.js";
|
|
3
|
+
import { InferOrUnknown } from "../util/types-util.js";
|
|
4
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
|
+
|
|
6
|
+
//#region src/api/request-output-context.d.ts
|
|
7
|
+
interface ResponseInit<T extends StatusCode = StatusCode> {
|
|
8
|
+
headers?: HeadersInit;
|
|
9
|
+
status?: T;
|
|
10
|
+
statusText?: string;
|
|
11
|
+
}
|
|
12
|
+
declare abstract class OutputContext<const TOutput, const TErrorCode extends string> {
|
|
13
|
+
/**
|
|
14
|
+
* Creates an error response.
|
|
15
|
+
*
|
|
16
|
+
* Shortcut for `throw new FragnoApiError(...)`
|
|
17
|
+
*/
|
|
18
|
+
error: ({
|
|
19
|
+
message,
|
|
20
|
+
code
|
|
21
|
+
}: {
|
|
22
|
+
message: string;
|
|
23
|
+
code: TErrorCode;
|
|
24
|
+
}, initOrStatus?: ResponseInit | StatusCode, headers?: HeadersInit) => Response;
|
|
25
|
+
empty: (initOrStatus?: ResponseInit<ContentlessStatusCode> | ContentlessStatusCode, headers?: HeadersInit) => Response;
|
|
26
|
+
json: (object: TOutput, initOrStatus?: ResponseInit | StatusCode, headers?: HeadersInit) => Response;
|
|
27
|
+
jsonStream: (cb: (stream: ResponseStream<TOutput>) => void | Promise<void>, {
|
|
28
|
+
onError,
|
|
29
|
+
headers
|
|
30
|
+
}?: {
|
|
31
|
+
onError?: (error: Error, stream: ResponseStream<TOutput>) => void | Promise<void>;
|
|
32
|
+
headers?: HeadersInit;
|
|
33
|
+
}) => Response;
|
|
34
|
+
}
|
|
35
|
+
declare class RequestOutputContext<const TOutputSchema extends StandardSchemaV1 | undefined = undefined, const TErrorCode extends string = string> extends OutputContext<InferOrUnknown<TOutputSchema>, TErrorCode> {
|
|
36
|
+
#private;
|
|
37
|
+
constructor(outputSchema?: TOutputSchema);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { OutputContext, RequestOutputContext };
|
|
41
|
+
//# sourceMappingURL=request-output-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-output-context.d.ts","names":[],"sources":["../../src/api/request-output-context.ts"],"sourcesContent":[],"mappings":";;;;;;UAOU,uBAAuB,aAAa;EAApC,OAAA,CAAA,EACE,WADU;EAAW,MAAA,CAAA,EAEtB,CAFsB;EAAa,UAAA,CAAA,EAAA,MAAA;;AAEnC,uBAkCW,aAlCX,CAAA,aAAA,EAAA,yBAAA,MAAA,CAAA,CAAA;EAAC;AAkCZ;;;;EAQmB,KAAA,EAAA,CAAA;IAAA,OAAA;IAAA;EAEd,CAFc,EAAA;IAAe,OAAA,EAAA,MAAA;IACpB,IAAA,EAFkC,UAElC;EACT,CAAA,EAAA,YAAA,CAAA,EAFc,YAEd,GAF6B,UAE7B,EAAA,OAAA,CAAA,EADS,WACT,EAAA,GAAA,QAAA;EAiB2B,KAAA,EAAA,CAAA,YAAA,CAAA,EAAb,YAAa,CAAA,qBAAA,CAAA,GAAyB,qBAAzB,EAAA,OAAA,CAAA,EAClB,WADkB,EAAA,GAE3B,QAF2B;EAAb,IAAA,EAAA,CAAA,MAAA,EA6BP,OA7BO,EAAA,YAAA,CAAA,EA8BA,YA9BA,GA8Be,UA9Bf,EAAA,OAAA,CAAA,EA+BL,WA/BK,EAAA,GAgCd,QAhCc;EAAsC,UAAA,EAAA,CAAA,EAAA,EAAA,CAAA,MAAA,EAuDxC,cAvDwC,CAuDzB,OAvDyB,CAAA,EAAA,GAAA,IAAA,GAuDL,OAvDK,CAAA,IAAA,CAAA,EAAA;IAAA,OAAA;IAAA;EA6B7C,CAAA,CA7B6C,EAAA;IAC3C,OAAA,CAAA,EAAA,CAAA,KAAA,EA2DU,KA3DV,EAAA,MAAA,EA2DyB,cA3DzB,CA2DwC,OA3DxC,CAAA,EAAA,GAAA,IAAA,GA2D4D,OA3D5D,CAAA,IAAA,CAAA;IACT,OAAA,CAAA,EA2DW,WA3DX;EA2BO,CAAA,EAAA,GAkCP,QAlCO;;AACsB,cAqErB,oBArEqB,CAAA,4BAsEJ,gBAtEI,GAAA,SAAA,GAAA,SAAA,EAAA,yBAAA,MAAA,GAAA,MAAA,CAAA,SAwExB,aAxEwB,CAwEV,cAxEU,CAwEK,aAxEL,CAAA,EAwEqB,UAxErB,CAAA,CAAA;EACpB,CAAA,OAAA;EACT,WAAA,CAAA,YAAA,CAAA,EA0EwB,aA1ExB"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ResponseStream } from "./internal/response-stream.js";
|
|
2
|
+
|
|
3
|
+
//#region src/api/request-output-context.ts
|
|
4
|
+
/**
|
|
5
|
+
* Utility function to merge headers from multiple sources.
|
|
6
|
+
* Later headers override earlier ones.
|
|
7
|
+
*/
|
|
8
|
+
function mergeHeaders(...headerSources) {
|
|
9
|
+
const mergedHeaders = new Headers();
|
|
10
|
+
for (const headerSource of headerSources) {
|
|
11
|
+
if (!headerSource) continue;
|
|
12
|
+
if (headerSource instanceof Headers) for (const [key, value] of headerSource.entries()) mergedHeaders.set(key, value);
|
|
13
|
+
else if (Array.isArray(headerSource)) for (const [key, value] of headerSource) mergedHeaders.set(key, value);
|
|
14
|
+
else for (const [key, value] of Object.entries(headerSource)) mergedHeaders.set(key, value);
|
|
15
|
+
}
|
|
16
|
+
return mergedHeaders;
|
|
17
|
+
}
|
|
18
|
+
var OutputContext = class {
|
|
19
|
+
/**
|
|
20
|
+
* Creates an error response.
|
|
21
|
+
*
|
|
22
|
+
* Shortcut for `throw new FragnoApiError(...)`
|
|
23
|
+
*/
|
|
24
|
+
error = ({ message, code }, initOrStatus, headers) => {
|
|
25
|
+
if (typeof initOrStatus === "undefined") return Response.json({
|
|
26
|
+
message,
|
|
27
|
+
code
|
|
28
|
+
}, {
|
|
29
|
+
status: 500,
|
|
30
|
+
headers
|
|
31
|
+
});
|
|
32
|
+
if (typeof initOrStatus === "number") return Response.json({
|
|
33
|
+
message,
|
|
34
|
+
code
|
|
35
|
+
}, {
|
|
36
|
+
status: initOrStatus,
|
|
37
|
+
headers
|
|
38
|
+
});
|
|
39
|
+
const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
|
|
40
|
+
return Response.json({
|
|
41
|
+
message,
|
|
42
|
+
code
|
|
43
|
+
}, {
|
|
44
|
+
status: initOrStatus.status,
|
|
45
|
+
headers: mergedHeaders
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
empty = (initOrStatus, headers) => {
|
|
49
|
+
const defaultHeaders = {};
|
|
50
|
+
if (typeof initOrStatus === "undefined") {
|
|
51
|
+
const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
|
|
52
|
+
return new Response(null, {
|
|
53
|
+
status: 201,
|
|
54
|
+
headers: mergedHeaders$1
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (typeof initOrStatus === "number") {
|
|
58
|
+
const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
|
|
59
|
+
return new Response(null, {
|
|
60
|
+
status: initOrStatus,
|
|
61
|
+
headers: mergedHeaders$1
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const mergedHeaders = mergeHeaders(defaultHeaders, initOrStatus.headers, headers);
|
|
65
|
+
return new Response(null, {
|
|
66
|
+
status: initOrStatus.status,
|
|
67
|
+
headers: mergedHeaders
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
json = (object, initOrStatus, headers) => {
|
|
71
|
+
if (typeof initOrStatus === "undefined") return Response.json(object, {
|
|
72
|
+
status: 200,
|
|
73
|
+
headers
|
|
74
|
+
});
|
|
75
|
+
if (typeof initOrStatus === "number") return Response.json(object, {
|
|
76
|
+
status: initOrStatus,
|
|
77
|
+
headers
|
|
78
|
+
});
|
|
79
|
+
const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
|
|
80
|
+
return Response.json(object, {
|
|
81
|
+
status: initOrStatus.status,
|
|
82
|
+
headers: mergedHeaders
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
jsonStream = (cb, { onError, headers } = {}) => {
|
|
86
|
+
const defaultHeaders = {
|
|
87
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
88
|
+
"transfer-encoding": "chunked",
|
|
89
|
+
"cache-control": "no-cache"
|
|
90
|
+
};
|
|
91
|
+
const { readable, writable } = new TransformStream();
|
|
92
|
+
const stream = new ResponseStream(writable, readable);
|
|
93
|
+
(async () => {
|
|
94
|
+
try {
|
|
95
|
+
await cb(stream);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (e === void 0) {} else if (e instanceof Error && onError) await onError(e, stream);
|
|
98
|
+
else console.error(e);
|
|
99
|
+
} finally {
|
|
100
|
+
stream.close();
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
return new Response(stream.responseReadable, {
|
|
104
|
+
status: 200,
|
|
105
|
+
headers: mergeHeaders(defaultHeaders, headers)
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
var RequestOutputContext = class extends OutputContext {
|
|
110
|
+
#outputSchema;
|
|
111
|
+
constructor(outputSchema) {
|
|
112
|
+
super();
|
|
113
|
+
this.#outputSchema = outputSchema;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
export { OutputContext, RequestOutputContext };
|
|
119
|
+
//# sourceMappingURL=request-output-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-output-context.js","names":["mergedHeaders","#outputSchema"],"sources":["../../src/api/request-output-context.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ContentlessStatusCode, StatusCode } from \"../http/http-status\";\nimport { ResponseStream } from \"./internal/response-stream\";\nimport type { InferOrUnknown } from \"../util/types-util\";\n\nexport type ResponseData = string | ArrayBuffer | ReadableStream | Uint8Array<ArrayBuffer>;\n\ninterface ResponseInit<T extends StatusCode = StatusCode> {\n headers?: HeadersInit;\n status?: T;\n statusText?: string;\n}\n\n/**\n * Utility function to merge headers from multiple sources.\n * Later headers override earlier ones.\n */\nfunction mergeHeaders(...headerSources: (HeadersInit | undefined)[]): HeadersInit | undefined {\n const mergedHeaders = new Headers();\n\n for (const headerSource of headerSources) {\n if (!headerSource) {\n continue;\n }\n\n if (headerSource instanceof Headers) {\n for (const [key, value] of headerSource.entries()) {\n mergedHeaders.set(key, value);\n }\n } else if (Array.isArray(headerSource)) {\n for (const [key, value] of headerSource) {\n mergedHeaders.set(key, value);\n }\n } else {\n for (const [key, value] of Object.entries(headerSource)) {\n mergedHeaders.set(key, value);\n }\n }\n }\n\n return mergedHeaders;\n}\n\nexport abstract class OutputContext<const TOutput, const TErrorCode extends string> {\n /**\n * Creates an error response.\n *\n * Shortcut for `throw new FragnoApiError(...)`\n */\n error = (\n { message, code }: { message: string; code: TErrorCode },\n initOrStatus?: ResponseInit | StatusCode,\n headers?: HeadersInit,\n ): Response => {\n if (typeof initOrStatus === \"undefined\") {\n return Response.json({ message: message, code }, { status: 500, headers });\n }\n\n if (typeof initOrStatus === \"number\") {\n return Response.json({ message: message, code }, { status: initOrStatus, headers });\n }\n\n const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);\n return Response.json(\n { message: message, code },\n { status: initOrStatus.status, headers: mergedHeaders },\n );\n };\n\n empty = (\n initOrStatus?: ResponseInit<ContentlessStatusCode> | ContentlessStatusCode,\n headers?: HeadersInit,\n ): Response => {\n const defaultHeaders = {};\n\n if (typeof initOrStatus === \"undefined\") {\n const mergedHeaders = mergeHeaders(defaultHeaders, headers);\n return new Response(null, {\n status: 201,\n headers: mergedHeaders,\n });\n }\n\n if (typeof initOrStatus === \"number\") {\n const mergedHeaders = mergeHeaders(defaultHeaders, headers);\n return new Response(null, {\n status: initOrStatus,\n headers: mergedHeaders,\n });\n }\n\n const mergedHeaders = mergeHeaders(defaultHeaders, initOrStatus.headers, headers);\n return new Response(null, {\n status: initOrStatus.status,\n headers: mergedHeaders,\n });\n };\n\n json = (\n object: TOutput,\n initOrStatus?: ResponseInit | StatusCode,\n headers?: HeadersInit,\n ): Response => {\n if (typeof initOrStatus === \"undefined\") {\n return Response.json(object, {\n status: 200,\n headers,\n });\n }\n\n if (typeof initOrStatus === \"number\") {\n return Response.json(object, {\n status: initOrStatus,\n headers,\n });\n }\n\n const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);\n return Response.json(object, {\n status: initOrStatus.status,\n headers: mergedHeaders,\n });\n };\n\n jsonStream = (\n cb: (stream: ResponseStream<TOutput>) => void | Promise<void>,\n {\n onError,\n headers,\n }: {\n onError?: (error: Error, stream: ResponseStream<TOutput>) => void | Promise<void>;\n headers?: HeadersInit;\n } = {},\n ): Response => {\n // Note: this is intentionally an arrow function (=>) to keep `this` context.\n const defaultHeaders = {\n \"content-type\": \"application/x-ndjson; charset=utf-8\",\n \"transfer-encoding\": \"chunked\",\n \"cache-control\": \"no-cache\",\n };\n\n const { readable, writable } = new TransformStream();\n const stream = new ResponseStream(writable, readable);\n\n (async () => {\n try {\n await cb(stream);\n } catch (e) {\n if (e === undefined) {\n // If reading is canceled without a reason value (e.g. by StreamingApi)\n // then the .pipeTo() promise will reject with undefined.\n // In this case, do nothing because the stream is already closed.\n } else if (e instanceof Error && onError) {\n await onError(e, stream);\n } else {\n console.error(e);\n }\n } finally {\n stream.close();\n }\n })();\n\n return new Response(stream.responseReadable, {\n status: 200,\n headers: mergeHeaders(defaultHeaders, headers),\n });\n };\n}\n\nexport class RequestOutputContext<\n const TOutputSchema extends StandardSchemaV1 | undefined = undefined,\n const TErrorCode extends string = string,\n> extends OutputContext<InferOrUnknown<TOutputSchema>, TErrorCode> {\n // eslint-disable-next-line no-unused-private-class-members\n #outputSchema?: TOutputSchema;\n\n constructor(outputSchema?: TOutputSchema) {\n super();\n this.#outputSchema = outputSchema;\n }\n}\n"],"mappings":";;;;;;;AAiBA,SAAS,aAAa,GAAG,eAAqE;CAC5F,MAAM,gBAAgB,IAAI,SAAS;AAEnC,MAAK,MAAM,gBAAgB,eAAe;AACxC,MAAI,CAAC,aACH;AAGF,MAAI,wBAAwB,QAC1B,MAAK,MAAM,CAAC,KAAK,UAAU,aAAa,SAAS,CAC/C,eAAc,IAAI,KAAK,MAAM;WAEtB,MAAM,QAAQ,aAAa,CACpC,MAAK,MAAM,CAAC,KAAK,UAAU,aACzB,eAAc,IAAI,KAAK,MAAM;MAG/B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,aAAa,CACrD,eAAc,IAAI,KAAK,MAAM;;AAKnC,QAAO;;AAGT,IAAsB,gBAAtB,MAAoF;;;;;;CAMlF,SACE,EAAE,SAAS,QACX,cACA,YACa;AACb,MAAI,OAAO,iBAAiB,YAC1B,QAAO,SAAS,KAAK;GAAW;GAAS;GAAM,EAAE;GAAE,QAAQ;GAAK;GAAS,CAAC;AAG5E,MAAI,OAAO,iBAAiB,SAC1B,QAAO,SAAS,KAAK;GAAW;GAAS;GAAM,EAAE;GAAE,QAAQ;GAAc;GAAS,CAAC;EAGrF,MAAM,gBAAgB,aAAa,aAAa,SAAS,QAAQ;AACjE,SAAO,SAAS,KACd;GAAW;GAAS;GAAM,EAC1B;GAAE,QAAQ,aAAa;GAAQ,SAAS;GAAe,CACxD;;CAGH,SACE,cACA,YACa;EACb,MAAM,iBAAiB,EAAE;AAEzB,MAAI,OAAO,iBAAiB,aAAa;GACvC,MAAMA,kBAAgB,aAAa,gBAAgB,QAAQ;AAC3D,UAAO,IAAI,SAAS,MAAM;IACxB,QAAQ;IACR,SAASA;IACV,CAAC;;AAGJ,MAAI,OAAO,iBAAiB,UAAU;GACpC,MAAMA,kBAAgB,aAAa,gBAAgB,QAAQ;AAC3D,UAAO,IAAI,SAAS,MAAM;IACxB,QAAQ;IACR,SAASA;IACV,CAAC;;EAGJ,MAAM,gBAAgB,aAAa,gBAAgB,aAAa,SAAS,QAAQ;AACjF,SAAO,IAAI,SAAS,MAAM;GACxB,QAAQ,aAAa;GACrB,SAAS;GACV,CAAC;;CAGJ,QACE,QACA,cACA,YACa;AACb,MAAI,OAAO,iBAAiB,YAC1B,QAAO,SAAS,KAAK,QAAQ;GAC3B,QAAQ;GACR;GACD,CAAC;AAGJ,MAAI,OAAO,iBAAiB,SAC1B,QAAO,SAAS,KAAK,QAAQ;GAC3B,QAAQ;GACR;GACD,CAAC;EAGJ,MAAM,gBAAgB,aAAa,aAAa,SAAS,QAAQ;AACjE,SAAO,SAAS,KAAK,QAAQ;GAC3B,QAAQ,aAAa;GACrB,SAAS;GACV,CAAC;;CAGJ,cACE,IACA,EACE,SACA,YAIE,EAAE,KACO;EAEb,MAAM,iBAAiB;GACrB,gBAAgB;GAChB,qBAAqB;GACrB,iBAAiB;GAClB;EAED,MAAM,EAAE,UAAU,aAAa,IAAI,iBAAiB;EACpD,MAAM,SAAS,IAAI,eAAe,UAAU,SAAS;AAErD,GAAC,YAAY;AACX,OAAI;AACF,UAAM,GAAG,OAAO;YACT,GAAG;AACV,QAAI,MAAM,QAAW,YAIV,aAAa,SAAS,QAC/B,OAAM,QAAQ,GAAG,OAAO;QAExB,SAAQ,MAAM,EAAE;aAEV;AACR,WAAO,OAAO;;MAEd;AAEJ,SAAO,IAAI,SAAS,OAAO,kBAAkB;GAC3C,QAAQ;GACR,SAAS,aAAa,gBAAgB,QAAQ;GAC/C,CAAC;;;AAIN,IAAa,uBAAb,cAGU,cAAyD;CAEjE;CAEA,YAAY,cAA8B;AACxC,SAAO;AACP,QAAKC,eAAgB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ExtractPathParams } from "./internal/path.js";
|
|
2
|
+
import { InferOr } from "../util/types-util.js";
|
|
3
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
4
|
+
|
|
5
|
+
//#region src/api/route-handler-input-options.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for calling a route handler
|
|
9
|
+
*/
|
|
10
|
+
type RouteHandlerInputOptions<TPath extends string, TInputSchema extends StandardSchemaV1 | undefined> = {
|
|
11
|
+
pathParams?: ExtractPathParams<TPath>;
|
|
12
|
+
query?: URLSearchParams | Record<string, string>;
|
|
13
|
+
headers?: Headers | Record<string, string>;
|
|
14
|
+
} & (TInputSchema extends undefined ? {
|
|
15
|
+
body?: never;
|
|
16
|
+
} : {
|
|
17
|
+
body: InferOr<TInputSchema, unknown>;
|
|
18
|
+
});
|
|
19
|
+
//#endregion
|
|
20
|
+
export { RouteHandlerInputOptions };
|
|
21
|
+
//# sourceMappingURL=route-handler-input-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-handler-input-options.d.ts","names":[],"sources":["../../src/api/route-handler-input-options.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAOA;AAEuB,KAFX,wBAEW,CAAA,cAAA,MAAA,EAAA,qBAAA,gBAAA,GAAA,SAAA,CAAA,GAAA;EAEU,UAAA,CAAA,EAAlB,iBAAkB,CAAA,KAAA,CAAA;EAAlB,KAAA,CAAA,EACL,eADK,GACa,MADb,CAAA,MAAA,EAAA,MAAA,CAAA;EACL,OAAA,CAAA,EACE,OADF,GACY,MADZ,CAAA,MAAA,EAAA,MAAA,CAAA;CAAkB,GAAA,CAEvB,YAFuB,SAAA,SAAA,GAAA;EAChB,IAAA,CAAA,EAAA,KAAA;CAAU,GAAA;EACjB,IAAA,EAA4D,OAA5D,CAAoE,YAApE,EAAA,OAAA,CAAA;CAAoE,CAAA"}
|