@sourceregistry/sveltekit-enhance 1.1.1 → 1.2.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 CHANGED
@@ -1,112 +1,362 @@
1
+ <div align="center">
2
+
1
3
  # @sourceregistry/sveltekit-enhance
2
4
 
3
- [![npm version](https://img.shields.io/npm/v/@sourceregistry/sveltekit-enhance?logo=npm)](https://www.npmjs.com/package/@sourceregistry/sveltekit-enhance)
4
- [![License](https://img.shields.io/npm/l/@sourceregistry/sveltekit-enhance)](https://github.com/SourceRegistry/sveltekit-enhance/blob/main/LICENSE)
5
- [![CI](https://github.com/SourceRegistry/sveltekit-enhance/actions/workflows/ci.yml/badge.svg)](https://github.com/SourceRegistry/sveltekit-enhance/actions/workflows/ci.yml)
5
+ **Composable middleware, guards, and form utilities for SvelteKit**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@sourceregistry/sveltekit-enhance?style=flat-square&color=f96743)](https://www.npmjs.com/package/@sourceregistry/sveltekit-enhance)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@sourceregistry/sveltekit-enhance?style=flat-square)](https://www.npmjs.com/package/@sourceregistry/sveltekit-enhance)
9
+ [![license](https://img.shields.io/npm/l/@sourceregistry/sveltekit-enhance?style=flat-square)](./LICENSE)
10
+ [![SvelteKit](https://img.shields.io/badge/SvelteKit-%5E2.58-FF3E00?style=flat-square&logo=svelte&logoColor=white)](https://kit.svelte.dev)
11
+ [![issues](https://img.shields.io/github/issues/SourceRegistry/sveltekit-enhance?style=flat-square)](https://github.com/SourceRegistry/sveltekit-enhance/issues)
6
12
 
7
- Production-ready utilities for **SvelteKit server flows**.
8
- Use `@sourceregistry/sveltekit-enhance` to build cleaner actions, loads, methods, and hooks with reusable guards for authentication, feature flags, request correlation, and form processing.
13
+ Wrap actions, loads, methods, and hooks with composable enhancers. Stack auth guards, feature flags, request tracing, and form parsing without touching SvelteKit's internals.
9
14
 
10
- ## Why teams use this
15
+ [Docs](https://sourceregistry.github.io/sveltekit-enhance/) · [npm](https://www.npmjs.com/package/@sourceregistry/sveltekit-enhance) · [Issues](https://github.com/SourceRegistry/sveltekit-enhance/issues)
11
16
 
12
- - Standardize server-side guard logic across routes.
13
- - Reduce repetitive request parsing and validation code.
14
- - Keep middleware-like behavior explicit and composable.
15
- - Improve observability with correlation IDs on every response.
17
+ </div>
18
+
19
+ ---
16
20
 
17
21
  ## Installation
18
22
 
19
- ```bash
23
+ ```sh
20
24
  npm install @sourceregistry/sveltekit-enhance
21
25
  ```
22
26
 
23
- ## Core concepts
27
+ **Peer dependency:** `@sveltejs/kit ^2.58.0`
24
28
 
25
- The package provides an `enhance` wrapper for:
29
+ ---
26
30
 
27
- - `enhance.action(...)`
28
- - `enhance.load(...)`
29
- - `enhance.method(...)`
30
- - `enhance.handle(...)`
31
+ ## Overview
31
32
 
32
- Each wrapper accepts one or more guard functions and merges their outputs into a typed `guard` object.
33
+ ```ts
34
+ import { enhance, Auth, RequestCorrelation, RequestMonitor, Form } from '@sourceregistry/sveltekit-enhance';
33
35
 
34
- ## Quick start
36
+ // hooks.server.ts
37
+ export const handle = enhance.handle(
38
+ async ({ event, resolve }) => resolve(event),
39
+ RequestCorrelation.attach,
40
+ RequestMonitor.trace({ logger: myLogger, record: metrics.record }),
41
+ );
35
42
 
36
- ```ts
37
- // src/routes/account/+page.server.ts
38
- import {enhance} from '@sourceregistry/sveltekit-enhance';
39
- import {Auth, FeatureFlag, form} from '@sourceregistry/sveltekit-enhance';
43
+ // +server.ts
44
+ export const POST = enhance.method(
45
+ async (event) => new Response(JSON.stringify(event.context)),
46
+ Auth.Bearer,
47
+ FeatureFlag.all('PUBLIC_API_ENABLED'),
48
+ );
40
49
 
50
+ // +page.server.ts
41
51
  export const actions = {
42
- save: enhance.action(
43
- async ({request, guard}) => {
44
- const data = await request.formData();
45
- const email = form.string$(data, 'email');
46
-
47
- return {
48
- ok: true,
49
- token: guard.token,
50
- email
51
- };
52
- },
52
+ default: enhance.action(
53
+ async (event) => {
54
+ const name = event.context.form.string$('name');
55
+ return success({ name });
56
+ },
57
+ Form.schema(myValidator),
58
+ ),
59
+ };
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Core API
65
+
66
+ Import from `@sourceregistry/sveltekit-enhance`.
67
+
68
+ ### `enhance.handle`
69
+
70
+ Wraps SvelteKit's `handle` hook. Enhancers run left-to-right before the handler; their return values are merged into `context`.
71
+
72
+ ```ts
73
+ import { enhance } from '@sourceregistry/sveltekit-enhance';
74
+
75
+ // src/hooks.server.ts
76
+ export const handle = enhance.handle(
77
+ async ({ event, resolve, context }) => resolve(event),
78
+ enhancerA,
79
+ enhancerB,
80
+ );
81
+ ```
82
+
83
+ ### `enhance.load`
84
+
85
+ Wraps server `load` functions.
86
+
87
+ ```ts
88
+ // +page.server.ts
89
+ export const load = enhance.load(
90
+ async (event) => ({ user: event.context.user }),
53
91
  Auth.Bearer,
54
- FeatureFlag.all('PUBLIC_ACCOUNT_EDIT')
55
- )
92
+ );
93
+ ```
94
+
95
+ ### `enhance.action`
96
+
97
+ Wraps form actions.
98
+
99
+ ```ts
100
+ // +page.server.ts
101
+ export const actions = {
102
+ submit: enhance.action(
103
+ async (event) => success(event.context),
104
+ Auth.Bearer,
105
+ Form.schema(myValidator),
106
+ ),
56
107
  };
57
108
  ```
58
109
 
59
- ## Included helpers
110
+ ### `enhance.method`
111
+
112
+ Wraps `+server.ts` endpoint handlers.
113
+
114
+ ```ts
115
+ // +server.ts
116
+ export const GET = enhance.method(
117
+ async (event) => new Response(JSON.stringify(event.context)),
118
+ Auth.Bearer,
119
+ );
120
+ ```
121
+
122
+ ### Utilities
123
+
124
+ ```ts
125
+ import { fail, error, success, not_good } from '@sourceregistry/sveltekit-enhance';
126
+
127
+ fail(400, { message: 'bad input' }); // throws ActionFailure — use inside actions
128
+ error(404, { message: 'not found' }); // throws HttpError
129
+ success({ id: 1 }); // typed identity helper
130
+ not_good(input, 403); // delegates to fail or error based on callType
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Helpers
136
+
137
+ All helpers are available from `@sourceregistry/sveltekit-enhance` or `@sourceregistry/sveltekit-enhance/helpers`.
138
+
139
+ ---
60
140
 
61
- - `Auth.Bearer`
62
- Validates `Authorization: Bearer <token>` and returns `{ token }`.
141
+ ### `Auth`
63
142
 
64
- - `FeatureFlag.all(...flags)` / `FeatureFlag.oneOf(...flags)`
65
- Enforces public environment-based feature flags.
143
+ Extracts and validates `Authorization: Bearer <token>` headers.
66
144
 
67
- - `RequestCorrelation.attach`
68
- Reuses incoming `x-correlation-id` / `x-request-id` or generates one, stores it in `locals`, and appends it to response headers.
145
+ ```ts
146
+ import { Auth } from '@sourceregistry/sveltekit-enhance';
69
147
 
70
- - `Devtools.ignore`
71
- Ignores the Chrome DevTools app-specific probe route with a `204` response.
148
+ export const GET = enhance.method(
149
+ async (event) => new Response(event.context.token),
150
+ Auth.Bearer,
151
+ );
152
+ ```
72
153
 
73
- - `Form` utilities
74
- Typed helpers for strings, numbers, booleans, dates, files, arrays, JSON, selector helpers, and schema-style validation workflows.
154
+ Returns `{ token: string }`. Throws `401` if the header is missing or malformed.
75
155
 
76
- ## Example: handle hook with correlation ID
156
+ ---
157
+
158
+ ### `Devtools`
159
+
160
+ Silences Chrome DevTools probe requests (`/.well-known/appspecific/com.chrome.devtools.json`) with a `204 No Content`. Logs in `dev` mode.
77
161
 
78
162
  ```ts
79
- // src/hooks.server.ts
80
- import {enhance, RequestCorrelation} from '@sourceregistry/sveltekit-enhance';
163
+ import { Devtools } from '@sourceregistry/sveltekit-enhance';
164
+
165
+ export const handle = enhance.handle(myHandler, Devtools.ignore);
166
+ ```
167
+
168
+ ---
169
+
170
+ ### `FeatureFlag`
171
+
172
+ Guards routes behind SvelteKit public env vars (`$env/dynamic/public`). Always passes in `dev` mode.
173
+
174
+ ```ts
175
+ import { FeatureFlag } from '@sourceregistry/sveltekit-enhance';
176
+
177
+ // All listed flags must be enabled
178
+ FeatureFlag.all('PUBLIC_FEATURE_A', 'PUBLIC_FEATURE_B')
179
+
180
+ // At least one flag must be enabled
181
+ FeatureFlag.oneOf('PUBLIC_FEATURE_A', 'PUBLIC_FEATURE_B')
182
+ ```
183
+
184
+ Truthy values: `true`, `TRUE`, `on`, `ON`, `1`. Returns `{ flags }` or throws `503 Feature not enabled`.
185
+
186
+ ---
187
+
188
+ ### `RequestCorrelation`
189
+
190
+ Propagates a correlation ID across the request/response cycle.
191
+
192
+ - Reads `x-correlation-id` or `x-request-id` from incoming headers
193
+ - Validates: max 128 chars, pattern `[A-Za-z0-9._:-]+`
194
+ - Generates a UUID v4 if absent
195
+ - Echoes the ID back via `x-correlation-id` response header
196
+
197
+ ```ts
198
+ import { RequestCorrelation } from '@sourceregistry/sveltekit-enhance';
199
+
200
+ export const handle = enhance.handle(myHandler, RequestCorrelation.attach);
201
+ ```
202
+
203
+ | Local | Type | Description |
204
+ |-------|------|-------------|
205
+ | `correlation_id` | `string` | Resolved correlation ID |
206
+ | `request_started_at` | `number` | `Date.now()` at attach time |
207
+
208
+ ---
209
+
210
+ ### `RequestMonitor`
211
+
212
+ Structured HTTP request logging and optional metrics collection. Instruments the full lifecycle: start, completion (log level by status), and unhandled errors — all with elapsed duration.
213
+
214
+ ```ts
215
+ import { RequestMonitor } from '@sourceregistry/sveltekit-enhance';
81
216
 
82
217
  export const handle = enhance.handle(
83
- async ({event, resolve}) => resolve(event),
84
- RequestCorrelation.attach
218
+ myHandler,
219
+ RequestCorrelation.attach,
220
+ RequestMonitor.trace({
221
+ logger: myLogger, // optional, defaults to console
222
+ record: metrics.record, // optional
223
+ }),
85
224
  );
86
225
  ```
87
226
 
88
- ## API exports
227
+ #### `TraceOptions`
228
+
229
+ | Option | Type | Default | Description |
230
+ |--------|------|---------|-------------|
231
+ | `logger` | `TraceLogger` | `console` | Must implement `debug`, `info`, `warn`, `error` |
232
+ | `record` | `(entry: RecordTraceMetricEntry) => any` | — | Called after every request |
233
+
234
+ #### Log events
235
+
236
+ | Event | Level | Condition |
237
+ |-------|-------|-----------|
238
+ | `http.request.started` | `debug` | Before resolve |
239
+ | `http.request.completed` | `info` | `status < 400` |
240
+ | `http.request.completed` | `warn` | `status 4xx` |
241
+ | `http.request.completed` | `error` | `status 5xx` |
242
+ | `http.request.failed` | `error` | Unhandled throw |
243
+
244
+ #### Types
245
+
246
+ ```ts
247
+ type RecordTraceMetricEntry = { method: string; path: string; status: number; durationMs: number }
248
+ type TraceLogger = { debug(...args: any[]): any; info(...args: any[]): any; warn(...args: any[]): any; error(...args: any[]): any }
249
+ ```
250
+
251
+ Locals set: `trace: { id: string; started_at: bigint }`.
252
+
253
+ ---
254
+
255
+ ### `Form`
256
+
257
+ Typed, ergonomic FormData extraction. Works standalone or as an enhancer.
258
+
259
+ #### As an enhancer — `Form.schema`
260
+
261
+ Validates and deserializes form data via a `Validator<T>` before the handler runs.
262
+
263
+ ```ts
264
+ import { Form, enhance, success } from '@sourceregistry/sveltekit-enhance';
265
+
266
+ export const actions = {
267
+ default: enhance.action(
268
+ async (event) => success(event.context.form.result),
269
+ Form.schema(myValidator),
270
+ ),
271
+ };
272
+ ```
273
+
274
+ #### Standalone — `Form.handle`
275
+
276
+ ```ts
277
+ import { Form } from '@sourceregistry/sveltekit-enhance';
278
+
279
+ await Form.handle(request, ({ form }) => {
280
+ const name = form.string$('name');
281
+ const age = form.number('age');
282
+ return { name, age };
283
+ });
284
+ ```
285
+
286
+ #### Field extractors
287
+
288
+ Optional variants return `undefined` when the field is absent. Required variants (`$` suffix) throw `fail(400)`.
289
+
290
+ | Method | Returns | Notes |
291
+ |--------|---------|-------|
292
+ | `string(name)` / `string$(name)` | `string \| null \| undefined` | |
293
+ | `pattern$(name, pattern)` | `string` | `RegExp` or pattern string |
294
+ | `number(name)` / `number$(name)` | `number \| undefined` | |
295
+ | `boolean(name)` / `boolean$(name)` | `boolean \| undefined` | Accepts `true/false`, `1/0`, `on/off` |
296
+ | `date(name, parser?)` / `date$(name, parser)` | `Date \| undefined` | Custom parser supported |
297
+ | `json<T>(name, transformer?)` / `json$(name)` | `T \| undefined` | Optional transform fn |
298
+ | `jsond(options)` | `any` | All FormData → nested object via dot-notation keys |
299
+ | `file(name)` / `file$(name)` | `File \| null \| undefined` | |
300
+ | `files(name)` | `File[]` | Non-empty, named files only |
301
+ | `fileRecord(prefix, removePrefix?)` | `Record<string, File[]>` | Groups files by key prefix |
302
+ | `array<T>(name, mapper?)` / `array$(name)` | `T[] \| undefined` | |
303
+ | `enum(name, Enum)` / `enum$(name, Enum)` | `keyof E \| undefined` | |
304
+ | `record(options?)` | `Record<string, any>` | All entries; optional `filter` / `transformer` |
305
+ | `validate(schema, options?)` | `T` | Runs `Validator<T>`, throws `fail(400)` on failure |
306
+
307
+ #### Conditional helpers
89
308
 
90
309
  ```ts
91
- import {
92
- enhance,
93
- action,
94
- load,
95
- method,
96
- handle,
97
- Auth,
98
- FeatureFlag,
99
- RequestCorrelation,
100
- Devtools,
101
- Form
102
- } from '@sourceregistry/sveltekit-enhance';
310
+ form.onlyIf(condition, trueVal, falseVal?)
311
+ form.onlyIfPresent(key, (entry) => ..., fallback?)
312
+ form.onlyIfArrayPresent(key, (entries) => ..., fallback)
313
+ form.selector({ fieldName: (entry, key) => ..., $default?, $error? })
314
+ form.selector$({ ... }) // throws fail(400) if no case matches
315
+ form.basedOn(val, processor)
316
+ form.process(name, parser, processor)
103
317
  ```
104
318
 
105
- ## Compatibility
319
+ #### Standalone functions
320
+
321
+ All `FormContext` methods are also exported as standalone functions taking `FormData` as the first argument, plus:
322
+
323
+ ```ts
324
+ arrayString(formdata, name, delimiter, mapper?)
325
+ hasOneOf(formdata, names)
326
+ reviver(key, value) // JSON.parse reviver — coerces strings to typed primitives
327
+ ```
328
+
329
+ ---
330
+
331
+ ## Type Reference
332
+
333
+ ```ts
334
+ // Core
335
+ EnhanceInput<CallType> // event passed to all enhancers
336
+ EnhanceFunction<CallType> // (event: EnhanceInput) => MaybePromise<object | Response>
337
+ EnhanceHandle // final handle fn — ({ event, resolve, context }) => MaybePromise<Response>
338
+ EnhanceLoad // final load fn
339
+ EnhanceAction // final action fn
340
+ EnhanceMethod // final method fn
341
+ MaybePromise<T> // T | Promise<T>
342
+
343
+ // Form
344
+ Validator<T> // (value: unknown, path?: ValidationPath) => ValidationResult<T>
345
+ ValidationResult<T> // { success: true; data: T } | { success: false; errors: ValidationIssue[] }
346
+ ValidationIssue // { path: string; message: string; code?: string }
347
+ FormContext // fluent API returned by Form.enhance / Form.handle
348
+ InferValidator<V> // infers T from Validator<T>
349
+
350
+ // Helpers
351
+ TraceLogger // { debug, info, warn, error }
352
+ TraceOptions // { logger?: TraceLogger; record?: (entry) => any }
353
+ RecordTraceMetricEntry // { method: string; path: string; status: number; durationMs: number }
354
+ RequestTraceLocals // { trace?: { id: string; started_at: bigint } }
355
+ RequestCorrelationLocals // { correlation_id?: string; request_started_at?: number }
356
+ ```
106
357
 
107
- - SvelteKit 2+
108
- - Node.js runtime (matching your SvelteKit adapter/runtime support)
358
+ ---
109
359
 
110
360
  ## License
111
361
 
112
- Apache-2.0
362
+ [Apache-2.0](./LICENSE) © [A.P.A. Slaa](https://github.com/SourceRegistry)
@@ -3,3 +3,4 @@ export * from './devtools.js';
3
3
  export * from './featureflag.js';
4
4
  export * from './form.ts';
5
5
  export * from './request-correlation.ts';
6
+ export * from './request-monitor.js';
@@ -3,3 +3,4 @@ export * from './devtools.js';
3
3
  export * from './featureflag.js';
4
4
  export * from "./form.js";
5
5
  export * from "./request-correlation.js";
6
+ export * from './request-monitor.js';
@@ -0,0 +1,26 @@
1
+ import type { EnhanceFunction } from "../index.js";
2
+ export type RequestTraceLocals = {
3
+ trace?: {
4
+ id: string;
5
+ started_at: bigint;
6
+ };
7
+ };
8
+ export type RecordTraceMetricEntry = {
9
+ method: string;
10
+ path: string;
11
+ status: number;
12
+ durationMs: number;
13
+ };
14
+ export type TraceLogger = {
15
+ debug: (...args: any[]) => any;
16
+ info: (...args: any[]) => any;
17
+ warn: (...args: any[]) => any;
18
+ error: (...args: any[]) => any;
19
+ };
20
+ export type TraceOptions = {
21
+ logger?: TraceLogger;
22
+ record?: (entry: RecordTraceMetricEntry) => any;
23
+ };
24
+ export declare const RequestMonitor: {
25
+ trace: (options?: TraceOptions) => EnhanceFunction<"handle">;
26
+ };
@@ -0,0 +1,72 @@
1
+ const durationMs = (startedAt) => Number(process.hrtime.bigint() - startedAt) / 1_000_000;
2
+ const resolveRoute = (event) => event.route.id ?? event.url.pathname;
3
+ export const RequestMonitor = {
4
+ trace: (options = {}) => {
5
+ const { logger = console, record, } = options;
6
+ return async (event) => {
7
+ const locals = event.locals;
8
+ const requestId = locals.requestId ?? "unknown";
9
+ const route = resolveRoute(event);
10
+ locals.trace = {
11
+ id: requestId,
12
+ started_at: process.hrtime.bigint()
13
+ };
14
+ logger.debug("http.request.started", {
15
+ request_id: requestId,
16
+ method: event.request.method,
17
+ route,
18
+ client_ip: event?.getClientAddress?.()
19
+ });
20
+ try {
21
+ const response = await event.resolve(event.event);
22
+ if (!response)
23
+ return response;
24
+ const elapsedMs = Number(durationMs(locals.trace.started_at).toFixed(2));
25
+ record?.({
26
+ method: event.request.method,
27
+ path: route,
28
+ status: response.status,
29
+ durationMs: elapsedMs,
30
+ });
31
+ const context = {
32
+ request_id: requestId,
33
+ method: event.request.method,
34
+ route,
35
+ status: response.status,
36
+ duration_ms: elapsedMs,
37
+ client_ip: event?.getClientAddress?.()
38
+ };
39
+ if (response.status >= 500) {
40
+ logger.error("http.request.completed", context);
41
+ return response;
42
+ }
43
+ if (response.status >= 400) {
44
+ logger.warn("http.request.completed", context);
45
+ return response;
46
+ }
47
+ logger.info("http.request.completed", context);
48
+ return response;
49
+ }
50
+ catch (error) {
51
+ const elapsedMs = Number(durationMs(locals.trace.started_at).toFixed(2));
52
+ record?.({
53
+ method: event.request.method,
54
+ path: route,
55
+ status: 500,
56
+ durationMs: elapsedMs,
57
+ });
58
+ logger.error("http.request.failed", {
59
+ request_id: requestId,
60
+ method: event.request.method,
61
+ route,
62
+ duration_ms: elapsedMs,
63
+ client_ip: event?.getClientAddress?.(),
64
+ error: error instanceof Error
65
+ ? { name: error.name, message: error.message }
66
+ : { value: String(error) }
67
+ });
68
+ throw error;
69
+ }
70
+ };
71
+ }
72
+ };
package/dist/index.d.ts CHANGED
@@ -18,10 +18,11 @@ export type EnhanceInput<CallType extends EnhanceCallType = EnhanceCallType, Par
18
18
  request: Request;
19
19
  callType: CallType;
20
20
  fetch: typeof fetch;
21
+ getClientAddress?: () => string;
21
22
  get errorHandlers(): EnhanceErrorHandler[];
22
23
  } & (CallType extends 'handle' ? {
23
24
  get responseHandlers(): EnhanceResponseHandler[];
24
- resolve: (event?: RequestEvent) => never;
25
+ resolve: (event?: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
25
26
  readonly event: RequestEvent;
26
27
  } : CallType extends 'load' ? {
27
28
  parent: ServerLoadEvent<Params, ParentData, RouteId>['parent'];
package/dist/index.js CHANGED
@@ -118,6 +118,7 @@ handle, ...contexts) => {
118
118
  };
119
119
  export const handle = (handle, ...contexts) => async (input) => {
120
120
  let combined = {};
121
+ let resolved = false;
121
122
  const contextInput = Object.assign(input.event, {
122
123
  __errorHandlers__: [],
123
124
  __responseHandler__: [],
@@ -129,24 +130,71 @@ export const handle = (handle, ...contexts) => async (input) => {
129
130
  get responseHandlers() {
130
131
  return this.__responseHandler__;
131
132
  },
132
- resolve: (event = input.event) => {
133
- throw input.resolve(event);
133
+ resolve: (event = input.event, opts) => {
134
+ resolved = true;
135
+ return apply_handle(contexts.length, event, opts);
134
136
  },
135
137
  get event() {
136
138
  return input.event;
137
139
  }
138
140
  });
139
- for (const context of contexts) {
141
+ const merge_options = (parent_options, options) => {
142
+ const transformPageChunk = async ({ html, done }) => {
143
+ if (options?.transformPageChunk) {
144
+ html = (await options.transformPageChunk({ html, done })) ?? '';
145
+ }
146
+ if (parent_options?.transformPageChunk) {
147
+ html = (await parent_options.transformPageChunk({ html, done })) ?? '';
148
+ }
149
+ return html;
150
+ };
151
+ return {
152
+ transformPageChunk,
153
+ filterSerializedResponseHeaders: parent_options?.filterSerializedResponseHeaders ??
154
+ options?.filterSerializedResponseHeaders,
155
+ preload: parent_options?.preload ?? options?.preload
156
+ };
157
+ };
158
+ const apply_handle = async (i, event, parent_options) => {
159
+ if (i < contexts.length) {
160
+ const context = contexts[i];
161
+ const previous_resolve = contextInput.resolve;
162
+ contextInput.resolve = (next_event = event, options) => {
163
+ resolved = true;
164
+ return apply_handle(i + 1, next_event, merge_options(parent_options, options));
165
+ };
166
+ try {
167
+ resolved = false;
168
+ const result = await context(contextInput);
169
+ if (result instanceof Response)
170
+ return result;
171
+ if (resolved) {
172
+ throw new Error('enhance.handle context called resolve but did not return its response');
173
+ }
174
+ combined = Object.assign(combined, result);
175
+ return apply_handle(i + 1, event, parent_options);
176
+ }
177
+ catch (e) {
178
+ return EnhanceErrorHandle(e, contextInput);
179
+ }
180
+ finally {
181
+ contextInput.resolve = previous_resolve;
182
+ }
183
+ }
140
184
  try {
141
- const result = await context(contextInput);
142
- combined = Object.assign(combined, result);
185
+ return await handle({
186
+ ...input,
187
+ context: combined,
188
+ event,
189
+ resolve: (next_event, options) => input.resolve(next_event, merge_options(parent_options, options))
190
+ });
143
191
  }
144
192
  catch (e) {
145
193
  return EnhanceErrorHandle(e, contextInput);
146
194
  }
147
- }
195
+ };
148
196
  try {
149
- const response = await handle(Object.assign(input, { context: combined }));
197
+ const response = await apply_handle(0, input.event);
150
198
  if (contextInput.responseHandlers.length > 0) {
151
199
  for (const responseHandler of contextInput.responseHandlers) {
152
200
  await responseHandler({ event: contextInput, response });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sourceregistry/sveltekit-enhance",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Composable enhance and form utilities for SvelteKit actions, loads, methods, and hooks.",
5
5
  "author": "A.P.A. Slaa (a.p.a.slaa@projectsource.nl)",
6
6
  "scripts": {
@@ -31,6 +31,10 @@
31
31
  "types": "./dist/index.d.ts",
32
32
  "svelte": "./dist/index.js",
33
33
  "default": "./dist/index.js"
34
+ },
35
+ "./helpers": {
36
+ "types": "./dist/helpers/index.d.ts",
37
+ "default": "./dist/helpers/index.js"
34
38
  }
35
39
  },
36
40
  "repository": {