@azure-net/kit 6.0.0-next.7 → 6.0.0-next.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ must remain synchronous.
14
14
 
15
15
  The plugin provides virtual hooks only. It does not create or rewrite page/layout
16
16
  modules. Manual `hooks.server`, `hooks.client` and `hooks` files are forbidden.
17
- Initial Edges SSR state is hydrated once; later navigation is client-owned.
17
+ Initial request state is hydrated once through Svelte hydratable; later navigation is client-owned.
18
18
 
19
19
  `program.ts` is universal: a static server-only import does not become safe merely
20
20
  because it is used inside `useServer`. Use an explicit build-time browser guard
@@ -195,12 +195,10 @@ export const { register } = createApp((app) =>
195
195
  app
196
196
  .useServer(async ({ event, resolve, useMiddlewares }) => {
197
197
  await useMiddlewares([ServerAuth]);
198
- await TranslationManager().preloadTranslation();
199
198
  return resolve(event);
200
199
  })
201
200
  .useBoot(({ state }) => {
202
201
  useCurrentUser().setUser(state.currentUser ?? null);
203
- TranslationManager().syncFromState(state);
204
202
  })
205
203
  );
206
204
  ```
@@ -229,7 +227,7 @@ script and serialization. No custom HTML parser is used by the kit.
229
227
  `createBoundaryProvider` is replaced by `CreateServiceContainer` (no compatibility alias):
230
228
 
231
229
  ```ts
232
- const ApplicationProvider = CreateDiContainer('ApplicationProvider', {
230
+ const ApplicationProvider = CreateServiceContainer('ApplicationProvider', {
233
231
  register: () => ({ Users: () => new UsersUseCases(repository) })
234
232
  });
235
233
  const { Users } = ApplicationProvider();
@@ -239,4 +237,139 @@ The accessor remains lazy, synchronous and request-scoped on SSR. `RegisterProvi
239
237
  registers an individual class factory; `singleton` means one browser instance but
240
238
  one instance per request on the server, never a server process singleton.
241
239
 
242
- Built-in i18n: see [I18N.md](./I18N.md). preloadTranslation() fills request state automatically; syncFromState(state) restores it synchronously in useBoot. No separate translation package, writable/derived stores or edges adapter is required.
240
+ Built-in i18n: see [I18N.md](./I18N.md). Register a `new TranslationManager(options)` through `app.useTranslations(manager)`. Server preparation, locale hydration and client dictionary loading run automatically before the corresponding user hooks. No separate translation package or manual lifecycle calls are required.
241
+
242
+ ## AsyncSignal SSR Hydration
243
+
244
+ Client-only signals remain synchronous. Opt into SSR with a stable, non-empty key:
245
+
246
+ ```svelte
247
+ <script lang="ts">
248
+ import { createAsyncSignal } from '@azure-net/kit';
249
+
250
+ const result = await createAsyncSignal(() => loadProfile(), {
251
+ server: true,
252
+ key: 'profile:current-user'
253
+ });
254
+ </script>
255
+
256
+ <p>{result.response?.name}</p>
257
+ <button onclick={() => result.refresh()}>Refresh</button>
258
+ ```
259
+
260
+ Call this during component initialization with Svelte experimental async enabled.
261
+ Svelte's `hydratable` transfers the initial serializable result to the browser,
262
+ which reuses it without a duplicate request; later refreshes execute normally.
263
+ There is no module-global server request state. The handler must work in both
264
+ environments. Keys must match across SSR/hydration and distinguish request inputs.
265
+
266
+ `beforeSend` runs only for actual requests; success/error callbacks and `ready`
267
+ settlement apply on both sides. Request errors produce an error-state signal.
268
+ `immediate: false` keeps the signal idle and disables initial hydration. Batch
269
+ factories stay synchronous and explicitly reject `server: true` at type-check and
270
+ runtime; use individually awaited server signals instead.
271
+
272
+ See [AsyncSignal details](./src/lib/svelte/async-signal/README.md) for lifecycle,
273
+ serialization, errors, cancellation, CSP, and batch behavior.
274
+
275
+ ## SvelteKit 3 hooks and class middleware
276
+
277
+ Error callbacks use the native discriminated `CaughtError` contract from
278
+ `@sveltejs/kit/hooks`. `status` and `message` are no longer added at the top level.
279
+ For `kind: 'app'`, `'framework'` or `'validation'`, read `error.status` and
280
+ `error.message`. For `kind: 'unknown'`, `error` remains `unknown`: narrow it before
281
+ logging and do not return its potentially private message to clients. Validation
282
+ issues are available only in the `'validation'` branch. Return `{ status, message }`
283
+ to override the public response, or return nothing to retain SvelteKit defaults.
284
+ The removed `handleValidationError` hook is not generated.
285
+
286
+ ```ts
287
+ app.useServerError(({ kind, error, useLogger }) => {
288
+ if (kind === 'unknown') {
289
+ useLogger(error, {});
290
+ return { status: 500, message: 'Internal Error' };
291
+ }
292
+ return error;
293
+ });
294
+ ```
295
+
296
+ Middleware arrays now contain **constructors**, not functions or existing instances.
297
+ Constructors must accept no required arguments. A fresh instance is constructed
298
+ only when its turn is reached, on every request/navigation. Resolve dependencies
299
+ inside that instance, not into module-level request-specific variables.
300
+
301
+ ```ts
302
+ import type { IServerMiddleware, ServerMiddlewareContext, IClientMiddleware, ClientMiddlewareContext } from '@azure-net/kit';
303
+
304
+ class ServerAuth implements IServerMiddleware {
305
+ async execute({ event, next }: ServerMiddlewareContext) {
306
+ if (!event.cookies.get('session')) return next('/login', 303);
307
+ next();
308
+ }
309
+ }
310
+
311
+ class ClientGuard implements IClientMiddleware {
312
+ execute({ next, to, navigation }: ClientMiddlewareContext): undefined {
313
+ if (navigation?.shallow) return next();
314
+ if (to.pathname === '/private') return next('/login', { replace: true });
315
+ return next();
316
+ }
317
+ }
318
+
319
+ app
320
+ .useServer(async ({ useMiddlewares, event, resolve }) => {
321
+ await useMiddlewares([ServerAuth]);
322
+ return resolve(event);
323
+ })
324
+ .useClient(({ useMiddlewares }) => {
325
+ useMiddlewares([ClientGuard]);
326
+ });
327
+ ```
328
+
329
+ `IMiddleware<Context, Result>` is the common interface. Client `execute` is strictly
330
+ synchronous (`undefined` return, normally `return next()`); server `execute` can
331
+ return `void` or `Promise<void>`. Omitting `next()` blocks the chain. Neither the
332
+ chain nor the container automatically disposes subscriptions owned by middleware:
333
+ keep middleware short-lived and do not attach long-lived listeners there.
334
+
335
+ Client `next(location, options)` and `useRedirect(...).clientRedirectSettings`
336
+ use SvelteKit's `GotoOptions` directly: `replace`, `reset`, `refreshAll`, `shallow`,
337
+ `persistState`, `state` and `invalidate`. Client navigation data includes the native
338
+ `navigation` event. In Kit 3 shallow changes also trigger navigation hooks; choose
339
+ explicitly whether a guard should skip them. `goto` is for app routes, not external
340
+ URLs; external navigation should use browser location APIs.
341
+
342
+ ## CompositionRoot
343
+
344
+ `CreateServiceContainer` remains unchanged. `CompositionRoot` is a lazy class
345
+ facade over it, with a unique symbol as its internal container identity. It can be created at module
346
+ scope; `resolve()` resolves against the current SSR request or browser
347
+ cache, never a request captured by the constructor.
348
+
349
+ ```ts
350
+ import { CompositionRoot } from '@azure-net/kit';
351
+
352
+ export const Http = new CompositionRoot({
353
+ register: () => ({ Api: () => new ApiDatasource() })
354
+ });
355
+
356
+ export const Identity = new CompositionRoot({
357
+ dependsOn: { Http },
358
+ register: ({ Http }) => ({
359
+ Account: () => new AccountUseCases(new AccountRepository(Http.Api))
360
+ })
361
+ });
362
+
363
+ // Inside a presenter factory or an active request:
364
+ const { Account } = Identity.resolve();
365
+ ```
366
+
367
+ Dependencies accept both other `CompositionRoot` instances and existing
368
+ `CreateServiceContainer` accessors. Service types are inferred through the chain.
369
+ Only registration metadata is wrapped; there is no extra proxy around services
370
+ and no new caching layer. Repeated resolution preserves existing container/service
371
+ identity. If explicit cleanup is needed, use `await cleanupProvider(Identity.name)`.
372
+ Declare roots once, rather than creating new roots per component render.
373
+ `name` is now a symbol, not a serializable string. Its description is for diagnostics
374
+ only; equal descriptions never share a cache. Existing string keys passed directly
375
+ to `CreateServiceContainer` and `cleanupProvider` remain supported.
@@ -4,10 +4,12 @@ export interface TranslationLifecycle {
4
4
  syncFromState(state: App.RequestContextState): void;
5
5
  transformHtml(html: string): string;
6
6
  }
7
- declare const translationManager: unique symbol;
8
- export type TranslationManagerFactory = (() => object) & {
7
+ export declare const translationManager: unique symbol;
8
+ export interface TranslationManagerHandle {
9
9
  readonly [translationManager]: true;
10
- };
10
+ }
11
+ export type TranslationManagerFactory = (() => object) & TranslationManagerHandle;
12
+ export type TranslationManagerSource = TranslationManagerFactory | TranslationManagerHandle;
11
13
  export declare const bindTranslationLifecycle: (manager: object, lifecycle: TranslationLifecycle) => void;
12
14
  export declare const getTranslationLifecycle: (manager: object) => TranslationLifecycle;
13
- export {};
15
+ export declare const getTranslationManagerLifecycle: (manager: TranslationManagerSource) => TranslationLifecycle;
@@ -5,6 +5,7 @@ export const bindTranslationLifecycle = (manager, lifecycle) => {
5
5
  export const getTranslationLifecycle = (manager) => {
6
6
  const lifecycle = lifecycles.get(manager);
7
7
  if (!lifecycle)
8
- throw new Error('[AzureNetKit:i18n] Expected a manager returned by createTranslations().');
8
+ throw new Error('[AzureNetKit:i18n] Expected a TranslationManager or a manager returned by createTranslations().');
9
9
  return lifecycle;
10
10
  };
11
+ export const getTranslationManagerLifecycle = (manager) => getTranslationLifecycle(typeof manager === 'function' ? manager() : manager);
@@ -0,0 +1,19 @@
1
+ import { createTranslations } from './Translations.svelte.js';
2
+ import { type translationManager, type TranslationManagerHandle } from './Internal.js';
3
+ import type { MessageLoader, Path, TranslationVariables } from './Types.js';
4
+ export type TranslationManagerOptions<TMessages extends Record<string, MessageLoader>, TInitLocale extends Extract<keyof TMessages, string>> = Parameters<typeof createTranslations<TMessages, TInitLocale>>[0];
5
+ /** A module-safe facade. Only the provider owns request-local reactive state. */
6
+ export declare class TranslationManager<const TMessages extends Record<string, MessageLoader>, const TInitLocale extends Extract<keyof TMessages, string>> implements TranslationManagerHandle {
7
+ #private;
8
+ readonly [translationManager]: true;
9
+ constructor(options: TranslationManagerOptions<TMessages, TInitLocale>);
10
+ readonly t: (translation?: Path<Awaited<ReturnType<TMessages[TInitLocale]>>> | string | {
11
+ key: Path<Awaited<ReturnType<TMessages[TInitLocale]>>> | string;
12
+ vars?: TranslationVariables;
13
+ }, vars?: TranslationVariables) => string;
14
+ readonly switchLocale: (locale: Extract<keyof TMessages, string>) => Promise<void>;
15
+ readonly applyHtmlLocaleAttr: (html: string) => string;
16
+ readonly subscribeLocaleChangeEvent: (listener: (locale: Extract<keyof TMessages, string>) => void) => (() => void);
17
+ get locale(): Extract<keyof TMessages, string>;
18
+ get locales(): Readonly<Record<Extract<keyof TMessages, string>, Extract<keyof TMessages, string>>>;
19
+ }
@@ -0,0 +1,26 @@
1
+ import { createTranslations } from './Translations.svelte.js';
2
+ import { bindTranslationLifecycle, getTranslationLifecycle } from './Internal.js';
3
+ /** A module-safe facade. Only the provider owns request-local reactive state. */
4
+ export class TranslationManager {
5
+ #factory;
6
+ constructor(options) {
7
+ this.#factory = createTranslations(options);
8
+ // Resolve on every lifecycle invocation, never capture a server request here.
9
+ bindTranslationLifecycle(this, {
10
+ preloadTranslation: () => getTranslationLifecycle(this.#factory()).preloadTranslation(),
11
+ initializeClient: () => getTranslationLifecycle(this.#factory()).initializeClient(),
12
+ syncFromState: (state) => getTranslationLifecycle(this.#factory()).syncFromState(state),
13
+ transformHtml: (html) => getTranslationLifecycle(this.#factory()).transformHtml(html)
14
+ });
15
+ }
16
+ t = (translation, vars) => this.#factory().t(translation, vars);
17
+ switchLocale = (locale) => this.#factory().switchLocale(locale);
18
+ applyHtmlLocaleAttr = (html) => this.#factory().applyHtmlLocaleAttr(html);
19
+ subscribeLocaleChangeEvent = (listener) => this.#factory().subscribeLocaleChangeEvent(listener);
20
+ get locale() {
21
+ return this.#factory().locale;
22
+ }
23
+ get locales() {
24
+ return this.#factory().locales;
25
+ }
26
+ }
@@ -1,2 +1,3 @@
1
1
  export { createTranslations } from './Translations.svelte.js';
2
+ export { TranslationManager, type TranslationManagerOptions } from './TranslationManager.js';
2
3
  export type { Translation, TranslationShape, TranslationSnapshot, TranslationCookieOptions, Path } from './Types.js';
@@ -1 +1,2 @@
1
1
  export { createTranslations } from './Translations.svelte.js';
2
+ export { TranslationManager } from './TranslationManager.js';
@@ -1,16 +1,16 @@
1
- import { type TranslationManagerFactory } from '../../i18n/Internal.js';
1
+ import { type TranslationManagerSource } from '../../i18n/Internal.js';
2
2
  import { type ContextData } from '../../shared/request-context/Internal.js';
3
3
  import { AppError, type IAppError } from '../app-error/AppError.js';
4
4
  import { type AsyncHelperRetry } from '../app-error/ErrorRuntime.js';
5
- import { type IServerMiddleware } from './middleware/ServerMiddleware.js';
5
+ import { type ServerMiddlewareClass } from './middleware/ServerMiddleware.js';
6
6
  import type { Handle, HandleFetch, HandleServerError, HandleClientError, Reroute, ServerInit, Transport } from '@sveltejs/kit/hooks';
7
7
  import type { RequestEvent } from '@sveltejs/kit';
8
- import { type IClientMiddleware } from './middleware/ClientMiddleware.js';
8
+ import { type ClientMiddlewareClass } from './middleware/ClientMiddleware.js';
9
9
  import { useLogger } from '../logger/index.js';
10
10
  type MaybePromise<T> = T | Promise<T>;
11
11
  type HandleInput = Parameters<Handle>[0];
12
12
  type Resolve = HandleInput['resolve'];
13
- type BuilderFlags = Partial<Record<'useTranslations' | 'useBoot' | 'use' | 'useClient' | 'useClientInit' | 'useClientError' | 'useServerInit' | 'useServer' | 'useServerError' | 'useServerFetch' | 'useServerValidationError' | 'useAzureNetKitError' | 'useReroute' | 'useTransport', true>>;
13
+ type BuilderFlags = Partial<Record<'useTranslations' | 'useBoot' | 'use' | 'useClient' | 'useClientInit' | 'useClientError' | 'useServerInit' | 'useServer' | 'useServerError' | 'useServerFetch' | 'useAzureNetKitError' | 'useReroute' | 'useTransport', true>>;
14
14
  type WithFlag<TFlags extends BuilderFlags, TKey extends keyof BuilderFlags> = TFlags & Record<TKey, true>;
15
15
  type OnceMethod<TFlags extends BuilderFlags, TKey extends keyof BuilderFlags, TMethod> = TFlags[TKey] extends true ? object : TMethod;
16
16
  export interface AppUniversalLifecycleContext {
@@ -20,24 +20,19 @@ export interface AppUniversalLifecycleContext {
20
20
  event?: RequestEvent;
21
21
  }
22
22
  export interface AppClientLifecycleContext {
23
- useMiddlewares: (middlewares: IClientMiddleware[]) => void;
23
+ useMiddlewares: (middlewares: readonly ClientMiddlewareClass[]) => void;
24
24
  }
25
25
  export type AppClientInitLifecycleContext = void;
26
- export type AppClientErrorContext = Parameters<HandleClientError>[0] & {
27
- status: number;
28
- message: string;
29
- };
26
+ export type AppClientErrorContext = Parameters<HandleClientError>[0];
30
27
  export type AppServerInitLifecycleContext = void;
31
28
  export interface AppServerLifecycleContext {
32
29
  requestContext: ContextData;
33
30
  event: RequestEvent;
34
31
  resolve: Resolve;
35
- useMiddlewares: (middlewares: IServerMiddleware[]) => Promise<void>;
32
+ useMiddlewares: (middlewares: readonly ServerMiddlewareClass[]) => Promise<void>;
36
33
  }
37
34
  export type AppServerErrorContext = Parameters<HandleServerError>[0] & {
38
35
  requestContext: ContextData;
39
- status: number;
40
- message: string;
41
36
  useLogger: typeof useLogger;
42
37
  };
43
38
  export type AppServerFetchContext = Parameters<HandleFetch>[0] & {
@@ -68,7 +63,7 @@ export type AppBootCallback = (context: AppBootContext) => undefined;
68
63
  export type CreateAppBuilder<TFlags extends BuilderFlags = object> = OnceMethod<TFlags, 'useBoot', {
69
64
  useBoot(callback: AppBootCallback): CreateAppBuilder<WithFlag<TFlags, 'useBoot'>>;
70
65
  }> & OnceMethod<TFlags, 'useTranslations', {
71
- useTranslations(manager: TranslationManagerFactory): CreateAppBuilder<WithFlag<TFlags, 'useTranslations'>>;
66
+ useTranslations(manager: TranslationManagerSource): CreateAppBuilder<WithFlag<TFlags, 'useTranslations'>>;
72
67
  }> & OnceMethod<TFlags, 'use', {
73
68
  use(callback: AppUniversalLifecycleCallback): CreateAppBuilder<WithFlag<TFlags, 'use'>>;
74
69
  }> & OnceMethod<TFlags, 'useClient', {
@@ -1,4 +1,4 @@
1
- import { getTranslationLifecycle } from '../../i18n/Internal.js';
1
+ import { getTranslationManagerLifecycle } from '../../i18n/Internal.js';
2
2
  import { hydratable } from 'svelte';
3
3
  import { getContextInternals } from '../../shared/request-context/Internal.js';
4
4
  import { BROWSER } from '../../external/tools/index.js';
@@ -9,15 +9,6 @@ import { executeServerMiddlewares } from './middleware/ServerMiddleware.js';
9
9
  import { beforeNavigate } from '$app/navigation';
10
10
  import { executeClientMiddlewares } from './middleware/ClientMiddleware.js';
11
11
  import { useLogger } from '../logger/index.js';
12
- // Kit 3 nests safe status/message inside caught errors; unknown errors keep safe defaults.
13
- const errorDetails = (input) => {
14
- const legacy = input;
15
- const error = input.kind !== 'unknown' && input.error && typeof input.error === 'object' ? input.error : {};
16
- return {
17
- status: typeof legacy.status === 'number' ? legacy.status : typeof error.status === 'number' ? error.status : 500,
18
- message: typeof legacy.message === 'string' ? legacy.message : typeof error.message === 'string' ? error.message : 'Internal Error'
19
- };
20
- };
21
12
  const clientLifecycleFlags = new WeakMap();
22
13
  const getRequestContext = () => {
23
14
  try {
@@ -162,7 +153,7 @@ export const createApp = (callback) => {
162
153
  };
163
154
  if (!callbacks.useTranslations)
164
155
  return initialize();
165
- translationInitPromise = getTranslationLifecycle(callbacks.useTranslations())
156
+ translationInitPromise = getTranslationManagerLifecycle(callbacks.useTranslations)
166
157
  .initializeClient()
167
158
  .then(initialize)
168
159
  .catch((error) => {
@@ -192,7 +183,6 @@ export const createApp = (callback) => {
192
183
  return serverInitPromise;
193
184
  };
194
185
  const runServerError = (input) => {
195
- const { status, message } = errorDetails(input);
196
186
  if (BROWSER)
197
187
  return undefined;
198
188
  const requestContext = getServerRequestContext();
@@ -201,8 +191,6 @@ export const createApp = (callback) => {
201
191
  const result = callbacks.useServerError?.({
202
192
  requestContext,
203
193
  ...input,
204
- status,
205
- message,
206
194
  useLogger
207
195
  });
208
196
  if (result && typeof result.then === 'function') {
@@ -217,14 +205,11 @@ export const createApp = (callback) => {
217
205
  }
218
206
  };
219
207
  const runClientError = (input) => {
220
- const { status, message } = errorDetails(input);
221
208
  if (!BROWSER)
222
209
  return undefined;
223
210
  bindErrorResolver();
224
211
  return callbacks.useClientError?.({
225
- ...input,
226
- status,
227
- message
212
+ ...input
228
213
  });
229
214
  };
230
215
  const runServerFetch = (input) => {
@@ -237,7 +222,7 @@ export const createApp = (callback) => {
237
222
  };
238
223
  const reroute = (input) => callbacks.useReroute?.(input);
239
224
  const handle = async ({ event, resolve: originalResolve }) => {
240
- const translations = callbacks.useTranslations ? getTranslationLifecycle(callbacks.useTranslations()) : undefined;
225
+ const translations = callbacks.useTranslations ? getTranslationManagerLifecycle(callbacks.useTranslations) : undefined;
241
226
  const resolve = translations
242
227
  ? (event, options) => originalResolve(event, {
243
228
  ...options,
@@ -285,7 +270,7 @@ export const createApp = (callback) => {
285
270
  flags.boot = true;
286
271
  const state = hydratable('@azure-net/kit:initial-state:v1', () => (BROWSER ? {} : RequestContext.current().state));
287
272
  if (callbacks.useTranslations)
288
- getTranslationLifecycle(callbacks.useTranslations()).syncFromState(state);
273
+ getTranslationManagerLifecycle(callbacks.useTranslations).syncFromState(state);
289
274
  const result = callbacks.useBoot?.({ state });
290
275
  if (result && typeof result.then === 'function') {
291
276
  void Promise.resolve(result).catch(() => undefined);
@@ -1,5 +1,6 @@
1
1
  export { createApp, type AppAzureNetKitErrorCallback, type AppAzureNetKitErrorContext, type AppClientErrorCallback, type AppClientErrorContext, type AppClientInitLifecycleCallback, type AppClientInitLifecycleContext, type AppClientLifecycleCallback, type AppClientLifecycleContext, type AppRegistrar, type AppServerErrorCallback, type AppServerErrorContext, type AppServerFetchCallback, type AppServerFetchContext, type AppServerInitLifecycleCallback, type AppServerInitLifecycleContext, type AppServerLifecycleCallback, type AppServerLifecycleContext, type AppRerouteCallback, type AppUniversalLifecycleCallback, type AppUniversalLifecycleContext, type CreateAppInstance } from './App.js';
2
- export type { IClientMiddleware } from './middleware/ClientMiddleware.js';
3
- export type { IServerMiddleware } from './middleware/ServerMiddleware.js';
2
+ export type { IClientMiddleware, ClientMiddlewareContext, ClientMiddlewareClass } from './middleware/ClientMiddleware.js';
3
+ export type { IServerMiddleware, ServerMiddlewareContext, ServerMiddlewareClass } from './middleware/ServerMiddleware.js';
4
+ export type { IMiddleware, MiddlewareClass } from './middleware/Types.js';
4
5
  export type { EnsureRoute, EnsureRouteSource, EnsureRouteTarget } from './middleware/Shared.js';
5
6
  export type { AppBootContext, AppBootCallback } from './App.js';
@@ -1,10 +1,16 @@
1
1
  import { type EnsureRoute } from './Shared.js';
2
- import type { RedirectStatus } from '../../../svelte/redirect/index.js';
3
- import type { BeforeNavigate } from '$app/navigation';
4
- export type IClientMiddleware = (middlewareData: {
5
- to: URL;
6
- from?: URL;
7
- next: (location?: string | URL, status?: RedirectStatus) => undefined;
2
+ import type { BeforeNavigate, GotoOptions } from '$app/navigation';
3
+ import type { ReadonlyURL } from '$app/state';
4
+ import type { IMiddleware, MiddlewareClass } from './Types.js';
5
+ export interface ClientMiddlewareContext {
6
+ to: ReadonlyURL;
7
+ from?: ReadonlyURL;
8
+ navigation?: BeforeNavigate;
9
+ next: (location?: string | URL, options?: GotoOptions) => undefined;
8
10
  ensureRoute: EnsureRoute;
9
- }) => undefined;
10
- export declare const executeClientMiddlewares: (middlewares: IClientMiddleware[], navigation?: BeforeNavigate) => void;
11
+ }
12
+ export interface IClientMiddleware extends IMiddleware<ClientMiddlewareContext, undefined> {
13
+ execute(context: ClientMiddlewareContext): undefined;
14
+ }
15
+ export type ClientMiddlewareClass = MiddlewareClass<IClientMiddleware>;
16
+ export declare const executeClientMiddlewares: (middlewares: readonly ClientMiddlewareClass[], navigation?: BeforeNavigate) => void;
@@ -3,24 +3,46 @@ import { page } from '$app/state';
3
3
  import { goto } from '$app/navigation';
4
4
  export const executeClientMiddlewares = (middlewares, navigation) => {
5
5
  const from = navigation?.from?.url ?? undefined;
6
- const to = navigation?.to?.url ?? page?.url;
6
+ const to = navigation?.to?.url ?? page.url;
7
7
  for (const middleware of middlewares) {
8
8
  let shouldContinue = false;
9
9
  let redirectTo;
10
- const next = (location) => {
10
+ let redirectOptions;
11
+ let active = true;
12
+ const next = (location, options) => {
13
+ if (!active)
14
+ throw new Error('[Middleware] next() must be called synchronously and only once.');
15
+ active = false;
11
16
  shouldContinue = true;
12
17
  redirectTo = location;
18
+ redirectOptions = options;
13
19
  return undefined;
14
20
  };
15
- middleware({
16
- to,
17
- from,
18
- next,
19
- ensureRoute
20
- });
21
- if (redirectTo) {
21
+ let result;
22
+ try {
23
+ result = new middleware().execute({
24
+ to,
25
+ from,
26
+ next,
27
+ ensureRoute,
28
+ navigation
29
+ });
30
+ }
31
+ catch (error) {
32
+ navigation?.cancel();
33
+ throw error;
34
+ }
35
+ finally {
36
+ active = false;
37
+ }
38
+ if (result && typeof result.then === 'function') {
39
+ navigation?.cancel();
40
+ void Promise.resolve(result).catch(() => undefined);
41
+ throw new Error('[Middleware] Client execute() must be synchronous.');
42
+ }
43
+ if (redirectTo !== undefined) {
22
44
  navigation?.cancel();
23
- void goto(redirectTo);
45
+ void goto(redirectTo, redirectOptions).catch((error) => console.error('[Middleware] Redirect failed:', error));
24
46
  return;
25
47
  }
26
48
  if (!shouldContinue) {
@@ -1,11 +1,16 @@
1
1
  import { type RequestEvent } from '@sveltejs/kit';
2
2
  import { type EnsureRoute } from './Shared.js';
3
3
  import type { RedirectStatus } from '../../../svelte/redirect/index.js';
4
- export type IServerMiddleware = (middlewareData: {
4
+ import type { IMiddleware, MiddlewareClass } from './Types.js';
5
+ export interface ServerMiddlewareContext {
5
6
  to: RequestEvent['url'];
6
7
  from?: RequestEvent['url'];
7
8
  next: (location?: string | URL, status?: RedirectStatus) => void;
8
- event?: RequestEvent;
9
+ event: RequestEvent;
9
10
  ensureRoute: EnsureRoute;
10
- }) => Promise<void> | void;
11
- export declare const executeServerMiddlewares: (middlewares: IServerMiddleware[]) => Promise<void>;
11
+ }
12
+ export interface IServerMiddleware extends IMiddleware<ServerMiddlewareContext, Promise<void> | void> {
13
+ execute(context: ServerMiddlewareContext): Promise<void> | void;
14
+ }
15
+ export type ServerMiddlewareClass = MiddlewareClass<IServerMiddleware>;
16
+ export declare const executeServerMiddlewares: (middlewares: readonly ServerMiddlewareClass[]) => Promise<void>;
@@ -7,7 +7,7 @@ export const executeServerMiddlewares = async (middlewares) => {
7
7
  const event = RequestContext.current().event;
8
8
  if (event) {
9
9
  const referer = event?.request.headers.get('referer');
10
- const from = referer ? new URL(referer) : undefined;
10
+ const from = referer && URL.canParse(referer) ? new URL(referer) : undefined;
11
11
  const to = event?.url;
12
12
  for (const middleware of middlewares) {
13
13
  let shouldContinue = false;
@@ -17,7 +17,7 @@ export const executeServerMiddlewares = async (middlewares) => {
17
17
  return redirect(status, location);
18
18
  }
19
19
  };
20
- await middleware({
20
+ await new middleware().execute({
21
21
  event,
22
22
  to,
23
23
  from,
@@ -0,0 +1,4 @@
1
+ export interface IMiddleware<TContext, TResult = void> {
2
+ execute(context: TContext): TResult;
3
+ }
4
+ export type MiddlewareClass<T> = new () => T;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import { type ProviderSettings, type ProviderWithType } from './Provider.js';
2
+ type Services = Record<string, () => unknown>;
3
+ type Dependency = ProviderWithType<Services> | {
4
+ readonly resolve: ProviderWithType<Services>;
5
+ };
6
+ type Dependencies = Record<string, Dependency>;
7
+ type Providers<D extends Dependencies> = {
8
+ [K in keyof D]: D[K] extends {
9
+ readonly resolve: infer P extends ProviderWithType<Services>;
10
+ } ? P : D[K] extends ProviderWithType<Services> ? D[K] : never;
11
+ };
12
+ export type CompositionRootSettings<T extends Services, D extends Dependencies = Record<never, never>> = Omit<ProviderSettings<T, Providers<D>>, 'dependsOn'> & {
13
+ dependsOn?: D;
14
+ };
15
+ /** Holds registration metadata only. Resolving always uses the current request's container. */
16
+ export declare class CompositionRoot<T extends Services, D extends Dependencies = Record<never, never>> {
17
+ readonly name: symbol;
18
+ readonly resolve: ProviderWithType<T>;
19
+ constructor(settings: CompositionRootSettings<T, D>);
20
+ }
21
+ export {};
@@ -0,0 +1,11 @@
1
+ import { CreateServiceContainer } from './Provider.js';
2
+ const toProvider = (dependency) => typeof dependency === 'function' ? dependency : dependency.resolve;
3
+ /** Holds registration metadata only. Resolving always uses the current request's container. */
4
+ export class CompositionRoot {
5
+ name = Symbol('@azure-net/kit:CompositionRoot');
6
+ resolve;
7
+ constructor(settings) {
8
+ const dependsOn = Object.fromEntries(Object.entries(settings.dependsOn ?? {}).map(([key, dependency]) => [key, toProvider(dependency)]));
9
+ this.resolve = CreateServiceContainer(this.name, { ...settings, dependsOn });
10
+ }
11
+ }
@@ -1,3 +1,4 @@
1
+ export type ProviderKey = string | symbol;
1
2
  type ServiceFactory<T> = () => T;
2
3
  type ServiceMap = Record<string, ServiceFactory<unknown>>;
3
4
  type ResolvedServices<T extends ServiceMap> = {
@@ -16,6 +17,6 @@ export interface ProviderSettings<T extends ServiceMap, D extends Record<string,
16
17
  boot?: (services: ResolvedServices<T>) => undefined;
17
18
  register: ProviderFactory<T, D>;
18
19
  }
19
- export declare const CreateServiceContainer: <T extends ServiceMap, D extends Record<string, ProviderWithType<ServiceMap>>>(name: string, settings: ProviderSettings<T, D>) => ProviderWithType<T>;
20
- export declare function cleanupProvider(name: string): Promise<void>;
20
+ export declare const CreateServiceContainer: <T extends ServiceMap, D extends Record<string, ProviderWithType<ServiceMap>>>(name: ProviderKey, settings: ProviderSettings<T, D>) => ProviderWithType<T>;
21
+ export declare function cleanupProvider(name: ProviderKey): Promise<void>;
21
22
  export {};
@@ -67,19 +67,20 @@ const getConstructionStack = () => {
67
67
  const context = RequestContext.current();
68
68
  return (getContextInternals(context).boundaryProviderConstructionStack ??= []);
69
69
  };
70
- const runWithCycleGuard = (key, callback) => {
70
+ const runWithCycleGuard = (provider, member, callback) => {
71
71
  const stack = getConstructionStack();
72
- if (stack.includes(key)) {
73
- const cycleStart = stack.indexOf(key);
74
- const cyclePath = [...stack.slice(cycleStart), key].join(' -> ');
72
+ const cycleStart = stack.findIndex((entry) => entry.provider === provider && entry.member === member);
73
+ const entry = { provider, member };
74
+ if (cycleStart !== -1) {
75
+ const cyclePath = [...stack.slice(cycleStart), entry].map(({ provider, member }) => `${String(provider)}.${member}`).join(' -> ');
75
76
  throw new AzureNetKitInternalError(`[BoundaryProvider] Circular provider dependency detected: ${cyclePath}`);
76
77
  }
77
- stack.push(key);
78
+ stack.push(entry);
78
79
  try {
79
80
  return callback();
80
81
  }
81
82
  finally {
82
- const idx = stack.lastIndexOf(key);
83
+ const idx = stack.lastIndexOf(entry);
83
84
  if (idx !== -1)
84
85
  stack.splice(idx, 1);
85
86
  }
@@ -109,7 +110,7 @@ export const CreateServiceContainer = (name, settings) => {
109
110
  }
110
111
  });
111
112
  }
112
- factories = runWithCycleGuard(`${name}.register`, () => register(deps));
113
+ factories = runWithCycleGuard(name, 'register', () => register(deps));
113
114
  return factories;
114
115
  };
115
116
  const providerProxy = new Proxy({}, {
@@ -125,15 +126,15 @@ export const CreateServiceContainer = (name, settings) => {
125
126
  return undefined;
126
127
  if (Reflect.has(target, key))
127
128
  return Reflect.get(target, key, receiver);
128
- throw new AzureNetKitInternalError(`[BoundaryProvider] Service '${key}' not found in provider '${name}'`);
129
+ throw new AzureNetKitInternalError(`[BoundaryProvider] Service '${key}' not found in provider '${String(name)}'`);
129
130
  }
130
131
  const factory = factories[key];
131
132
  if (typeof factory !== 'function') {
132
133
  throw new AzureNetKitInternalError('[BoundaryProvider] Factory must be a function');
133
134
  }
134
- const instance = runWithCycleGuard(`${name}.${key}`, () => factory());
135
+ const instance = runWithCycleGuard(name, key, () => factory());
135
136
  if (instance && typeof instance?.then === 'function') {
136
- throw new AzureNetKitInternalError(`[BoundaryProvider] Service '${key}' in provider '${name}' returned Promise.`);
137
+ throw new AzureNetKitInternalError(`[BoundaryProvider] Service '${key}' in provider '${String(name)}' returned Promise.`);
137
138
  }
138
139
  cache.set(key, instance);
139
140
  return instance;
@@ -172,7 +173,7 @@ export const CreateServiceContainer = (name, settings) => {
172
173
  const bootResult = boot(providerProxy);
173
174
  if (bootResult && typeof bootResult.then === 'function') {
174
175
  void Promise.resolve(bootResult).catch(() => undefined);
175
- throw new AzureNetKitInternalError(`[BoundaryProvider] Boot for provider '${name}' must be synchronous.`);
176
+ throw new AzureNetKitInternalError(`[BoundaryProvider] Boot for provider '${String(name)}' must be synchronous.`);
176
177
  }
177
178
  setBootFlag(name, true);
178
179
  }
@@ -192,10 +193,10 @@ export async function cleanupProvider(name) {
192
193
  if (service && typeof service === 'object' && 'dispose' in service) {
193
194
  try {
194
195
  const disposeResult = service.dispose();
195
- promises.push(Promise.resolve(disposeResult).catch((err) => console.error(`Error disposing ${name}.${key}:`, err)));
196
+ promises.push(Promise.resolve(disposeResult).catch((err) => console.error(`Error disposing ${String(name)}.${key}:`, err)));
196
197
  }
197
198
  catch (err) {
198
- console.error(`Error disposing ${name}.${key}:`, err);
199
+ console.error(`Error disposing ${String(name)}.${key}:`, err);
199
200
  }
200
201
  }
201
202
  }
@@ -1 +1,2 @@
1
1
  export * from './Provider.js';
2
+ export { CompositionRoot, type CompositionRootSettings } from './CompositionRoot.js';
@@ -1 +1,2 @@
1
1
  export * from './Provider.js';
2
+ export { CompositionRoot } from './CompositionRoot.js';
@@ -1,6 +1,7 @@
1
1
  export { UniversalCookie, UniversalCookieInstance, UniversalNamedCookieInstance, type CookieOptions } from './cookie/index.js';
2
2
  export { BackgroundTask } from './background-task/index.js';
3
3
  export { ClassMirror } from './class-mirror/index.js';
4
- export { CreateServiceContainer, cleanupProvider, type ProviderSettings } from './boundary-provider/index.js';
4
+ export { CreateServiceContainer, cleanupProvider, type ProviderSettings, type ProviderKey } from './boundary-provider/index.js';
5
+ export { CompositionRoot, type CompositionRootSettings } from './boundary-provider/index.js';
5
6
  export * from './app/index.js';
6
7
  export { useLogger, LoggerErrors, type ILoggerError, type ILoggerSettings } from './logger/index.js';
@@ -2,5 +2,6 @@ export { UniversalCookie, UniversalCookieInstance, UniversalNamedCookieInstance
2
2
  export { BackgroundTask } from './background-task/index.js';
3
3
  export { ClassMirror } from './class-mirror/index.js';
4
4
  export { CreateServiceContainer, cleanupProvider } from './boundary-provider/index.js';
5
+ export { CompositionRoot } from './boundary-provider/index.js';
5
6
  export * from './app/index.js';
6
7
  export { useLogger, LoggerErrors } from './logger/index.js';
@@ -1,7 +1,6 @@
1
1
  export type AsyncStatus = 'idle' | 'pending' | 'success' | 'error';
2
2
  export type AsyncSignalSource = 'auto' | 'manual' | 'global';
3
- export interface AsyncSignalOptions<TData, TError = Error> {
4
- server?: boolean;
3
+ interface AsyncSignalBaseOptions<TData, TError = Error> {
5
4
  immediate?: boolean;
6
5
  initialData?: TData | (() => TData);
7
6
  beforeSend?: (meta: {
@@ -12,6 +11,15 @@ export interface AsyncSignalOptions<TData, TError = Error> {
12
11
  onError?: (error: TError) => void | Promise<void>;
13
12
  key?: string;
14
13
  }
14
+ export type AsyncSignalOptions<TData, TError = Error> = AsyncSignalBaseOptions<TData, TError> & ({
15
+ server: true;
16
+ key: string;
17
+ } | {
18
+ server?: false;
19
+ });
20
+ type AsyncSignalBatchSignalOptions<TData, TError> = Omit<AsyncSignalBaseOptions<TData, TError>, 'immediate'> & {
21
+ server?: false;
22
+ };
15
23
  export interface AsyncSignalSvelte<TData, TError = Error> {
16
24
  response?: TData;
17
25
  error?: TError;
@@ -27,13 +35,21 @@ export interface AsyncSignalBatchOptions {
27
35
  parallel?: boolean;
28
36
  }
29
37
  export interface AsyncSignalBatchFactory {
30
- <TData, TError = Error>(handler: (signal?: AbortSignal) => Promise<TData>, options?: Omit<AsyncSignalOptions<TData, TError>, 'immediate'>): AsyncSignalSvelte<TData, TError>;
38
+ <TData, TError = Error>(handler: (signal?: AbortSignal) => Promise<TData>, options?: AsyncSignalBatchSignalOptions<TData, TError>): AsyncSignalSvelte<TData, TError>;
31
39
  }
32
40
  export interface AsyncSignalBatch<TSignals extends readonly AsyncSignalSvelte<unknown, unknown>[]> {
33
41
  execute: () => TSignals;
34
42
  }
35
43
  export declare const isAsyncSignalScheduledForClient: (signal: AsyncSignalSvelte<unknown, unknown>) => boolean;
36
- export declare const createAsyncSignal: <TData, TError = Error>(handler: (signal?: AbortSignal) => Promise<TData>, options?: AsyncSignalOptions<TData, TError>) => AsyncSignalSvelte<TData, TError>;
44
+ export declare function createAsyncSignal<TData, TError = Error>(handler: (signal?: AbortSignal) => Promise<TData>, options: AsyncSignalBaseOptions<TData, TError> & {
45
+ server: true;
46
+ key: string;
47
+ }): Promise<AsyncSignalSvelte<TData, TError>>;
48
+ export declare function createAsyncSignal<TData, TError = Error>(handler: (signal?: AbortSignal) => Promise<TData>, options?: AsyncSignalBaseOptions<TData, TError> & {
49
+ server?: false;
50
+ }): AsyncSignalSvelte<TData, TError>;
51
+ export declare function createAsyncSignal<TData, TError = Error>(handler: (signal?: AbortSignal) => Promise<TData>, options: AsyncSignalOptions<TData, TError>): AsyncSignalSvelte<TData, TError> | Promise<AsyncSignalSvelte<TData, TError>>;
37
52
  export declare const createAsyncSignalBatch: <const TSignals extends readonly AsyncSignalSvelte<unknown, unknown>[]>(factory: (createSignal: AsyncSignalBatchFactory) => TSignals, options?: AsyncSignalBatchOptions) => AsyncSignalBatch<TSignals>;
38
53
  export declare const refreshAsyncSignal: (key: string) => Promise<void>;
39
54
  export declare const refreshAllAsyncSignals: () => Promise<void>;
55
+ export {};
@@ -1,4 +1,4 @@
1
- import { untrack } from 'svelte';
1
+ import { hydratable, untrack } from 'svelte';
2
2
  import { BROWSER } from '../../external/tools/index.js';
3
3
  const ASYNC_SIGNAL_BATCH_INTERNAL = Symbol('async-signal-batch-internal');
4
4
  const ASYNC_SIGNAL_SCHEDULED_FOR_CLIENT = Symbol('async-signal-scheduled-for-client');
@@ -60,7 +60,14 @@ const createAsyncSignalManager = () => {
60
60
  return { refreshAll, refreshByKey, generateKey, register, unregister };
61
61
  };
62
62
  const asyncSignalManager = createAsyncSignalManager();
63
- export const createAsyncSignal = (handler, options = {}) => {
63
+ export function createAsyncSignal(handler, options = {}) {
64
+ if (options.server && (typeof options.key !== 'string' || !options.key.trim())) {
65
+ throw new Error('[AsyncSignal] server: true requires a stable, non-empty key.');
66
+ }
67
+ const signal = createAsyncSignalInstance(handler, options, options.server && options.immediate !== false ? options.key : undefined);
68
+ return options.server ? signal.ready.then(() => signal) : signal;
69
+ }
70
+ const createAsyncSignalInstance = (handler, options = {}, hydrationKey) => {
64
71
  const { server = false, immediate = true, initialData = undefined, key } = options;
65
72
  const resolvedInitialData = typeof initialData === 'function' ? initialData() : initialData;
66
73
  let scheduledForClient = immediate && !BROWSER && !server;
@@ -105,15 +112,43 @@ export const createAsyncSignal = (handler, options = {}) => {
105
112
  status = 'pending';
106
113
  error = undefined;
107
114
  try {
108
- if (options.beforeSend) {
109
- const beforeSendResult = options.beforeSend({ initial, source });
110
- if (beforeSendResult && typeof beforeSendResult.then === 'function')
111
- await beforeSendResult;
115
+ const request = async () => {
116
+ if (options.beforeSend) {
117
+ const beforeSendResult = options.beforeSend({ initial, source });
118
+ if (beforeSendResult && typeof beforeSendResult.then === 'function')
119
+ await beforeSendResult;
120
+ }
121
+ if (runId !== currentRunId || abortController !== localController || localController.signal.aborted) {
122
+ return undefined;
123
+ }
124
+ return handler(localController.signal);
125
+ };
126
+ let result;
127
+ if (initial && hydrationKey !== undefined) {
128
+ let originalError;
129
+ // Call before yielding: hydratable needs the component's active render context.
130
+ const outcome = await hydratable(`@azure-net/kit/async-signal:${hydrationKey}`, async () => {
131
+ try {
132
+ return { ok: true, data: await request() };
133
+ }
134
+ catch (error) {
135
+ originalError = { value: error };
136
+ // Error instances are not devalue-serializable; do not expose server stacks.
137
+ return error instanceof Error
138
+ ? { ok: false, native: true, error: { name: error.name, message: error.message } }
139
+ : { ok: false, native: false, error };
140
+ }
141
+ });
142
+ if (!outcome.ok) {
143
+ if (originalError)
144
+ throw originalError.value;
145
+ throw outcome.native ? Object.assign(new Error(outcome.error.message), { name: outcome.error.name }) : outcome.error;
146
+ }
147
+ result = outcome.data;
112
148
  }
113
- if (runId !== currentRunId || abortController !== localController || localController.signal.aborted) {
114
- return undefined;
149
+ else {
150
+ result = await request();
115
151
  }
116
- const result = await handler(localController.signal);
117
152
  if (runId !== currentRunId || abortController !== localController || localController.signal.aborted) {
118
153
  return undefined;
119
154
  }
@@ -183,21 +218,26 @@ export const createAsyncSignal = (handler, options = {}) => {
183
218
  if (BROWSER) {
184
219
  const signalKey = key ?? asyncSignalManager.generateKey();
185
220
  const callback = (source) => startWhenUnlocked(source);
186
- asyncSignalManager.register(signalKey, callback);
221
+ if (hydrationKey === undefined)
222
+ asyncSignalManager.register(signalKey, callback);
187
223
  $effect(() => {
224
+ // Async component initialization may be abandoned before this effect runs.
225
+ if (hydrationKey !== undefined)
226
+ asyncSignalManager.register(signalKey, callback);
188
227
  return () => {
189
228
  asyncSignalManager.unregister(signalKey, callback);
190
229
  };
191
230
  });
192
231
  }
193
232
  if (immediate) {
233
+ // Automatic runs have no caller to catch a rejected onError callback.
194
234
  if (!BROWSER && server) {
195
235
  untrack(() => {
196
- void start('auto');
236
+ void start('auto').catch(() => undefined);
197
237
  });
198
238
  }
199
239
  else if (BROWSER) {
200
- void start('auto');
240
+ void start('auto').catch(() => undefined);
201
241
  }
202
242
  }
203
243
  const asyncSignal = {
@@ -272,7 +312,10 @@ export const createAsyncSignal = (handler, options = {}) => {
272
312
  export const createAsyncSignalBatch = (factory, options = {}) => {
273
313
  const internals = new Map();
274
314
  const createSignal = (handler, signalOptions = {}) => {
275
- const signal = createAsyncSignal(handler, { ...signalOptions, immediate: false });
315
+ if (signalOptions.server) {
316
+ throw new Error('[AsyncSignalBatch] server: true is not supported. Await createAsyncSignal during component initialization instead.');
317
+ }
318
+ const signal = createAsyncSignalInstance(handler, { ...signalOptions, immediate: false });
276
319
  const internal = signal[ASYNC_SIGNAL_BATCH_INTERNAL];
277
320
  internal.lock();
278
321
  internals.set(signal, internal);
@@ -0,0 +1,89 @@
1
+ # AsyncSignal server hydration
2
+
3
+ `createAsyncSignal(handler)` and `{ server: false }` return a signal synchronously,
4
+ with the existing client-only automatic execution behavior. With `{ server: true }`,
5
+ the return type is `Promise<AsyncSignalSvelte<T>>`; await it to obtain the settled
6
+ initial signal on both the server and the browser.
7
+
8
+ ```svelte
9
+ <script lang="ts">
10
+ import { createAsyncSignal } from '@azure-net/kit';
11
+
12
+ let { api } = $props<{
13
+ api: { getProfile(signal?: AbortSignal): Promise<{ name: string }> };
14
+ }>();
15
+
16
+ const profile = await createAsyncSignal((signal) => api.getProfile(signal), {
17
+ server: true,
18
+ key: 'profile:current-user'
19
+ });
20
+ </script>
21
+
22
+ <p>{profile.response?.name}</p>
23
+ <button onclick={() => profile.refresh()}>Refresh</button>
24
+ ```
25
+
26
+ Enable Svelte's `compilerOptions.experimental.async`. Call the factory during
27
+ component initialization, directly in the component script as above, or through a
28
+ synchronous helper called there. Do not first await inside an ordinary async helper,
29
+ or create a server-hydratable signal in a module singleton, event handler, `onMount`,
30
+ or `$effect`: Svelte needs the active component render context when `hydratable` is
31
+ called. The request handler itself may await normally. Supply a handler that can run
32
+ in both environments. In this example the parent supplies an API service created
33
+ for the current SSR request, and independently creates its browser counterpart.
34
+ Do not serialize the service through `load` data. Its server implementation must
35
+ capture the current request's fetch/auth context, or use an absolute trusted API
36
+ URL with explicitly scoped credentials. Global server `fetch('/api/profile')`
37
+ cannot resolve a relative URL, and `credentials: 'include'` does not automatically
38
+ forward the incoming user's cookies on the server. Forward only the credentials
39
+ needed by a trusted destination; never put them in the hydration payload.
40
+
41
+ The key must be non-empty, stable between SSR and hydration, and unique for each
42
+ logically distinct request in a render (include parameters when relevant). The
43
+ library namespaces it. Svelte owns the per-render payload; AsyncSignal does not add
44
+ a shared server request cache. This does not make module-global services, mutable
45
+ handler state, or publicly cached personalized HTML safe: the application must
46
+ keep those request-scoped or otherwise isolate users.
47
+ Data and non-Error rejection values must be serializable by
48
+ Svelte's `hydratable`/devalue. Native errors transfer their name and message, not
49
+ stack traces, subclasses, or custom properties. Do not return secrets or sensitive
50
+ error messages: hydratable payloads are sent to the browser.
51
+
52
+ Only the initial automatic request is hydratable. Hydration reuses its result
53
+ without invoking the handler or `beforeSend`; `onSuccess`/`onError` run for the
54
+ settled result in each environment, and `ready` waits for those callbacks. An
55
+ initial request failure yields a signal with `status: 'error'`, as before, rather
56
+ than rejecting the factory promise. Subsequent `execute`, `refresh`, and global
57
+ refreshes use normal request, cancellation, callback, and ready-cycle behavior.
58
+ `beforeSend` sees `initial: false` on a refresh after hydration.
59
+
60
+ If `onSuccess` throws, the signal enters the error state and invokes `onError`,
61
+ retaining the loaded response. If `onError` also throws, `ready` still settles;
62
+ explicit `execute`/`refresh` reject with that callback error, while automatic
63
+ initial loading consumes it to avoid an unhandled rejection. The signal's `error`
64
+ retains the original request or success-callback failure. Hydratable signals join
65
+ the browser global-refresh registry only when their component effect runs, so an
66
+ initialization abandoned before mounting leaves no registry entry. Unmounting is
67
+ not equivalent to calling `abort`: already-running handlers/callbacks may finish.
68
+
69
+ `initialData` (or its synchronous factory) initializes `response` independently in
70
+ each environment. It does not skip the automatic request or change an idle signal
71
+ to success. With `server: true` and the default `immediate: true`, the factory still
72
+ awaits initial loading; success replaces the fallback, while failure retains it.
73
+ Only the request outcome is transferred, not `initialData`, so use matching fallback
74
+ values on the server and browser to avoid hydration mismatches after a failure.
75
+
76
+ `immediate: false` disables initial loading and hydration, including in server mode:
77
+ `await createAsyncSignal(handler, { server: true, key: 'profile', immediate: false,
78
+ initialData })` returns an idle signal whose `response` and resolved `ready` contain
79
+ the initial value (or `undefined` if omitted). The key is still required. Later
80
+ explicit execution is ordinary, non-hydratable work.
81
+
82
+ Batch factories remain synchronous and deferred;
83
+ `createAsyncSignalBatch` explicitly rejects `server: true` in its factory at both
84
+ type-check and runtime. Use individually awaited server signals instead. Ordinary
85
+ batches preserve locks, parallel/sequential execution, cancellation, and ready gates.
86
+
87
+ Outside SvelteKit, include both `head` and `body` returned by `await render(...)` in
88
+ the document: Svelte places the hydration payload in `head`. Configure a CSP nonce
89
+ or hash through Svelte's render options if your application uses CSP.
@@ -1,13 +1,7 @@
1
+ import { type GotoOptions } from '$app/navigation';
1
2
  export type RedirectStatus = 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308;
2
3
  export interface IRedirectSettings {
3
- clientRedirectSettings?: {
4
- replaceState?: boolean | undefined;
5
- noScroll?: boolean | undefined;
6
- keepFocus?: boolean | undefined;
7
- invalidateAll?: boolean | undefined;
8
- invalidate?: (string | URL | ((url: URL) => boolean))[] | undefined;
9
- state?: App.PageState | undefined;
10
- };
4
+ clientRedirectSettings?: GotoOptions;
11
5
  serverRedirectSettings?: {
12
6
  status?: RedirectStatus;
13
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-net/kit",
3
- "version": "6.0.0-next.7",
3
+ "version": "6.0.0-next.9",
4
4
  "license": "MIT",
5
5
  "author": "Pixel1917",
6
6
  "description": "Universal toolkit and library collection, developed as part of the Azure-net ecosystem",
@@ -71,16 +71,16 @@
71
71
  },
72
72
  "peerDependencies": {
73
73
  "@sveltejs/kit": ">=3.0.0",
74
- "svelte": ">=5.55.4"
74
+ "svelte": ">=5.57.1"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@eslint/compat": "^2.0.5",
78
78
  "@eslint/js": "^10.0.1",
79
79
  "@playwright/test": "^1.59.1",
80
- "@sveltejs/adapter-auto": "^7.0.1",
80
+ "@sveltejs/adapter-auto": "^8.0.0",
81
81
  "@sveltejs/kit": "^3.0.0",
82
82
  "@sveltejs/load-config": "^0.2.2",
83
- "@sveltejs/package": "^2.5.7",
83
+ "@sveltejs/package": "^3.0.1",
84
84
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
85
85
  "@testing-library/jest-dom": "^6.9.1",
86
86
  "@testing-library/svelte": "^5.3.1",
@@ -98,7 +98,7 @@
98
98
  "svelte-check": "^4.7.6",
99
99
  "typescript": "^6.0.2",
100
100
  "typescript-eslint": "^8.58.2",
101
- "vite": "^8.0.8",
101
+ "vite": "^8.0.12",
102
102
  "vitest": "^4.1.4"
103
103
  },
104
104
  "keywords": [