@thomasfosterau/effect-sveltekit 0.0.0 → 0.1.0
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/README.md +36 -13
- package/dist/Hooks.d.ts +2 -2
- package/dist/Hooks.js +2 -1
- package/dist/Hooks.js.map +1 -1
- package/dist/adapter/assets.d.ts +33 -0
- package/dist/adapter/assets.js +87 -0
- package/dist/adapter/assets.js.map +1 -0
- package/dist/adapter/handler.d.ts +75 -10
- package/dist/adapter/handler.js +73 -44
- package/dist/adapter/handler.js.map +1 -1
- package/dist/adapter/index.d.ts +15 -0
- package/dist/adapter/index.js +43 -5
- package/dist/adapter/index.js.map +1 -1
- package/dist/app-runtime.d.ts +18 -2
- package/dist/app-runtime.js +13 -10
- package/dist/app-runtime.js.map +1 -1
- package/dist/internal/remote-run.d.ts +17 -0
- package/dist/internal/remote-run.js +39 -0
- package/dist/internal/remote-run.js.map +1 -0
- package/dist/internal/remote-validators.js +23 -0
- package/dist/internal/remote-validators.js.map +1 -0
- package/dist/remote.server.d.ts +107 -33
- package/dist/remote.server.js +96 -41
- package/dist/remote.server.js.map +1 -1
- package/dist/request-runtime.d.ts +138 -0
- package/dist/request-runtime.js +186 -0
- package/dist/request-runtime.js.map +1 -0
- package/dist/server/Endpoint.d.ts +35 -4
- package/dist/server/Endpoint.js +14 -5
- package/dist/server/Endpoint.js.map +1 -1
- package/dist/server.d.ts +3 -2
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/transport.d.ts +43 -5
- package/dist/transport.js +36 -6
- package/dist/transport.js.map +1 -1
- package/package.json +2 -2
package/dist/remote.server.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
import { SvelteKitRuntime } from "./runtime.js";
|
|
2
|
+
import { isBareHandler as isBareHandler$1, toStandardValidator } from "./internal/remote-validators.js";
|
|
3
|
+
import { defaultTranslate, makeRunHelpers } from "./internal/remote-run.js";
|
|
2
4
|
import "effect";
|
|
3
5
|
import "effect/unstable/http/HttpClient";
|
|
4
6
|
import * as AppServer from "$app/server";
|
|
5
7
|
//#region src/remote.server.ts
|
|
6
|
-
|
|
8
|
+
/** The default helpers: a fresh SvelteKit per-request runtime, passthrough translation. */
|
|
9
|
+
const defaultHelpers = makeRunHelpers(() => {
|
|
7
10
|
const event = AppServer.getRequestEvent();
|
|
8
|
-
return SvelteKitRuntime.forRequest(event).
|
|
9
|
-
}
|
|
11
|
+
return { runExit: (effect) => SvelteKitRuntime.forRequest(event).runPromiseExit(effect) };
|
|
12
|
+
}, defaultTranslate);
|
|
13
|
+
/** Build helpers from a {@link RequestRuntimeProvider}, translator, and form-error mapper. */
|
|
14
|
+
const helpersFromProvider = (provider, translate, formError) => makeRunHelpers(() => {
|
|
15
|
+
const event = AppServer.getRequestEvent();
|
|
16
|
+
return {
|
|
17
|
+
runExit: (effect) => provider.run(event).runPromiseExit(effect),
|
|
18
|
+
dispose: () => provider.disposeAfterResponse?.(event)
|
|
19
|
+
};
|
|
20
|
+
}, translate, formError);
|
|
10
21
|
function unavailable(name) {
|
|
11
22
|
throw new Error(`${name} is not available in the installed SvelteKit version. Update @sveltejs/kit to use this API.`);
|
|
12
23
|
}
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* This cannot be a simple `typeof value === "function"` check on the handler
|
|
17
|
-
* position: some Standard Schema implementations (such as Effect's
|
|
18
|
-
* `Schema.toStandardSchemaV1`) return callable class values, which would be
|
|
19
|
-
* mistaken for handler functions.
|
|
20
|
-
*/
|
|
21
|
-
const isStandardSchema = (value) => value !== null && (typeof value === "object" || typeof value === "function") && "~standard" in value;
|
|
22
|
-
/** True when the argument is the no-validation handler overload. */
|
|
23
|
-
const isBareHandler = (value) => typeof value === "function" && !isStandardSchema(value);
|
|
24
|
-
function query(arg1, arg2) {
|
|
24
|
+
/** True when the argument is the no-validation handler overload (typed narrowing). */
|
|
25
|
+
const isBareHandler = (value) => isBareHandler$1(value);
|
|
26
|
+
const makeQuery = (run) => ((arg1, arg2) => {
|
|
25
27
|
if (isBareHandler(arg1)) {
|
|
26
28
|
const handler = arg1;
|
|
27
|
-
return AppServer.query(() =>
|
|
29
|
+
return AppServer.query(() => run(handler()));
|
|
28
30
|
}
|
|
29
31
|
if (!arg2) throw new Error("query requires a handler function when a schema is provided");
|
|
30
|
-
return AppServer.query(arg1, (arg) =>
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return AppServer.query.batch(validate, (args) => runRemoteEffect(fn(args)));
|
|
34
|
-
}
|
|
32
|
+
return AppServer.query(toStandardValidator(arg1), (arg) => run(arg2(arg)));
|
|
33
|
+
});
|
|
34
|
+
const makeQueryBatch = (run) => ((validate, fn) => AppServer.query.batch(toStandardValidator(validate), (args) => run(fn(args))));
|
|
35
35
|
const appServerCompat = AppServer;
|
|
36
36
|
const queryCompat = AppServer.query;
|
|
37
37
|
function getQueryLive() {
|
|
@@ -44,45 +44,100 @@ function getRequested() {
|
|
|
44
44
|
if (typeof requestedFn !== "function") unavailable("requested");
|
|
45
45
|
return requestedFn;
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
return getQueryLive()(validate, (arg) =>
|
|
49
|
-
}
|
|
50
|
-
|
|
47
|
+
const makeQueryLive = (run) => ((validate, fn) => {
|
|
48
|
+
return getQueryLive()(toStandardValidator(validate), (arg) => run(fn(arg)));
|
|
49
|
+
});
|
|
50
|
+
const makeForm = (helpers) => ((arg1, arg2) => {
|
|
51
51
|
if (isBareHandler(arg1)) {
|
|
52
52
|
const handler = arg1;
|
|
53
|
-
return AppServer.form(() =>
|
|
53
|
+
return AppServer.form(() => helpers.run(handler()));
|
|
54
54
|
}
|
|
55
55
|
if (!arg2) throw new Error("form requires a handler function when a schema is provided");
|
|
56
|
-
return AppServer.form(arg1, (data, issue) =>
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
return AppServer.form(toStandardValidator(arg1), (data, issue) => helpers.runForm(arg2(data, issue), {
|
|
57
|
+
issue,
|
|
58
|
+
data
|
|
59
|
+
}));
|
|
60
|
+
});
|
|
61
|
+
const makeCommand = (run) => ((arg1, arg2) => {
|
|
59
62
|
if (isBareHandler(arg1)) {
|
|
60
63
|
const handler = arg1;
|
|
61
|
-
return AppServer.command(() =>
|
|
64
|
+
return AppServer.command(() => run(handler()));
|
|
62
65
|
}
|
|
63
66
|
if (!arg2) throw new Error("command requires a handler function when a schema is provided");
|
|
64
|
-
return AppServer.command(arg1, (arg) =>
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
+
return AppServer.command(toStandardValidator(arg1), (arg) => run(arg2(arg)));
|
|
68
|
+
});
|
|
69
|
+
const makePrerender = (run) => ((arg1, arg2, arg3) => {
|
|
67
70
|
if (isBareHandler(arg1)) {
|
|
68
71
|
const handler = arg1;
|
|
69
|
-
return AppServer.prerender(() =>
|
|
72
|
+
return AppServer.prerender(() => run(handler()), arg2);
|
|
70
73
|
}
|
|
71
74
|
if (typeof arg2 !== "function") throw new Error("prerender requires a handler function when a schema is provided");
|
|
72
|
-
return AppServer.prerender(arg1, (arg) =>
|
|
73
|
-
}
|
|
75
|
+
return AppServer.prerender(toStandardValidator(arg1), (arg) => run(arg2(arg)), arg3);
|
|
76
|
+
});
|
|
74
77
|
function requested(...args) {
|
|
75
78
|
return getRequested()(...args);
|
|
76
79
|
}
|
|
80
|
+
const makeQueryNamespace = (run) => Object.assign(makeQuery(run), {
|
|
81
|
+
batch: makeQueryBatch(run),
|
|
82
|
+
live: makeQueryLive(run)
|
|
83
|
+
});
|
|
84
|
+
const query = makeQueryNamespace(defaultHelpers.run);
|
|
85
|
+
query.batch;
|
|
86
|
+
query.live;
|
|
87
|
+
const form = makeForm(defaultHelpers);
|
|
88
|
+
const command = makeCommand(defaultHelpers.run);
|
|
89
|
+
const prerender = makePrerender(defaultHelpers.run);
|
|
90
|
+
/**
|
|
91
|
+
* Bind the remote-function helpers to a {@link RequestRuntimeProvider} (and an
|
|
92
|
+
* optional boundary error translator), so their handlers run on the provider's
|
|
93
|
+
* services `Env` — your own application catalogue — instead of the fixed
|
|
94
|
+
* {@link SvelteKitServices}. The Standard-Schema / `"unchecked"` validation
|
|
95
|
+
* argument and the native return types (`RemoteQueryFunction`, `RemoteForm`,
|
|
96
|
+
* ...) are exactly as the standalone helpers.
|
|
97
|
+
*
|
|
98
|
+
* For remote forms, the failure path runs `opts.formError` when provided —
|
|
99
|
+
* mapping a structured/nested error shape into SvelteKit's field issues (via the
|
|
100
|
+
* `issue` helper / `invalid()`) so a domain validation failure drives
|
|
101
|
+
* field-level invalidation rather than a generic failure. Otherwise the form
|
|
102
|
+
* falls back to `translateError`.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* const { query, form, command } = RemoteFunction.withRuntime(
|
|
107
|
+
* RequestRuntime.fromLocals((event) => event.locals.runtime),
|
|
108
|
+
* {
|
|
109
|
+
* translateError: toSvelteKit,
|
|
110
|
+
* formError: (error, { issue }) => {
|
|
111
|
+
* if (error instanceof ValidationFailure) issue.field(error.field, error.message);
|
|
112
|
+
* invalid();
|
|
113
|
+
* },
|
|
114
|
+
* },
|
|
115
|
+
* );
|
|
116
|
+
*
|
|
117
|
+
* export const items = query(() =>
|
|
118
|
+
* Effect.gen(function* () {
|
|
119
|
+
* const db = yield* Database;
|
|
120
|
+
* return yield* db.list();
|
|
121
|
+
* }),
|
|
122
|
+
* );
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
function withRuntime(provider, opts) {
|
|
126
|
+
const helpers = helpersFromProvider(provider, opts?.translateError ?? defaultTranslate, opts?.formError);
|
|
127
|
+
return {
|
|
128
|
+
query: makeQueryNamespace(helpers.run),
|
|
129
|
+
form: makeForm(helpers),
|
|
130
|
+
command: makeCommand(helpers.run),
|
|
131
|
+
prerender: makePrerender(helpers.run)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
77
134
|
const RemoteFunction = {
|
|
78
|
-
query
|
|
79
|
-
batch: queryBatch,
|
|
80
|
-
live: queryLive
|
|
81
|
-
}),
|
|
135
|
+
query,
|
|
82
136
|
form,
|
|
83
137
|
command,
|
|
84
138
|
prerender,
|
|
85
|
-
requested
|
|
139
|
+
requested,
|
|
140
|
+
withRuntime
|
|
86
141
|
};
|
|
87
142
|
//#endregion
|
|
88
143
|
export { RemoteFunction };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.server.js","names":[],"sources":["../src/remote.server.ts"],"sourcesContent":["import * as AppServer from \"$app/server\";\nimport type {\n RemoteCommand,\n RemoteForm,\n RemoteFormInput,\n RemotePrerenderFunction,\n RemoteQueryFunction,\n InvalidField,\n} from \"@sveltejs/kit\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport * as HttpClient from \"effect/unstable/http/HttpClient\";\nimport { Effect } from \"effect\";\nimport { SvelteKitRuntime, SvelteKitRequestEvent } from \"./runtime.js\";\n\ntype SvelteKitEffectEnv = HttpClient.HttpClient | SvelteKitRequestEvent;\n\nfunction runRemoteEffect<A, E, R extends SvelteKitEffectEnv>(\n effect: Effect.Effect<A, E, R>,\n): Promise<A> {\n const event = AppServer.getRequestEvent();\n const runtime = SvelteKitRuntime.forRequest(event);\n return runtime.runPromise(effect);\n}\n\nfunction unavailable(name: string): never {\n throw new Error(\n `${name} is not available in the installed SvelteKit version. Update @sveltejs/kit to use this API.`,\n );\n}\n\ntype UnknownEffect = Effect.Effect<unknown, unknown, SvelteKitEffectEnv>;\ntype EffectHandler = (arg: unknown) => UnknownEffect;\ntype EffectFormHandler = (data: unknown, issue: unknown) => UnknownEffect;\ntype EffectVoidHandler = () => UnknownEffect;\n\n/**\n * Detects a Standard Schema validator via its `~standard` marker property.\n *\n * This cannot be a simple `typeof value === \"function\"` check on the handler\n * position: some Standard Schema implementations (such as Effect's\n * `Schema.toStandardSchemaV1`) return callable class values, which would be\n * mistaken for handler functions.\n */\nconst isStandardSchema = (value: unknown): value is StandardSchemaV1 =>\n value !== null &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n \"~standard\" in value;\n\n/** True when the argument is the no-validation handler overload. */\nconst isBareHandler = (value: unknown): value is EffectVoidHandler =>\n typeof value === \"function\" && !isStandardSchema(value);\n\nexport function query<Output>(\n fn: () => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n): RemoteQueryFunction<void, Output>;\nexport function query<Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n): RemoteQueryFunction<Input, Output>;\nexport function query<Schema extends StandardSchemaV1, Output>(\n schema: Schema,\n fn: (\n arg: StandardSchemaV1.InferOutput<Schema>,\n ) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n): RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output>;\nexport function query(\n arg1: unknown,\n arg2?: (arg: unknown) => Effect.Effect<unknown, unknown, SvelteKitEffectEnv>,\n): any {\n if (isBareHandler(arg1)) {\n const handler = arg1;\n return AppServer.query(() => runRemoteEffect(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"query requires a handler function when a schema is provided\");\n }\n\n return AppServer.query(arg1 as \"unchecked\", (arg: unknown) => runRemoteEffect(arg2(arg)));\n}\n\nexport function queryBatch<Input, Output>(\n validate: \"unchecked\",\n fn: (\n args: Input[],\n ) => Effect.Effect<(arg: Input, idx: number) => Output, unknown, SvelteKitEffectEnv>,\n): RemoteQueryFunction<Input, Output>;\nexport function queryBatch<Schema extends StandardSchemaV1, Output>(\n schema: Schema,\n fn: (\n args: StandardSchemaV1.InferOutput<Schema>[],\n ) => Effect.Effect<\n (arg: StandardSchemaV1.InferOutput<Schema>, idx: number) => Output,\n unknown,\n SvelteKitEffectEnv\n >,\n): RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output>;\nexport function queryBatch(\n validate: unknown,\n fn: (\n args: unknown[],\n ) => Effect.Effect<(arg: unknown, idx: number) => unknown, unknown, SvelteKitEffectEnv>,\n): any {\n return AppServer.query.batch(validate as \"unchecked\", (args: unknown[]) =>\n runRemoteEffect(fn(args)),\n );\n}\n\ntype QueryLiveFactory = (\n validate: unknown,\n fn: (arg: unknown) => Promise<AsyncIterable<unknown>> | AsyncIterable<unknown>,\n) => unknown;\n\ntype QueryWithCompat = typeof AppServer.query & {\n live?: QueryLiveFactory;\n};\n\ntype AppServerWithCompat = typeof AppServer & {\n requested?: (...args: unknown[]) => unknown;\n};\n\nconst appServerCompat = AppServer as AppServerWithCompat;\nconst queryCompat = AppServer.query as QueryWithCompat;\n\nfunction getQueryLive(): QueryLiveFactory {\n const live = queryCompat.live;\n\n if (typeof live !== \"function\") {\n unavailable(\"query.live\");\n }\n\n return live;\n}\n\nfunction getRequested(): (...args: unknown[]) => unknown {\n const requestedFn = appServerCompat.requested;\n\n if (typeof requestedFn !== \"function\") {\n unavailable(\"requested\");\n }\n\n return requestedFn;\n}\n\nexport function queryLive<Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<AsyncIterable<Output>, unknown, SvelteKitEffectEnv>,\n): RemoteQueryFunction<Input, Output>;\nexport function queryLive<Schema extends StandardSchemaV1, Output>(\n schema: Schema,\n fn: (\n arg: StandardSchemaV1.InferOutput<Schema>,\n ) => Effect.Effect<AsyncIterable<Output>, unknown, SvelteKitEffectEnv>,\n): RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output>;\nexport function queryLive(\n validate: unknown,\n fn: (arg: unknown) => Effect.Effect<AsyncIterable<unknown>, unknown, SvelteKitEffectEnv>,\n): any {\n const live = getQueryLive();\n return live(validate, (arg) => runRemoteEffect(fn(arg)));\n}\n\nexport function form<Output>(\n fn: () => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n): RemoteForm<void, Output>;\nexport function form<Input extends RemoteFormInput, Output>(\n validate: \"unchecked\",\n fn: (\n data: Input,\n issue: InvalidField<Input>,\n ) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n): RemoteForm<Input, Output>;\nexport function form<Schema extends StandardSchemaV1<RemoteFormInput, Record<string, any>>, Output>(\n validate: Schema,\n fn: (\n data: StandardSchemaV1.InferOutput<Schema>,\n issue: InvalidField<StandardSchemaV1.InferInput<Schema>>,\n ) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n): RemoteForm<StandardSchemaV1.InferInput<Schema>, Output>;\nexport function form(arg1: unknown, arg2?: unknown): any {\n if (isBareHandler(arg1)) {\n const handler = arg1;\n return AppServer.form(() => runRemoteEffect(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"form requires a handler function when a schema is provided\");\n }\n\n return AppServer.form(arg1 as \"unchecked\", (data: unknown, issue: unknown) =>\n runRemoteEffect((arg2 as EffectFormHandler)(data, issue)),\n );\n}\n\nexport function command<Output>(\n fn: () => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n): RemoteCommand<void, Output>;\nexport function command<Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n): RemoteCommand<Input, Output>;\nexport function command<Schema extends StandardSchemaV1, Output>(\n validate: Schema,\n fn: (\n arg: StandardSchemaV1.InferOutput<Schema>,\n ) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n): RemoteCommand<StandardSchemaV1.InferInput<Schema>, Output>;\nexport function command(\n arg1: unknown,\n arg2?: (arg: unknown) => Effect.Effect<unknown, unknown, SvelteKitEffectEnv>,\n): any {\n if (isBareHandler(arg1)) {\n const handler = arg1;\n return AppServer.command(() => runRemoteEffect(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"command requires a handler function when a schema is provided\");\n }\n\n return AppServer.command(arg1 as \"unchecked\", (arg: unknown) => runRemoteEffect(arg2(arg)));\n}\n\nexport function prerender<Output>(\n fn: () => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n options?: {\n inputs?: () => Promise<void[]> | void[];\n dynamic?: boolean;\n },\n): RemotePrerenderFunction<void, Output>;\nexport function prerender<Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n options?: {\n inputs?: () => Promise<Input[]> | Input[];\n dynamic?: boolean;\n },\n): RemotePrerenderFunction<Input, Output>;\nexport function prerender<Schema extends StandardSchemaV1, Output>(\n schema: Schema,\n fn: (\n arg: StandardSchemaV1.InferOutput<Schema>,\n ) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n options?: {\n inputs?: () =>\n | Promise<StandardSchemaV1.InferInput<Schema>[]>\n | StandardSchemaV1.InferInput<Schema>[];\n dynamic?: boolean;\n },\n): RemotePrerenderFunction<StandardSchemaV1.InferInput<Schema>, Output>;\nexport function prerender(\n arg1: unknown,\n arg2?:\n | ((arg: unknown) => Effect.Effect<unknown, unknown, SvelteKitEffectEnv>)\n | {\n inputs?: () => Promise<unknown[]> | unknown[];\n dynamic?: boolean;\n },\n arg3?: {\n inputs?: () => Promise<unknown[]> | unknown[];\n dynamic?: boolean;\n },\n): any {\n if (isBareHandler(arg1)) {\n const handler = arg1;\n return AppServer.prerender(\n () => runRemoteEffect(handler()),\n arg2 as { inputs?: () => Promise<void[]> | void[]; dynamic?: boolean } | undefined,\n );\n }\n\n if (typeof arg2 !== \"function\") {\n throw new Error(\"prerender requires a handler function when a schema is provided\");\n }\n\n return AppServer.prerender(\n arg1 as \"unchecked\",\n (arg: unknown) => runRemoteEffect((arg2 as EffectHandler)(arg)),\n arg3,\n );\n}\n\nexport function requested<Result = unknown>(...args: unknown[]): Result {\n const requestedFn = getRequested();\n return requestedFn(...args) as Result;\n}\n\ntype QueryNamespace = {\n <Output>(\n fn: () => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n ): RemoteQueryFunction<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n ): RemoteQueryFunction<Input, Output>;\n <Schema extends StandardSchemaV1, Output>(\n schema: Schema,\n fn: (\n arg: StandardSchemaV1.InferOutput<Schema>,\n ) => Effect.Effect<Output, unknown, SvelteKitEffectEnv>,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output>;\n batch: typeof queryBatch;\n live: typeof queryLive;\n};\n\nconst queryWithHelpers = Object.assign(query, {\n batch: queryBatch,\n live: queryLive,\n}) as QueryNamespace;\n\nexport const RemoteFunction = {\n query: queryWithHelpers,\n form,\n command,\n prerender,\n requested,\n};\n"],"mappings":";;;;;AAgBA,SAAS,gBACP,QACY;CACZ,MAAM,QAAQ,UAAU,gBAAgB;CAExC,OADgB,iBAAiB,WAAW,KAC/B,EAAE,WAAW,MAAM;AAClC;AAEA,SAAS,YAAY,MAAqB;CACxC,MAAM,IAAI,MACR,GAAG,KAAK,4FACV;AACF;;;;;;;;;AAeA,MAAM,oBAAoB,UACxB,UAAU,SACT,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,eAAe;;AAGjB,MAAM,iBAAiB,UACrB,OAAO,UAAU,cAAc,CAAC,iBAAiB,KAAK;AAexD,SAAgB,MACd,MACA,MACK;CACL,IAAI,cAAc,IAAI,GAAG;EACvB,MAAM,UAAU;EAChB,OAAO,UAAU,YAAY,gBAAgB,QAAQ,CAAC,CAAC;CACzD;CAEA,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,6DAA6D;CAG/E,OAAO,UAAU,MAAM,OAAsB,QAAiB,gBAAgB,KAAK,GAAG,CAAC,CAAC;AAC1F;AAkBA,SAAgB,WACd,UACA,IAGK;CACL,OAAO,UAAU,MAAM,MAAM,WAA0B,SACrD,gBAAgB,GAAG,IAAI,CAAC,CAC1B;AACF;AAeA,MAAM,kBAAkB;AACxB,MAAM,cAAc,UAAU;AAE9B,SAAS,eAAiC;CACxC,MAAM,OAAO,YAAY;CAEzB,IAAI,OAAO,SAAS,YAClB,YAAY,YAAY;CAG1B,OAAO;AACT;AAEA,SAAS,eAAgD;CACvD,MAAM,cAAc,gBAAgB;CAEpC,IAAI,OAAO,gBAAgB,YACzB,YAAY,WAAW;CAGzB,OAAO;AACT;AAYA,SAAgB,UACd,UACA,IACK;CAEL,OADa,aACH,EAAE,WAAW,QAAQ,gBAAgB,GAAG,GAAG,CAAC,CAAC;AACzD;AAmBA,SAAgB,KAAK,MAAe,MAAqB;CACvD,IAAI,cAAc,IAAI,GAAG;EACvB,MAAM,UAAU;EAChB,OAAO,UAAU,WAAW,gBAAgB,QAAQ,CAAC,CAAC;CACxD;CAEA,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,4DAA4D;CAG9E,OAAO,UAAU,KAAK,OAAsB,MAAe,UACzD,gBAAiB,KAA2B,MAAM,KAAK,CAAC,CAC1D;AACF;AAeA,SAAgB,QACd,MACA,MACK;CACL,IAAI,cAAc,IAAI,GAAG;EACvB,MAAM,UAAU;EAChB,OAAO,UAAU,cAAc,gBAAgB,QAAQ,CAAC,CAAC;CAC3D;CAEA,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,+DAA+D;CAGjF,OAAO,UAAU,QAAQ,OAAsB,QAAiB,gBAAgB,KAAK,GAAG,CAAC,CAAC;AAC5F;AA6BA,SAAgB,UACd,MACA,MAMA,MAIK;CACL,IAAI,cAAc,IAAI,GAAG;EACvB,MAAM,UAAU;EAChB,OAAO,UAAU,gBACT,gBAAgB,QAAQ,CAAC,GAC/B,IACF;CACF;CAEA,IAAI,OAAO,SAAS,YAClB,MAAM,IAAI,MAAM,iEAAiE;CAGnF,OAAO,UAAU,UACf,OACC,QAAiB,gBAAiB,KAAuB,GAAG,CAAC,GAC9D,IACF;AACF;AAEA,SAAgB,UAA4B,GAAG,MAAyB;CAEtE,OADoB,aACH,EAAE,GAAG,IAAI;AAC5B;AAyBA,MAAa,iBAAiB;CAC5B,OANuB,OAAO,OAAO,OAAO;EAC5C,OAAO;EACP,MAAM;CACR,CAGS;CACP;CACA;CACA;CACA;AACF"}
|
|
1
|
+
{"version":3,"file":"remote.server.js","names":["isBareHandlerValue"],"sources":["../src/remote.server.ts"],"sourcesContent":["import * as AppServer from \"$app/server\";\nimport type {\n RemoteCommand,\n RemoteForm,\n RemoteFormInput,\n RemotePrerenderFunction,\n RemoteQueryFunction,\n InvalidField,\n} from \"@sveltejs/kit\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport * as HttpClient from \"effect/unstable/http/HttpClient\";\nimport { Effect, type Schema } from \"effect\";\nimport { SvelteKitRuntime, SvelteKitRequestEvent } from \"./runtime.js\";\nimport {\n isBareHandler as isBareHandlerValue,\n toStandardValidator,\n} from \"./internal/remote-validators.js\";\nimport {\n defaultTranslate,\n makeRunHelpers,\n type FormErrorMapper,\n type RunHelpers,\n type Runner,\n} from \"./internal/remote-run.js\";\nimport type { ErrorTranslator, RequestRuntimeProvider } from \"./request-runtime.js\";\n\nexport type { FormErrorMapper } from \"./internal/remote-run.js\";\n\ntype SvelteKitEffectEnv = HttpClient.HttpClient | SvelteKitRequestEvent;\n\n/** The default helpers: a fresh SvelteKit per-request runtime, passthrough translation. */\nconst defaultHelpers: RunHelpers = makeRunHelpers(() => {\n const event = AppServer.getRequestEvent();\n return { runExit: (effect) => SvelteKitRuntime.forRequest(event).runPromiseExit(effect) };\n}, defaultTranslate);\n\n/** Build helpers from a {@link RequestRuntimeProvider}, translator, and form-error mapper. */\nconst helpersFromProvider = (\n provider: RequestRuntimeProvider<any, any>,\n translate: ErrorTranslator,\n formError?: FormErrorMapper,\n): RunHelpers =>\n makeRunHelpers(\n () => {\n const event = AppServer.getRequestEvent();\n return {\n runExit: (effect) => provider.run(event).runPromiseExit(effect),\n dispose: () => provider.disposeAfterResponse?.(event),\n };\n },\n translate,\n formError,\n );\n\nfunction unavailable(name: string): never {\n throw new Error(\n `${name} is not available in the installed SvelteKit version. Update @sveltejs/kit to use this API.`,\n );\n}\n\ntype EffectHandler<Env> = (arg: unknown) => Effect.Effect<unknown, unknown, Env>;\ntype EffectFormHandler<Env> = (\n data: unknown,\n issue: unknown,\n) => Effect.Effect<unknown, unknown, Env>;\ntype EffectVoidHandler<Env> = () => Effect.Effect<unknown, unknown, Env>;\n\n/** True when the argument is the no-validation handler overload (typed narrowing). */\nconst isBareHandler = <Env>(value: unknown): value is EffectVoidHandler<Env> =>\n isBareHandlerValue(value);\n\n/** The public shape of the `query` helper, parameterised by its handler env. */\nexport interface QueryFn<Env> {\n <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteQueryFunction<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, Env>,\n ): RemoteQueryFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (arg: StandardSchemaV1.InferOutput<Sch>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<Output, unknown, Env>,\n ): RemoteQueryFunction<S[\"Encoded\"], Output>;\n}\n\nconst makeQuery = <Env>(run: Runner): QueryFn<Env> =>\n ((arg1: unknown, arg2?: (arg: unknown) => Effect.Effect<unknown, unknown, Env>): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.query(() => run(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"query requires a handler function when a schema is provided\");\n }\n\n return AppServer.query(toStandardValidator(arg1) as \"unchecked\", (arg: unknown) =>\n run(arg2(arg)),\n );\n }) as QueryFn<Env>;\n\nexport interface QueryBatchFn<Env> {\n <Input, Output>(\n validate: \"unchecked\",\n fn: (args: Input[]) => Effect.Effect<(arg: Input, idx: number) => Output, unknown, Env>,\n ): RemoteQueryFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (\n args: StandardSchemaV1.InferOutput<Sch>[],\n ) => Effect.Effect<\n (arg: StandardSchemaV1.InferOutput<Sch>, idx: number) => Output,\n unknown,\n Env\n >,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (args: S[\"Type\"][]) => Effect.Effect<(arg: S[\"Type\"], idx: number) => Output, unknown, Env>,\n ): RemoteQueryFunction<S[\"Encoded\"], Output>;\n}\n\nconst makeQueryBatch = <Env>(run: Runner): QueryBatchFn<Env> =>\n ((\n validate: unknown,\n fn: (args: unknown[]) => Effect.Effect<(arg: unknown, idx: number) => unknown, unknown, Env>,\n ): any =>\n AppServer.query.batch(toStandardValidator(validate) as \"unchecked\", (args: unknown[]) =>\n run(fn(args)),\n )) as QueryBatchFn<Env>;\n\ntype QueryLiveFactory = (\n validate: unknown,\n fn: (arg: unknown) => Promise<AsyncIterable<unknown>> | AsyncIterable<unknown>,\n) => unknown;\n\ntype QueryWithCompat = typeof AppServer.query & {\n live?: QueryLiveFactory;\n};\n\ntype AppServerWithCompat = typeof AppServer & {\n requested?: (...args: unknown[]) => unknown;\n};\n\nconst appServerCompat = AppServer as AppServerWithCompat;\nconst queryCompat = AppServer.query as QueryWithCompat;\n\nfunction getQueryLive(): QueryLiveFactory {\n const live = queryCompat.live;\n\n if (typeof live !== \"function\") {\n unavailable(\"query.live\");\n }\n\n return live;\n}\n\nfunction getRequested(): (...args: unknown[]) => unknown {\n const requestedFn = appServerCompat.requested;\n\n if (typeof requestedFn !== \"function\") {\n unavailable(\"requested\");\n }\n\n return requestedFn;\n}\n\nexport interface QueryLiveFn<Env> {\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<AsyncIterable<Output>, unknown, Env>,\n ): RemoteQueryFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (\n arg: StandardSchemaV1.InferOutput<Sch>,\n ) => Effect.Effect<AsyncIterable<Output>, unknown, Env>,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<AsyncIterable<Output>, unknown, Env>,\n ): RemoteQueryFunction<S[\"Encoded\"], Output>;\n}\n\nconst makeQueryLive = <Env>(run: Runner): QueryLiveFn<Env> =>\n ((\n validate: unknown,\n fn: (arg: unknown) => Effect.Effect<AsyncIterable<unknown>, unknown, Env>,\n ): any => {\n const live = getQueryLive();\n return live(toStandardValidator(validate), (arg) => run(fn(arg)));\n }) as QueryLiveFn<Env>;\n\nexport interface FormFn<Env> {\n <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteForm<void, Output>;\n <Input extends RemoteFormInput, Output>(\n validate: \"unchecked\",\n fn: (data: Input, issue: InvalidField<Input>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteForm<Input, Output>;\n <Sch extends StandardSchemaV1<RemoteFormInput, Record<string, any>>, Output>(\n validate: Sch,\n fn: (\n data: StandardSchemaV1.InferOutput<Sch>,\n issue: InvalidField<StandardSchemaV1.InferInput<Sch>>,\n ) => Effect.Effect<Output, unknown, Env>,\n ): RemoteForm<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top & { readonly Encoded: RemoteFormInput }, Output>(\n validate: S,\n fn: (data: S[\"Type\"], issue: InvalidField<S[\"Encoded\"]>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteForm<S[\"Encoded\"], Output>;\n}\n\nconst makeForm = <Env>(helpers: RunHelpers): FormFn<Env> =>\n ((arg1: unknown, arg2?: unknown): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.form(() => helpers.run(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"form requires a handler function when a schema is provided\");\n }\n\n return AppServer.form(\n toStandardValidator(arg1) as \"unchecked\",\n (data: unknown, issue: unknown) =>\n helpers.runForm((arg2 as EffectFormHandler<Env>)(data, issue), {\n issue: issue as InvalidField<any>,\n data,\n }),\n );\n }) as FormFn<Env>;\n\nexport interface CommandFn<Env> {\n <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteCommand<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, Env>,\n ): RemoteCommand<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n validate: Sch,\n fn: (arg: StandardSchemaV1.InferOutput<Sch>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteCommand<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n validate: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<Output, unknown, Env>,\n ): RemoteCommand<S[\"Encoded\"], Output>;\n}\n\nconst makeCommand = <Env>(run: Runner): CommandFn<Env> =>\n ((arg1: unknown, arg2?: (arg: unknown) => Effect.Effect<unknown, unknown, Env>): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.command(() => run(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"command requires a handler function when a schema is provided\");\n }\n\n return AppServer.command(toStandardValidator(arg1) as \"unchecked\", (arg: unknown) =>\n run(arg2(arg)),\n );\n }) as CommandFn<Env>;\n\nexport interface PrerenderFn<Env> {\n <Output>(\n fn: () => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () => Promise<void[]> | void[];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () => Promise<Input[]> | Input[];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (arg: StandardSchemaV1.InferOutput<Sch>) => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () =>\n | Promise<StandardSchemaV1.InferInput<Sch>[]>\n | StandardSchemaV1.InferInput<Sch>[];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () => Promise<S[\"Encoded\"][]> | S[\"Encoded\"][];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<S[\"Encoded\"], Output>;\n}\n\nconst makePrerender = <Env>(run: Runner): PrerenderFn<Env> =>\n ((\n arg1: unknown,\n arg2?:\n | ((arg: unknown) => Effect.Effect<unknown, unknown, Env>)\n | {\n inputs?: () => Promise<unknown[]> | unknown[];\n dynamic?: boolean;\n },\n arg3?: {\n inputs?: () => Promise<unknown[]> | unknown[];\n dynamic?: boolean;\n },\n ): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.prerender(\n () => run(handler()),\n arg2 as { inputs?: () => Promise<void[]> | void[]; dynamic?: boolean } | undefined,\n );\n }\n\n if (typeof arg2 !== \"function\") {\n throw new Error(\"prerender requires a handler function when a schema is provided\");\n }\n\n return AppServer.prerender(\n toStandardValidator(arg1) as \"unchecked\",\n (arg: unknown) => run((arg2 as EffectHandler<Env>)(arg)),\n arg3,\n );\n }) as PrerenderFn<Env>;\n\nexport function requested<Result = unknown>(...args: unknown[]): Result {\n const requestedFn = getRequested();\n return requestedFn(...args) as Result;\n}\n\n/** A `query` with its `batch` / `live` helpers attached. */\nexport type QueryNamespace<Env> = QueryFn<Env> & {\n batch: QueryBatchFn<Env>;\n live: QueryLiveFn<Env>;\n};\n\nconst makeQueryNamespace = <Env>(run: Runner): QueryNamespace<Env> =>\n Object.assign(makeQuery<Env>(run), {\n batch: makeQueryBatch<Env>(run),\n live: makeQueryLive<Env>(run),\n }) as QueryNamespace<Env>;\n\n// ── Standalone exports (default SvelteKit per-request runtime) ──────────────\n\nexport const query: QueryNamespace<SvelteKitEffectEnv> = makeQueryNamespace(defaultHelpers.run);\nexport const queryBatch: QueryBatchFn<SvelteKitEffectEnv> = query.batch;\nexport const queryLive: QueryLiveFn<SvelteKitEffectEnv> = query.live;\nexport const form: FormFn<SvelteKitEffectEnv> = makeForm(defaultHelpers);\nexport const command: CommandFn<SvelteKitEffectEnv> = makeCommand(defaultHelpers.run);\nexport const prerender: PrerenderFn<SvelteKitEffectEnv> = makePrerender(defaultHelpers.run);\n\n/** The set of remote-function helpers bound to a particular runner. */\nexport interface RemoteFunctions<Env> {\n readonly query: QueryNamespace<Env>;\n readonly form: FormFn<Env>;\n readonly command: CommandFn<Env>;\n readonly prerender: PrerenderFn<Env>;\n}\n\n/**\n * Bind the remote-function helpers to a {@link RequestRuntimeProvider} (and an\n * optional boundary error translator), so their handlers run on the provider's\n * services `Env` — your own application catalogue — instead of the fixed\n * {@link SvelteKitServices}. The Standard-Schema / `\"unchecked\"` validation\n * argument and the native return types (`RemoteQueryFunction`, `RemoteForm`,\n * ...) are exactly as the standalone helpers.\n *\n * For remote forms, the failure path runs `opts.formError` when provided —\n * mapping a structured/nested error shape into SvelteKit's field issues (via the\n * `issue` helper / `invalid()`) so a domain validation failure drives\n * field-level invalidation rather than a generic failure. Otherwise the form\n * falls back to `translateError`.\n *\n * @example\n * ```ts\n * const { query, form, command } = RemoteFunction.withRuntime(\n * RequestRuntime.fromLocals((event) => event.locals.runtime),\n * {\n * translateError: toSvelteKit,\n * formError: (error, { issue }) => {\n * if (error instanceof ValidationFailure) issue.field(error.field, error.message);\n * invalid();\n * },\n * },\n * );\n *\n * export const items = query(() =>\n * Effect.gen(function* () {\n * const db = yield* Database;\n * return yield* db.list();\n * }),\n * );\n * ```\n */\nexport function withRuntime<R>(\n provider: RequestRuntimeProvider<R, any>,\n opts?: { translateError?: ErrorTranslator; formError?: FormErrorMapper },\n): RemoteFunctions<R> {\n const helpers = helpersFromProvider(\n provider,\n opts?.translateError ?? defaultTranslate,\n opts?.formError,\n );\n return {\n query: makeQueryNamespace<R>(helpers.run),\n form: makeForm<R>(helpers),\n command: makeCommand<R>(helpers.run),\n prerender: makePrerender<R>(helpers.run),\n };\n}\n\nexport const RemoteFunction = {\n query,\n form,\n command,\n prerender,\n requested,\n withRuntime,\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Cause, Context, Effect, Exit, Fiber, Layer, ManagedRuntime } from "effect";
|
|
2
|
+
import { Action, Actions, RequestEvent, RequestHandler, ServerLoad } from "@sveltejs/kit";
|
|
3
|
+
|
|
4
|
+
//#region src/request-runtime.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The runners a {@link RequestRuntimeProvider} resolves for a single request:
|
|
7
|
+
* a way to run an `Effect<A, unknown, R>` that requires the provider's services
|
|
8
|
+
* `R`. The error channel is `unknown` because a boundary translator (see
|
|
9
|
+
* {@link makeRuntimeAdapter}) decides how failures surface.
|
|
10
|
+
*/
|
|
11
|
+
interface RequestRuntimeRunners<in R> {
|
|
12
|
+
/** Run the request's effect, keeping its `Exit` (never rejects). */
|
|
13
|
+
readonly runPromiseExit: <A>(effect: Effect.Effect<A, unknown, R>) => Promise<Exit.Exit<A, unknown>>;
|
|
14
|
+
/** Fork the request's effect, returning the running fiber. */
|
|
15
|
+
readonly runFork: <A>(effect: Effect.Effect<A, unknown, R>) => Fiber.Fiber<A, unknown>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* How to execute a request's effects. Resolved per request, at call time, so a
|
|
19
|
+
* runtime the consumer rebuilds mid-request is honoured automatically.
|
|
20
|
+
*
|
|
21
|
+
* Implementations own the scope/lifecycle of whatever they build. `R` is the
|
|
22
|
+
* set of services the handler effect may require; `E` is the layer construction
|
|
23
|
+
* error type (informational — failures surface through the translator).
|
|
24
|
+
*/
|
|
25
|
+
interface RequestRuntimeProvider<R, E = never> {
|
|
26
|
+
/** Resolve the runners for a request. Consulted per call, never cached. */
|
|
27
|
+
readonly run: (event: RequestEvent) => RequestRuntimeRunners<R>;
|
|
28
|
+
/**
|
|
29
|
+
* Optional teardown to run after the response is produced. Called by
|
|
30
|
+
* {@link makeRuntimeAdapter} once the handler has settled — e.g. to route a
|
|
31
|
+
* per-request resource's disposal through `ctx.waitUntil` on Workers.
|
|
32
|
+
*/
|
|
33
|
+
readonly disposeAfterResponse?: (event: RequestEvent) => void;
|
|
34
|
+
/** Phantom marker so `E` participates in inference. */
|
|
35
|
+
readonly _E?: (_: never) => E;
|
|
36
|
+
}
|
|
37
|
+
/** Options for {@link RequestRuntime.fromLayerPerRequest}. */
|
|
38
|
+
interface FromLayerPerRequestOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Defer teardown of the per-request runtime to after the response is
|
|
41
|
+
* produced, instead of awaiting `dispose()` inline. On Workers, route it
|
|
42
|
+
* through `ctx.waitUntil` so disposal does not fight response streaming or
|
|
43
|
+
* socket pinning:
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* { afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()) }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
readonly afterResponse?: (event: RequestEvent, dispose: () => Promise<void>) => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Constructors for {@link RequestRuntimeProvider}. Each expresses one way to
|
|
53
|
+
* obtain a request's execution context; the wrappers don't care which.
|
|
54
|
+
*/
|
|
55
|
+
declare const RequestRuntime: {
|
|
56
|
+
/**
|
|
57
|
+
* Adopt a `ManagedRuntime` the consumer already built (the build-once model,
|
|
58
|
+
* expressed as a provider).
|
|
59
|
+
*/
|
|
60
|
+
fromManagedRuntime: <R, E>(rt: ManagedRuntime.ManagedRuntime<R, E>) => RequestRuntimeProvider<R, E>;
|
|
61
|
+
/**
|
|
62
|
+
* Let the package build a single `ManagedRuntime` from a layer, lazily on
|
|
63
|
+
* first use and shared across requests (the existing build-once-per-process
|
|
64
|
+
* model).
|
|
65
|
+
*/
|
|
66
|
+
fromLayer: <R, E>(layer: Layer.Layer<R, E>) => RequestRuntimeProvider<R, E>;
|
|
67
|
+
/**
|
|
68
|
+
* Adopt a runtime the consumer stashed on the request (e.g. on
|
|
69
|
+
* `event.locals`). Resolved at call time, so a runtime rebuilt mid-request is
|
|
70
|
+
* honoured automatically. The getter may return a `ManagedRuntime` or a
|
|
71
|
+
* built service `Context`.
|
|
72
|
+
*/
|
|
73
|
+
fromLocals: <R>(get: (event: RequestEvent) => ManagedRuntime.ManagedRuntime<R, unknown> | Context.Context<R>) => RequestRuntimeProvider<R>;
|
|
74
|
+
/**
|
|
75
|
+
* Let the package build *and dispose* a `ManagedRuntime` per request from a
|
|
76
|
+
* layer factory. Each handler invocation builds its own runtime from
|
|
77
|
+
* `layer(event)` and disposes it once the effect settles — so a per-request
|
|
78
|
+
* resource (a DB pool) dies with the request. Use `opts.afterResponse` to
|
|
79
|
+
* defer disposal to after the response (Workers `ctx.waitUntil`).
|
|
80
|
+
*
|
|
81
|
+
* For sharing one runtime across every load/action of a request, prefer
|
|
82
|
+
* {@link RequestRuntime.fromLocals} with a runtime the consumer builds in
|
|
83
|
+
* `hooks.server.ts`.
|
|
84
|
+
*/
|
|
85
|
+
fromLayerPerRequest: <R, E>(layer: (event: RequestEvent) => Layer.Layer<R, E>, opts?: FromLayerPerRequestOptions) => RequestRuntimeProvider<R, E>;
|
|
86
|
+
};
|
|
87
|
+
/** A boundary translator: turn a failed `Cause` into a thrown value. */
|
|
88
|
+
type ErrorTranslator = (cause: Cause.Cause<unknown>) => never;
|
|
89
|
+
/** Options shared by {@link makeRuntimeAdapter}. */
|
|
90
|
+
interface RuntimeAdapterOptions {
|
|
91
|
+
/**
|
|
92
|
+
* Translate a failed `Cause` into SvelteKit control flow at the boundary.
|
|
93
|
+
* Called for `serverLoad` / `action` / `actions` failures; it may throw
|
|
94
|
+
* `redirect()` / `error()` (and, for remote forms, call `invalid()`).
|
|
95
|
+
* Defaults to the package's passthrough: SvelteKit's own `redirect()` /
|
|
96
|
+
* `error()` are rethrown, anything else becomes an `EffectCauseError` (a 500).
|
|
97
|
+
*/
|
|
98
|
+
readonly translateError?: ErrorTranslator;
|
|
99
|
+
}
|
|
100
|
+
/** Options for the adapter's `endpoint` wrapper. */
|
|
101
|
+
interface AdapterEndpointOptions {
|
|
102
|
+
/**
|
|
103
|
+
* Include full error details (`Cause.pretty`) in the default 500 JSON body.
|
|
104
|
+
* Ignored when a `translateError` is configured. Defaults to `false`.
|
|
105
|
+
*/
|
|
106
|
+
readonly exposeErrors?: boolean;
|
|
107
|
+
}
|
|
108
|
+
/** The wrappers produced by {@link makeRuntimeAdapter}, bound to a provider. */
|
|
109
|
+
interface RuntimeAdapter<R> {
|
|
110
|
+
readonly serverLoad: <Params extends Record<string, string> = any, ParentData extends Record<string, any> = any, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>) => ServerLoad<Params, ParentData, OutputData, RouteId>;
|
|
111
|
+
readonly action: <Params extends Record<string, string> = any, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>) => Action<Params, OutputData, RouteId>;
|
|
112
|
+
readonly actions: <Params extends Record<string, string> = any, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(map: Record<string, (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>>) => Actions<Params, OutputData, RouteId>;
|
|
113
|
+
readonly endpoint: <Params extends Record<string, string> = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, R>, options?: AdapterEndpointOptions) => RequestHandler<Params, RouteId>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Bind the SvelteKit server wrappers to a {@link RequestRuntimeProvider} (and an
|
|
117
|
+
* optional boundary error translator).
|
|
118
|
+
*
|
|
119
|
+
* Unlike the standalone `serverLoad` / `action` / `endpoint`, the handler
|
|
120
|
+
* effect's required services `R` come from the provider — your own application
|
|
121
|
+
* catalogue — not the fixed {@link SvelteKitServices}. The package does not
|
|
122
|
+
* merge `HttpClient` / `SvelteKitRequestEvent` onto this path; add them to your
|
|
123
|
+
* own layer if you want them.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* // Package owns the runtime per request on Workers, disposed via waitUntil.
|
|
128
|
+
* const { serverLoad } = makeRuntimeAdapter(
|
|
129
|
+
* RequestRuntime.fromLayerPerRequest((event) => AppLayer(event.platform!.env), {
|
|
130
|
+
* afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()),
|
|
131
|
+
* }),
|
|
132
|
+
* );
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
declare const makeRuntimeAdapter: <R>(provider: RequestRuntimeProvider<R, any>, opts?: RuntimeAdapterOptions) => RuntimeAdapter<R>;
|
|
136
|
+
//#endregion
|
|
137
|
+
export { AdapterEndpointOptions, ErrorTranslator, FromLayerPerRequestOptions, RequestRuntime, RequestRuntimeProvider, RequestRuntimeRunners, RuntimeAdapter, RuntimeAdapterOptions, makeRuntimeAdapter };
|
|
138
|
+
//# sourceMappingURL=request-runtime.d.ts.map
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { causeToThrowable } from "./internal/error.js";
|
|
2
|
+
import { Cause, Effect, Exit, ManagedRuntime } from "effect";
|
|
3
|
+
import { isHttpError, isRedirect } from "@sveltejs/kit";
|
|
4
|
+
//#region src/request-runtime.ts
|
|
5
|
+
/**
|
|
6
|
+
* A general, Effect-idiomatic way to provide the per-request execution context
|
|
7
|
+
* for SvelteKit server wrappers.
|
|
8
|
+
*
|
|
9
|
+
* Where {@link makeServerRuntime} bakes in "the package owns a runtime, built
|
|
10
|
+
* once per process," this module factors out the single decision *how to obtain
|
|
11
|
+
* the execution context for this request* behind one open abstraction — a
|
|
12
|
+
* {@link RequestRuntimeProvider}. Existing models (a build-once
|
|
13
|
+
* `ManagedRuntime`, a layer built once), a consumer-owned runtime resolved from
|
|
14
|
+
* `event.locals`, and a package-built per-request runtime are all just
|
|
15
|
+
* {@link RequestRuntime} constructors of the same provider, not separate code
|
|
16
|
+
* paths.
|
|
17
|
+
*
|
|
18
|
+
* {@link makeRuntimeAdapter} binds the `serverLoad` / `action` / `actions` /
|
|
19
|
+
* `endpoint` wrappers to a provider (and an optional boundary error
|
|
20
|
+
* translator), so the handler effect's requirement channel `R` is the
|
|
21
|
+
* *provider's* services — your own application catalogue — rather than the fixed
|
|
22
|
+
* {@link SvelteKitServices}.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // The consumer owns a per-request ManagedRuntime on event.locals.runtime
|
|
27
|
+
* // (rebuilt mid-request); the package adopts it, resolved at call time.
|
|
28
|
+
* import { makeRuntimeAdapter, RequestRuntime } from "@thomasfosterau/effect-sveltekit/server";
|
|
29
|
+
*
|
|
30
|
+
* const { serverLoad, action, actions, endpoint } = makeRuntimeAdapter(
|
|
31
|
+
* RequestRuntime.fromLocals((event) => event.locals.runtime),
|
|
32
|
+
* { translateError: toSvelteKit },
|
|
33
|
+
* );
|
|
34
|
+
*
|
|
35
|
+
* export const load = serverLoad((event) =>
|
|
36
|
+
* Effect.gen(function* () {
|
|
37
|
+
* const db = yield* Database; // app service from locals.runtime
|
|
38
|
+
* return { items: yield* db.list() };
|
|
39
|
+
* }),
|
|
40
|
+
* );
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
/** Runners backed by an existing `ManagedRuntime`. */
|
|
44
|
+
const runnersFromManagedRuntime = (rt) => ({
|
|
45
|
+
runPromiseExit: (effect) => rt.runPromiseExit(effect),
|
|
46
|
+
runFork: (effect) => rt.runFork(effect)
|
|
47
|
+
});
|
|
48
|
+
/** Runners backed by a built service `Context` (run on the default scheduler). */
|
|
49
|
+
const runnersFromContext = (context) => ({
|
|
50
|
+
runPromiseExit: (effect) => Effect.runPromiseExitWith(context)(effect),
|
|
51
|
+
runFork: (effect) => Effect.runForkWith(context)(effect)
|
|
52
|
+
});
|
|
53
|
+
/** Runners for a target that is either a `ManagedRuntime` or a `Context`. */
|
|
54
|
+
const runnersFor = (target) => ManagedRuntime.isManagedRuntime(target) ? runnersFromManagedRuntime(target) : runnersFromContext(target);
|
|
55
|
+
/**
|
|
56
|
+
* Constructors for {@link RequestRuntimeProvider}. Each expresses one way to
|
|
57
|
+
* obtain a request's execution context; the wrappers don't care which.
|
|
58
|
+
*/
|
|
59
|
+
const RequestRuntime = {
|
|
60
|
+
/**
|
|
61
|
+
* Adopt a `ManagedRuntime` the consumer already built (the build-once model,
|
|
62
|
+
* expressed as a provider).
|
|
63
|
+
*/
|
|
64
|
+
fromManagedRuntime: (rt) => {
|
|
65
|
+
const runners = runnersFromManagedRuntime(rt);
|
|
66
|
+
return { run: () => runners };
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* Let the package build a single `ManagedRuntime` from a layer, lazily on
|
|
70
|
+
* first use and shared across requests (the existing build-once-per-process
|
|
71
|
+
* model).
|
|
72
|
+
*/
|
|
73
|
+
fromLayer: (layer) => {
|
|
74
|
+
let runners;
|
|
75
|
+
return { run: () => runners ??= runnersFromManagedRuntime(ManagedRuntime.make(layer)) };
|
|
76
|
+
},
|
|
77
|
+
/**
|
|
78
|
+
* Adopt a runtime the consumer stashed on the request (e.g. on
|
|
79
|
+
* `event.locals`). Resolved at call time, so a runtime rebuilt mid-request is
|
|
80
|
+
* honoured automatically. The getter may return a `ManagedRuntime` or a
|
|
81
|
+
* built service `Context`.
|
|
82
|
+
*/
|
|
83
|
+
fromLocals: (get) => ({ run: (event) => runnersFor(get(event)) }),
|
|
84
|
+
/**
|
|
85
|
+
* Let the package build *and dispose* a `ManagedRuntime` per request from a
|
|
86
|
+
* layer factory. Each handler invocation builds its own runtime from
|
|
87
|
+
* `layer(event)` and disposes it once the effect settles — so a per-request
|
|
88
|
+
* resource (a DB pool) dies with the request. Use `opts.afterResponse` to
|
|
89
|
+
* defer disposal to after the response (Workers `ctx.waitUntil`).
|
|
90
|
+
*
|
|
91
|
+
* For sharing one runtime across every load/action of a request, prefer
|
|
92
|
+
* {@link RequestRuntime.fromLocals} with a runtime the consumer builds in
|
|
93
|
+
* `hooks.server.ts`.
|
|
94
|
+
*/
|
|
95
|
+
fromLayerPerRequest: (layer, opts) => ({ run: (event) => {
|
|
96
|
+
const dispose = (rt) => () => rt.dispose();
|
|
97
|
+
const teardown = (rt) => {
|
|
98
|
+
if (opts?.afterResponse) {
|
|
99
|
+
opts.afterResponse(event, dispose(rt));
|
|
100
|
+
return Promise.resolve();
|
|
101
|
+
}
|
|
102
|
+
return rt.dispose();
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
runPromiseExit: async (effect) => {
|
|
106
|
+
const rt = ManagedRuntime.make(layer(event));
|
|
107
|
+
try {
|
|
108
|
+
return await rt.runPromiseExit(effect);
|
|
109
|
+
} finally {
|
|
110
|
+
await teardown(rt);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
runFork: (effect) => {
|
|
114
|
+
const rt = ManagedRuntime.make(layer(event));
|
|
115
|
+
const fiber = rt.runFork(effect);
|
|
116
|
+
fiber.addObserver(() => void teardown(rt));
|
|
117
|
+
return fiber;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
} })
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Bind the SvelteKit server wrappers to a {@link RequestRuntimeProvider} (and an
|
|
124
|
+
* optional boundary error translator).
|
|
125
|
+
*
|
|
126
|
+
* Unlike the standalone `serverLoad` / `action` / `endpoint`, the handler
|
|
127
|
+
* effect's required services `R` come from the provider — your own application
|
|
128
|
+
* catalogue — not the fixed {@link SvelteKitServices}. The package does not
|
|
129
|
+
* merge `HttpClient` / `SvelteKitRequestEvent` onto this path; add them to your
|
|
130
|
+
* own layer if you want them.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* // Package owns the runtime per request on Workers, disposed via waitUntil.
|
|
135
|
+
* const { serverLoad } = makeRuntimeAdapter(
|
|
136
|
+
* RequestRuntime.fromLayerPerRequest((event) => AppLayer(event.platform!.env), {
|
|
137
|
+
* afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()),
|
|
138
|
+
* }),
|
|
139
|
+
* );
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
const makeRuntimeAdapter = (provider, opts) => {
|
|
143
|
+
const translateError = opts?.translateError ?? ((cause) => {
|
|
144
|
+
throw causeToThrowable(cause);
|
|
145
|
+
});
|
|
146
|
+
const runSettle = async (event, effect) => {
|
|
147
|
+
const exit = await provider.run(event).runPromiseExit(effect);
|
|
148
|
+
try {
|
|
149
|
+
if (Exit.isSuccess(exit)) return exit.value;
|
|
150
|
+
return translateError(exit.cause);
|
|
151
|
+
} finally {
|
|
152
|
+
provider.disposeAfterResponse?.(event);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const action = (fn) => (event) => runSettle(event, fn(event));
|
|
156
|
+
return {
|
|
157
|
+
serverLoad: (fn) => (event) => runSettle(event, fn(event)),
|
|
158
|
+
action,
|
|
159
|
+
actions: (map) => {
|
|
160
|
+
const wrapped = {};
|
|
161
|
+
for (const [key, fn] of Object.entries(map)) wrapped[key] = action(fn);
|
|
162
|
+
return wrapped;
|
|
163
|
+
},
|
|
164
|
+
endpoint: (fn, options) => async (event) => {
|
|
165
|
+
const exit = await provider.run(event).runPromiseExit(fn(event));
|
|
166
|
+
try {
|
|
167
|
+
if (Exit.isSuccess(exit)) return exit.value;
|
|
168
|
+
if (opts?.translateError) return opts.translateError(exit.cause);
|
|
169
|
+
const squashed = Cause.squash(exit.cause);
|
|
170
|
+
if (isRedirect(squashed) || isHttpError(squashed)) throw squashed;
|
|
171
|
+
console.error("Endpoint error:", Cause.pretty(exit.cause));
|
|
172
|
+
const message = options?.exposeErrors ? Cause.pretty(exit.cause) : "Internal server error";
|
|
173
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
174
|
+
status: 500,
|
|
175
|
+
headers: { "Content-Type": "application/json" }
|
|
176
|
+
});
|
|
177
|
+
} finally {
|
|
178
|
+
provider.disposeAfterResponse?.(event);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
//#endregion
|
|
184
|
+
export { RequestRuntime, makeRuntimeAdapter };
|
|
185
|
+
|
|
186
|
+
//# sourceMappingURL=request-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-runtime.js","names":[],"sources":["../src/request-runtime.ts"],"sourcesContent":["/**\n * A general, Effect-idiomatic way to provide the per-request execution context\n * for SvelteKit server wrappers.\n *\n * Where {@link makeServerRuntime} bakes in \"the package owns a runtime, built\n * once per process,\" this module factors out the single decision *how to obtain\n * the execution context for this request* behind one open abstraction — a\n * {@link RequestRuntimeProvider}. Existing models (a build-once\n * `ManagedRuntime`, a layer built once), a consumer-owned runtime resolved from\n * `event.locals`, and a package-built per-request runtime are all just\n * {@link RequestRuntime} constructors of the same provider, not separate code\n * paths.\n *\n * {@link makeRuntimeAdapter} binds the `serverLoad` / `action` / `actions` /\n * `endpoint` wrappers to a provider (and an optional boundary error\n * translator), so the handler effect's requirement channel `R` is the\n * *provider's* services — your own application catalogue — rather than the fixed\n * {@link SvelteKitServices}.\n *\n * @example\n * ```ts\n * // The consumer owns a per-request ManagedRuntime on event.locals.runtime\n * // (rebuilt mid-request); the package adopts it, resolved at call time.\n * import { makeRuntimeAdapter, RequestRuntime } from \"@thomasfosterau/effect-sveltekit/server\";\n *\n * const { serverLoad, action, actions, endpoint } = makeRuntimeAdapter(\n * RequestRuntime.fromLocals((event) => event.locals.runtime),\n * { translateError: toSvelteKit },\n * );\n *\n * export const load = serverLoad((event) =>\n * Effect.gen(function* () {\n * const db = yield* Database; // app service from locals.runtime\n * return { items: yield* db.list() };\n * }),\n * );\n * ```\n */\n\nimport {\n isHttpError,\n isRedirect,\n type Action,\n type Actions,\n type RequestEvent,\n type RequestHandler,\n type ServerLoad,\n} from \"@sveltejs/kit\";\nimport { Cause, Context, Effect, Exit, type Fiber, Layer, ManagedRuntime } from \"effect\";\nimport { causeToThrowable } from \"./internal/error.js\";\n\n/**\n * The runners a {@link RequestRuntimeProvider} resolves for a single request:\n * a way to run an `Effect<A, unknown, R>` that requires the provider's services\n * `R`. The error channel is `unknown` because a boundary translator (see\n * {@link makeRuntimeAdapter}) decides how failures surface.\n */\nexport interface RequestRuntimeRunners<in R> {\n /** Run the request's effect, keeping its `Exit` (never rejects). */\n readonly runPromiseExit: <A>(\n effect: Effect.Effect<A, unknown, R>,\n ) => Promise<Exit.Exit<A, unknown>>;\n /** Fork the request's effect, returning the running fiber. */\n readonly runFork: <A>(effect: Effect.Effect<A, unknown, R>) => Fiber.Fiber<A, unknown>;\n}\n\n/**\n * How to execute a request's effects. Resolved per request, at call time, so a\n * runtime the consumer rebuilds mid-request is honoured automatically.\n *\n * Implementations own the scope/lifecycle of whatever they build. `R` is the\n * set of services the handler effect may require; `E` is the layer construction\n * error type (informational — failures surface through the translator).\n */\nexport interface RequestRuntimeProvider<R, E = never> {\n /** Resolve the runners for a request. Consulted per call, never cached. */\n readonly run: (event: RequestEvent) => RequestRuntimeRunners<R>;\n /**\n * Optional teardown to run after the response is produced. Called by\n * {@link makeRuntimeAdapter} once the handler has settled — e.g. to route a\n * per-request resource's disposal through `ctx.waitUntil` on Workers.\n */\n readonly disposeAfterResponse?: (event: RequestEvent) => void;\n /** Phantom marker so `E` participates in inference. */\n readonly _E?: (_: never) => E;\n}\n\n/** Runners backed by an existing `ManagedRuntime`. */\nconst runnersFromManagedRuntime = <R>(\n rt: ManagedRuntime.ManagedRuntime<R, unknown>,\n): RequestRuntimeRunners<R> => ({\n runPromiseExit: (effect) => rt.runPromiseExit(effect) as Promise<Exit.Exit<never, unknown>>,\n runFork: (effect) => rt.runFork(effect) as Fiber.Fiber<never, unknown>,\n});\n\n/** Runners backed by a built service `Context` (run on the default scheduler). */\nconst runnersFromContext = <R>(context: Context.Context<R>): RequestRuntimeRunners<R> => ({\n runPromiseExit: (effect) => Effect.runPromiseExitWith(context)(effect),\n runFork: (effect) => Effect.runForkWith(context)(effect),\n});\n\n/** Runners for a target that is either a `ManagedRuntime` or a `Context`. */\nconst runnersFor = <R>(\n target: ManagedRuntime.ManagedRuntime<R, unknown> | Context.Context<R>,\n): RequestRuntimeRunners<R> =>\n ManagedRuntime.isManagedRuntime(target)\n ? runnersFromManagedRuntime(target as ManagedRuntime.ManagedRuntime<R, unknown>)\n : runnersFromContext(target as Context.Context<R>);\n\n/** Options for {@link RequestRuntime.fromLayerPerRequest}. */\nexport interface FromLayerPerRequestOptions {\n /**\n * Defer teardown of the per-request runtime to after the response is\n * produced, instead of awaiting `dispose()` inline. On Workers, route it\n * through `ctx.waitUntil` so disposal does not fight response streaming or\n * socket pinning:\n *\n * ```ts\n * { afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()) }\n * ```\n */\n readonly afterResponse?: (event: RequestEvent, dispose: () => Promise<void>) => void;\n}\n\n/**\n * Constructors for {@link RequestRuntimeProvider}. Each expresses one way to\n * obtain a request's execution context; the wrappers don't care which.\n */\nexport const RequestRuntime = {\n /**\n * Adopt a `ManagedRuntime` the consumer already built (the build-once model,\n * expressed as a provider).\n */\n fromManagedRuntime: <R, E>(\n rt: ManagedRuntime.ManagedRuntime<R, E>,\n ): RequestRuntimeProvider<R, E> => {\n const runners = runnersFromManagedRuntime(rt as ManagedRuntime.ManagedRuntime<R, unknown>);\n return { run: () => runners };\n },\n\n /**\n * Let the package build a single `ManagedRuntime` from a layer, lazily on\n * first use and shared across requests (the existing build-once-per-process\n * model).\n */\n fromLayer: <R, E>(layer: Layer.Layer<R, E>): RequestRuntimeProvider<R, E> => {\n let runners: RequestRuntimeRunners<R> | undefined;\n return {\n run: () =>\n (runners ??= runnersFromManagedRuntime(\n ManagedRuntime.make(layer) as ManagedRuntime.ManagedRuntime<R, unknown>,\n )),\n };\n },\n\n /**\n * Adopt a runtime the consumer stashed on the request (e.g. on\n * `event.locals`). Resolved at call time, so a runtime rebuilt mid-request is\n * honoured automatically. The getter may return a `ManagedRuntime` or a\n * built service `Context`.\n */\n fromLocals: <R>(\n get: (event: RequestEvent) => ManagedRuntime.ManagedRuntime<R, unknown> | Context.Context<R>,\n ): RequestRuntimeProvider<R> => ({\n run: (event) => runnersFor(get(event)),\n }),\n\n /**\n * Let the package build *and dispose* a `ManagedRuntime` per request from a\n * layer factory. Each handler invocation builds its own runtime from\n * `layer(event)` and disposes it once the effect settles — so a per-request\n * resource (a DB pool) dies with the request. Use `opts.afterResponse` to\n * defer disposal to after the response (Workers `ctx.waitUntil`).\n *\n * For sharing one runtime across every load/action of a request, prefer\n * {@link RequestRuntime.fromLocals} with a runtime the consumer builds in\n * `hooks.server.ts`.\n */\n fromLayerPerRequest: <R, E>(\n layer: (event: RequestEvent) => Layer.Layer<R, E>,\n opts?: FromLayerPerRequestOptions,\n ): RequestRuntimeProvider<R, E> => ({\n run: (event) => {\n const dispose = (rt: ManagedRuntime.ManagedRuntime<R, E>) => () => rt.dispose();\n const teardown = (rt: ManagedRuntime.ManagedRuntime<R, E>): Promise<void> => {\n if (opts?.afterResponse) {\n opts.afterResponse(event, dispose(rt));\n return Promise.resolve();\n }\n return rt.dispose();\n };\n return {\n runPromiseExit: async (effect) => {\n const rt = ManagedRuntime.make(layer(event));\n try {\n return (await rt.runPromiseExit(effect)) as Exit.Exit<never, unknown>;\n } finally {\n await teardown(rt);\n }\n },\n runFork: (effect) => {\n const rt = ManagedRuntime.make(layer(event));\n const fiber = rt.runFork(effect) as Fiber.Fiber<never, unknown>;\n fiber.addObserver(() => void teardown(rt));\n return fiber;\n },\n };\n },\n }),\n};\n\n/** A boundary translator: turn a failed `Cause` into a thrown value. */\nexport type ErrorTranslator = (cause: Cause.Cause<unknown>) => never;\n\n/** Options shared by {@link makeRuntimeAdapter}. */\nexport interface RuntimeAdapterOptions {\n /**\n * Translate a failed `Cause` into SvelteKit control flow at the boundary.\n * Called for `serverLoad` / `action` / `actions` failures; it may throw\n * `redirect()` / `error()` (and, for remote forms, call `invalid()`).\n * Defaults to the package's passthrough: SvelteKit's own `redirect()` /\n * `error()` are rethrown, anything else becomes an `EffectCauseError` (a 500).\n */\n readonly translateError?: ErrorTranslator;\n}\n\n/** Options for the adapter's `endpoint` wrapper. */\nexport interface AdapterEndpointOptions {\n /**\n * Include full error details (`Cause.pretty`) in the default 500 JSON body.\n * Ignored when a `translateError` is configured. Defaults to `false`.\n */\n readonly exposeErrors?: boolean;\n}\n\n/** The wrappers produced by {@link makeRuntimeAdapter}, bound to a provider. */\nexport interface RuntimeAdapter<R> {\n readonly serverLoad: <\n Params extends Record<string, string> = any,\n ParentData extends Record<string, any> = any,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n >(\n fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>,\n ) => ServerLoad<Params, ParentData, OutputData, RouteId>;\n readonly action: <\n Params extends Record<string, string> = any,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n >(\n fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>,\n ) => Action<Params, OutputData, RouteId>;\n readonly actions: <\n Params extends Record<string, string> = any,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n >(\n map: Record<\n string,\n (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>\n >,\n ) => Actions<Params, OutputData, RouteId>;\n readonly endpoint: <\n Params extends Record<string, string> = any,\n RouteId extends string | null = any,\n >(\n fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, R>,\n options?: AdapterEndpointOptions,\n ) => RequestHandler<Params, RouteId>;\n}\n\n/**\n * Bind the SvelteKit server wrappers to a {@link RequestRuntimeProvider} (and an\n * optional boundary error translator).\n *\n * Unlike the standalone `serverLoad` / `action` / `endpoint`, the handler\n * effect's required services `R` come from the provider — your own application\n * catalogue — not the fixed {@link SvelteKitServices}. The package does not\n * merge `HttpClient` / `SvelteKitRequestEvent` onto this path; add them to your\n * own layer if you want them.\n *\n * @example\n * ```ts\n * // Package owns the runtime per request on Workers, disposed via waitUntil.\n * const { serverLoad } = makeRuntimeAdapter(\n * RequestRuntime.fromLayerPerRequest((event) => AppLayer(event.platform!.env), {\n * afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()),\n * }),\n * );\n * ```\n */\nexport const makeRuntimeAdapter = <R>(\n provider: RequestRuntimeProvider<R, any>,\n opts?: RuntimeAdapterOptions,\n): RuntimeAdapter<R> => {\n const translateError: ErrorTranslator =\n opts?.translateError ??\n ((cause) => {\n throw causeToThrowable(cause);\n });\n\n const runSettle = async <A>(\n event: RequestEvent,\n effect: Effect.Effect<A, unknown, R>,\n ): Promise<A> => {\n const exit = await provider.run(event).runPromiseExit(effect);\n try {\n if (Exit.isSuccess(exit)) return exit.value;\n return translateError(exit.cause);\n } finally {\n provider.disposeAfterResponse?.(event);\n }\n };\n\n const action: RuntimeAdapter<R>[\"action\"] = (fn) => (event) => runSettle(event, fn(event));\n\n return {\n serverLoad: (fn) => (event) => runSettle(event, fn(event)),\n action,\n actions: (map) => {\n const wrapped: Record<string, Action<any, any, any>> = {};\n for (const [key, fn] of Object.entries(map)) wrapped[key] = action(fn);\n return wrapped as never;\n },\n endpoint: (fn, options) => async (event) => {\n const exit = await provider.run(event as RequestEvent).runPromiseExit(fn(event));\n try {\n if (Exit.isSuccess(exit)) return exit.value;\n // A configured translator owns the whole failure path (control flow,\n // structured error bodies, ...).\n if (opts?.translateError) return opts.translateError(exit.cause);\n // Default: pass SvelteKit control flow through, otherwise a 500.\n const squashed = Cause.squash(exit.cause);\n if (isRedirect(squashed) || isHttpError(squashed)) throw squashed;\n console.error(\"Endpoint error:\", Cause.pretty(exit.cause));\n const message = options?.exposeErrors ? Cause.pretty(exit.cause) : \"Internal server error\";\n return new Response(JSON.stringify({ error: message }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n } finally {\n provider.disposeAfterResponse?.(event as RequestEvent);\n }\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,MAAM,6BACJ,QAC8B;CAC9B,iBAAiB,WAAW,GAAG,eAAe,MAAM;CACpD,UAAU,WAAW,GAAG,QAAQ,MAAM;AACxC;;AAGA,MAAM,sBAAyB,aAA2D;CACxF,iBAAiB,WAAW,OAAO,mBAAmB,OAAO,EAAE,MAAM;CACrE,UAAU,WAAW,OAAO,YAAY,OAAO,EAAE,MAAM;AACzD;;AAGA,MAAM,cACJ,WAEA,eAAe,iBAAiB,MAAM,IAClC,0BAA0B,MAAmD,IAC7E,mBAAmB,MAA4B;;;;;AAqBrD,MAAa,iBAAiB;;;;;CAK5B,qBACE,OACiC;EACjC,MAAM,UAAU,0BAA0B,EAA+C;EACzF,OAAO,EAAE,WAAW,QAAQ;CAC9B;;;;;;CAOA,YAAkB,UAA2D;EAC3E,IAAI;EACJ,OAAO,EACL,WACG,YAAY,0BACX,eAAe,KAAK,KAAK,CAC3B,EACJ;CACF;;;;;;;CAQA,aACE,SAC+B,EAC/B,MAAM,UAAU,WAAW,IAAI,KAAK,CAAC,EACvC;;;;;;;;;;;;CAaA,sBACE,OACA,UACkC,EAClC,MAAM,UAAU;EACd,MAAM,WAAW,aAAkD,GAAG,QAAQ;EAC9E,MAAM,YAAY,OAA2D;GAC3E,IAAI,MAAM,eAAe;IACvB,KAAK,cAAc,OAAO,QAAQ,EAAE,CAAC;IACrC,OAAO,QAAQ,QAAQ;GACzB;GACA,OAAO,GAAG,QAAQ;EACpB;EACA,OAAO;GACL,gBAAgB,OAAO,WAAW;IAChC,MAAM,KAAK,eAAe,KAAK,MAAM,KAAK,CAAC;IAC3C,IAAI;KACF,OAAQ,MAAM,GAAG,eAAe,MAAM;IACxC,UAAU;KACR,MAAM,SAAS,EAAE;IACnB;GACF;GACA,UAAU,WAAW;IACnB,MAAM,KAAK,eAAe,KAAK,MAAM,KAAK,CAAC;IAC3C,MAAM,QAAQ,GAAG,QAAQ,MAAM;IAC/B,MAAM,kBAAkB,KAAK,SAAS,EAAE,CAAC;IACzC,OAAO;GACT;EACF;CACF,EACF;AACF;;;;;;;;;;;;;;;;;;;;;AAkFA,MAAa,sBACX,UACA,SACsB;CACtB,MAAM,iBACJ,MAAM,oBACJ,UAAU;EACV,MAAM,iBAAiB,KAAK;CAC9B;CAEF,MAAM,YAAY,OAChB,OACA,WACe;EACf,MAAM,OAAO,MAAM,SAAS,IAAI,KAAK,EAAE,eAAe,MAAM;EAC5D,IAAI;GACF,IAAI,KAAK,UAAU,IAAI,GAAG,OAAO,KAAK;GACtC,OAAO,eAAe,KAAK,KAAK;EAClC,UAAU;GACR,SAAS,uBAAuB,KAAK;EACvC;CACF;CAEA,MAAM,UAAuC,QAAQ,UAAU,UAAU,OAAO,GAAG,KAAK,CAAC;CAEzF,OAAO;EACL,aAAa,QAAQ,UAAU,UAAU,OAAO,GAAG,KAAK,CAAC;EACzD;EACA,UAAU,QAAQ;GAChB,MAAM,UAAiD,CAAC;GACxD,KAAK,MAAM,CAAC,KAAK,OAAO,OAAO,QAAQ,GAAG,GAAG,QAAQ,OAAO,OAAO,EAAE;GACrE,OAAO;EACT;EACA,WAAW,IAAI,YAAY,OAAO,UAAU;GAC1C,MAAM,OAAO,MAAM,SAAS,IAAI,KAAqB,EAAE,eAAe,GAAG,KAAK,CAAC;GAC/E,IAAI;IACF,IAAI,KAAK,UAAU,IAAI,GAAG,OAAO,KAAK;IAGtC,IAAI,MAAM,gBAAgB,OAAO,KAAK,eAAe,KAAK,KAAK;IAE/D,MAAM,WAAW,MAAM,OAAO,KAAK,KAAK;IACxC,IAAI,WAAW,QAAQ,KAAK,YAAY,QAAQ,GAAG,MAAM;IACzD,QAAQ,MAAM,mBAAmB,MAAM,OAAO,KAAK,KAAK,CAAC;IACzD,MAAM,UAAU,SAAS,eAAe,MAAM,OAAO,KAAK,KAAK,IAAI;IACnE,OAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC,GAAG;KACtD,QAAQ;KACR,SAAS,EAAE,gBAAgB,mBAAmB;IAChD,CAAC;GACH,UAAU;IACR,SAAS,uBAAuB,KAAqB;GACvD;EACF;CACF;AACF"}
|