@azure-net/kit 5.1.1 → 5.3.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.
@@ -7,10 +7,8 @@ export class BaseHttpDatasource {
7
7
  this.httpClient = params.http ?? createHttpServiceInstance();
8
8
  this.query = params.query ?? createQueryInstance();
9
9
  }
10
- async _createRawRequest(callback) {
11
- return await callback({ http: this.httpClient, query: this.query }).catch((err) => {
12
- throw err;
13
- });
10
+ _createRawRequest(callback) {
11
+ return callback({ http: this.httpClient, query: this.query });
14
12
  }
15
13
  createRawRequest = this._createRawRequest.bind(this);
16
14
  }
@@ -8,8 +8,19 @@ const normalizeSettings = (base, override) => {
8
8
  };
9
9
  };
10
10
  const encodeQuery = (entries) => entries.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&');
11
+ const stringifyQueryValue = (value) => {
12
+ try {
13
+ return JSON.stringify(value);
14
+ }
15
+ catch (error) {
16
+ const circular = error instanceof TypeError && /circular|cyclic/i.test(error.message);
17
+ throw new AzureNetKitInternalError(`[QueryInstance] Query value could not be serialized${circular ? ': circular reference detected' : ''}`);
18
+ }
19
+ };
11
20
  const encodeArrayEntries = (key, values, arrayFormat) => {
12
- const cleanValues = values.filter((v) => v !== null && v !== undefined).map((v) => String(v));
21
+ const cleanValues = values
22
+ .filter((value) => value !== null && value !== undefined)
23
+ .map((value) => (typeof value === 'object' && !(value instanceof Date) ? stringifyQueryValue(value) : String(value)));
13
24
  switch (arrayFormat) {
14
25
  case 'repeat':
15
26
  return cleanValues.map((v) => [key, v]);
@@ -23,26 +34,35 @@ const encodeArrayEntries = (key, values, arrayFormat) => {
23
34
  throw new AzureNetKitInternalError(`[QueryInstance] Unsupported array format: ${arrayFormat}`);
24
35
  }
25
36
  };
26
- const serializeEntries = (obj, arrayFormat, objectFormat, prefix = '') => {
37
+ const serializeEntries = (obj, arrayFormat, objectFormat, prefix = '', ancestors = new WeakSet()) => {
38
+ if (ancestors.has(obj)) {
39
+ throw new AzureNetKitInternalError('[QueryInstance] Query value could not be serialized: circular reference detected');
40
+ }
41
+ ancestors.add(obj);
27
42
  const entries = [];
28
- for (const [key, value] of Object.entries(obj)) {
29
- if (value === null || value === undefined)
30
- continue;
31
- const fullKey = prefix ? (objectFormat === 'nested-brackets' ? `${prefix}[${key}]` : `${prefix}.${key}`) : key;
32
- if (Array.isArray(value)) {
33
- entries.push(...encodeArrayEntries(fullKey, value, arrayFormat));
34
- continue;
35
- }
36
- if (typeof value === 'object' && !(value instanceof Date)) {
37
- if (objectFormat === 'nested-brackets') {
38
- entries.push(...serializeEntries(value, arrayFormat, objectFormat, fullKey));
43
+ try {
44
+ for (const [key, value] of Object.entries(obj)) {
45
+ if (value === null || value === undefined)
46
+ continue;
47
+ const fullKey = prefix ? (objectFormat === 'nested-brackets' ? `${prefix}[${key}]` : `${prefix}.${key}`) : key;
48
+ if (Array.isArray(value)) {
49
+ entries.push(...encodeArrayEntries(fullKey, value, arrayFormat));
50
+ continue;
39
51
  }
40
- else {
41
- entries.push([fullKey, JSON.stringify(value)]);
52
+ if (typeof value === 'object' && !(value instanceof Date)) {
53
+ if (objectFormat === 'nested-brackets') {
54
+ entries.push(...serializeEntries(value, arrayFormat, objectFormat, fullKey, ancestors));
55
+ }
56
+ else {
57
+ entries.push([fullKey, stringifyQueryValue(value)]);
58
+ }
59
+ continue;
42
60
  }
43
- continue;
61
+ entries.push([fullKey, String(value)]);
44
62
  }
45
- entries.push([fullKey, String(value)]);
63
+ }
64
+ finally {
65
+ ancestors.delete(obj);
46
66
  }
47
67
  return entries;
48
68
  };
@@ -37,7 +37,7 @@ export class ResponseBuilder {
37
37
  const keys = path.split('.');
38
38
  let result = this.state.data;
39
39
  for (const key of keys) {
40
- if (result && typeof result === 'object' && key in result) {
40
+ if (result && typeof result === 'object' && Object.hasOwn(result, key)) {
41
41
  result = result[key];
42
42
  }
43
43
  else {
@@ -20,10 +20,28 @@ const findStaticImportInsertPosition = (sourceCode) => {
20
20
  };
21
21
  const createVirtualServerHooks = (silentChromeDevtools) => `// ${GENERATED_MARKER}
22
22
  import { edgesHandle, edgesHandleRaw } from '${VIRTUAL_SERVER_UTILS_ID}';
23
+ import { RequestContext } from '@azure-net/kit/server-context';
23
24
 
24
25
  const getRegister = async () => (await import('${VIRTUAL_PROGRAM_ID}')).register;
25
26
 
27
+ const withRequestContext = (event, callback) => {
28
+ try {
29
+ RequestContext.current();
30
+ } catch {
31
+ return edgesHandleRaw(event, callback);
32
+ }
33
+
34
+ return callback();
35
+ };
36
+
26
37
  export const init = async () => (await getRegister()).serverInit();
38
+ export const handleFetch = (input) => withRequestContext(input.event, async () => (await getRegister()).serverFetch(input));
39
+ export const handleValidationError = (input) => withRequestContext(
40
+ input.event,
41
+ async () => (await getRegister()).serverValidationError(input)
42
+ );
43
+ export const reroute = async (input) => (await getRegister()).reroute(input);
44
+ export const getTransport = async () => (await getRegister()).transport;
27
45
 
28
46
  export const handle = edgesHandle(async ({ serialize, edgesEvent, resolve }) => {
29
47
  const register = await getRegister();
@@ -59,8 +77,26 @@ const createVirtualServerUtils = () => "export { edgesHandle, edgesHandleRaw } f
59
77
  const createVirtualClientHooks = () => `// ${GENERATED_MARKER}
60
78
  const getRegister = async () => (await import('${VIRTUAL_PROGRAM_ID}')).register;
61
79
 
62
- export const init = async () => (await getRegister()).clientInit();
80
+ export const transport = {};
81
+
82
+ const createCodecView = (method) => new Proxy({}, {
83
+ get: (_, key) => transport[key]?.[method],
84
+ ownKeys: () => Reflect.ownKeys(transport),
85
+ getOwnPropertyDescriptor: (_, key) => key in transport
86
+ ? { configurable: true, enumerable: true, value: transport[key][method] }
87
+ : undefined
88
+ });
89
+
90
+ export const decoders = createCodecView('decode');
91
+ export const encoders = createCodecView('encode');
92
+
93
+ export const init = async () => {
94
+ const register = await getRegister();
95
+ Object.assign(transport, register.transport);
96
+ return register.clientInit();
97
+ };
63
98
  export const handleError = async (input) => (await getRegister()).clientError(input);
99
+ export const reroute = async (input) => (await getRegister()).reroute(input);
64
100
  `;
65
101
  const createVirtualProgram = (root) => {
66
102
  const tsProgram = path.join(root, 'src', 'program.ts');
@@ -90,7 +126,8 @@ const hasUserHook = (root, hookName) => {
90
126
  const assertNoUserHooks = (root) => {
91
127
  const serverHook = hasUserHook(root, 'hooks.server');
92
128
  const clientHook = hasUserHook(root, 'hooks.client');
93
- const hook = serverHook ?? clientHook;
129
+ const universalHook = hasUserHook(root, 'hooks');
130
+ const hook = serverHook ?? clientHook ?? universalHook;
94
131
  if (!hook)
95
132
  return;
96
133
  throw new Error(`[AzureNetPlugin] ${path.relative(root, hook)} is not allowed when AzureNetPlugin is enabled. ` +
@@ -112,21 +149,16 @@ const transformGeneratedServerInternal = (sourceCode) => {
112
149
  if (!getHooksPattern.test(sourceCode)) {
113
150
  throw new Error('[AzureNetPlugin] Unsupported SvelteKit generated server internals: get_hooks() was not found.');
114
151
  }
115
- const universalImportMatch = sourceCode.match(/\(\{\s*reroute,\s*transport\s*\}\s*=\s*await\s*import\([^)]+\)\);/);
116
- const universalImport = universalImportMatch?.[0] ? `\n\t${universalImportMatch[0]}` : '';
117
152
  const importLine = `import * as __azureNetServerHooks from '${VIRTUAL_SERVER_HOOKS_ID}';`;
118
153
  const getHooksReplacement = `export async function get_hooks() {
119
- \tlet reroute;
120
- \tlet transport;${universalImport}
121
-
122
154
  \treturn {
123
155
  \t\thandle: __azureNetServerHooks.handle,
124
- \t\thandleFetch: undefined,
156
+ \t\thandleFetch: __azureNetServerHooks.handleFetch,
125
157
  \t\thandleError: __azureNetServerHooks.handleError,
126
- \t\thandleValidationError: undefined,
158
+ \t\thandleValidationError: __azureNetServerHooks.handleValidationError,
127
159
  \t\tinit: __azureNetServerHooks.init,
128
- \t\treroute,
129
- \t\ttransport
160
+ \t\treroute: __azureNetServerHooks.reroute,
161
+ \t\ttransport: await __azureNetServerHooks.getTransport()
130
162
  \t};
131
163
  }`;
132
164
  const transformed = ensureVirtualImport(sourceCode, `// ${GENERATED_MARKER}\n${importLine}`).replace(getHooksPattern, getHooksReplacement);
@@ -142,6 +174,11 @@ const transformGeneratedClientApp = (sourceCode) => {
142
174
  if (!handleErrorPattern.test(sourceCode)) {
143
175
  throw new Error('[AzureNetPlugin] Unsupported SvelteKit generated client app: default handleError was not found.');
144
176
  }
177
+ const decodersPattern = /export const decoders = Object\.fromEntries\(Object\.entries\(hooks\.transport\)\.map\(\(\[k, v\]\) => \[k, v\.decode\]\)\);/;
178
+ const encodersPattern = /export const encoders = Object\.fromEntries\(Object\.entries\(hooks\.transport\)\.map\(\(\[k, v\]\) => \[k, v\.encode\]\)\);/;
179
+ if (!decodersPattern.test(sourceCode) || !encodersPattern.test(sourceCode)) {
180
+ throw new Error('[AzureNetPlugin] Unsupported SvelteKit generated client app: transport codecs were not found.');
181
+ }
145
182
  let transformed = ensureVirtualImport(sourceCode, `// ${GENERATED_MARKER}\nimport * as __azureNetClientHooks from '${VIRTUAL_CLIENT_HOOKS_ID}';`);
146
183
  transformed = transformed.replace(handleErrorPattern, 'handleError: __azureNetClientHooks.handleError');
147
184
  if (/^\s*init:\s*[^,\n]+,?/m.test(transformed)) {
@@ -150,6 +187,10 @@ const transformGeneratedClientApp = (sourceCode) => {
150
187
  else {
151
188
  transformed = transformed.replace(/handleError:\s*__azureNetClientHooks\.handleError,?/m, 'handleError: __azureNetClientHooks.handleError,\n\tinit: __azureNetClientHooks.init,');
152
189
  }
190
+ transformed = transformed.replace(/^\s*reroute:\s*[^,\n]+,?/m, '\treroute: __azureNetClientHooks.reroute,');
191
+ transformed = transformed.replace(/^\s*transport:\s*[^,\n]+,?/m, '\ttransport: __azureNetClientHooks.transport');
192
+ transformed = transformed.replace(decodersPattern, 'export const decoders = __azureNetClientHooks.decoders;');
193
+ transformed = transformed.replace(encodersPattern, 'export const encoders = __azureNetClientHooks.encoders;');
153
194
  return transformed;
154
195
  };
155
196
  export const AzureNetPlugin = (options) => {
@@ -1,6 +1,6 @@
1
1
  import { type ContextData } from '../../external/edges/ServerContext.js';
2
2
  import { type IServerMiddleware } from './middleware/ServerMiddleware.js';
3
- import type { Handle, HandleClientError, HandleServerError, RequestEvent, ServerInit } from '@sveltejs/kit';
3
+ import type { Handle, HandleClientError, HandleFetch, HandleServerError, HandleValidationError, RequestEvent, Reroute, ServerInit, Transport } from '@sveltejs/kit';
4
4
  import { type IClientMiddleware } from './middleware/ClientMiddleware.js';
5
5
  import { useLogger } from '../logger/index.js';
6
6
  type MaybePromise<T> = T | Promise<T>;
@@ -11,7 +11,7 @@ type AppDependencyFactories<TApp> = Record<string, AppDependencyFactory<TApp>>;
11
11
  type ResolvedDependencies<T> = {
12
12
  [K in keyof T]: T[K] extends (...args: never[]) => infer TReturn ? TReturn : never;
13
13
  };
14
- type BuilderFlags = Partial<Record<'use' | 'useClient' | 'useClientInit' | 'useClientError' | 'useServerInit' | 'useServer' | 'useServerError', true>>;
14
+ type BuilderFlags = Partial<Record<'use' | 'useClient' | 'useClientInit' | 'useClientError' | 'useServerInit' | 'useServer' | 'useServerError' | 'useServerFetch' | 'useServerValidationError' | 'useReroute' | 'useTransport', true>>;
15
15
  type WithFlag<TFlags extends BuilderFlags, TKey extends keyof BuilderFlags> = TFlags & Record<TKey, true>;
16
16
  type BuilderDependencies<TBuilder> = TBuilder extends {
17
17
  readonly __types__?: infer TDependencies;
@@ -55,6 +55,14 @@ export interface AppServerErrorContext<TDependencies extends Record<string, unkn
55
55
  message: string;
56
56
  useLogger: typeof useLogger;
57
57
  }
58
+ export type AppServerFetchContext<TDependencies extends Record<string, unknown>> = Parameters<HandleFetch>[0] & {
59
+ Container: AppContainer<TDependencies>;
60
+ requestContext: ContextData;
61
+ };
62
+ export type AppServerValidationErrorContext<TDependencies extends Record<string, unknown>> = Parameters<HandleValidationError>[0] & {
63
+ Container: AppContainer<TDependencies>;
64
+ requestContext: ContextData;
65
+ };
58
66
  export type AppUniversalLifecycleCallback<TDependencies extends Record<string, unknown>> = (context: AppUniversalLifecycleContext<TDependencies>) => MaybePromise<void>;
59
67
  export type AppClientLifecycleCallback<TDependencies extends Record<string, unknown>> = (context: AppClientLifecycleContext<TDependencies>) => MaybePromise<void>;
60
68
  export type AppClientInitLifecycleCallback<TDependencies extends Record<string, unknown>> = (context: AppClientInitLifecycleContext<TDependencies>) => MaybePromise<void>;
@@ -62,6 +70,9 @@ export type AppClientErrorCallback<TDependencies extends Record<string, unknown>
62
70
  export type AppServerInitLifecycleCallback = ServerInit;
63
71
  export type AppServerLifecycleCallback<TDependencies extends Record<string, unknown>> = (context: AppServerLifecycleContext<TDependencies>) => MaybePromise<Response | void>;
64
72
  export type AppServerErrorCallback<TDependencies extends Record<string, unknown>> = (context: AppServerErrorContext<TDependencies>) => ReturnType<HandleServerError>;
73
+ export type AppServerFetchCallback<TDependencies extends Record<string, unknown>> = (context: AppServerFetchContext<TDependencies>) => ReturnType<HandleFetch>;
74
+ export type AppServerValidationErrorCallback<TDependencies extends Record<string, unknown>> = (context: AppServerValidationErrorContext<TDependencies>) => ReturnType<HandleValidationError>;
75
+ export type AppRerouteCallback = Reroute;
65
76
  export type CreateAppBuilder<TDependencies extends Record<string, unknown>, TFlags extends BuilderFlags = object> = {
66
77
  readonly __types__?: TDependencies;
67
78
  dependencies<TNext extends AppDependencyFactories<AppContainer<TDependencies>>>(dependencies: TNext): CreateAppBuilder<TDependencies & ResolvedDependencies<TNext>, TFlags>;
@@ -79,6 +90,14 @@ export type CreateAppBuilder<TDependencies extends Record<string, unknown>, TFla
79
90
  useServer(callback: AppServerLifecycleCallback<TDependencies>): CreateAppBuilder<TDependencies, WithFlag<TFlags, 'useServer'>>;
80
91
  }> & OnceMethod<TFlags, 'useServerError', {
81
92
  useServerError(callback: AppServerErrorCallback<TDependencies>): CreateAppBuilder<TDependencies, WithFlag<TFlags, 'useServerError'>>;
93
+ }> & OnceMethod<TFlags, 'useServerFetch', {
94
+ useServerFetch(callback: AppServerFetchCallback<TDependencies>): CreateAppBuilder<TDependencies, WithFlag<TFlags, 'useServerFetch'>>;
95
+ }> & OnceMethod<TFlags, 'useServerValidationError', {
96
+ useServerValidationError(callback: AppServerValidationErrorCallback<TDependencies>): CreateAppBuilder<TDependencies, WithFlag<TFlags, 'useServerValidationError'>>;
97
+ }> & OnceMethod<TFlags, 'useReroute', {
98
+ useReroute(callback: AppRerouteCallback): CreateAppBuilder<TDependencies, WithFlag<TFlags, 'useReroute'>>;
99
+ }> & OnceMethod<TFlags, 'useTransport', {
100
+ useTransport(transport: Transport): CreateAppBuilder<TDependencies, WithFlag<TFlags, 'useTransport'>>;
82
101
  }>;
83
102
  export interface AppRegistrar {
84
103
  (): MaybePromise<void>;
@@ -86,7 +105,11 @@ export interface AppRegistrar {
86
105
  clientError: HandleClientError;
87
106
  serverInit: ServerInit;
88
107
  serverError: HandleServerError;
108
+ serverFetch: HandleFetch;
109
+ serverValidationError: HandleValidationError;
89
110
  handle: Handle;
111
+ reroute: Reroute;
112
+ transport: Transport;
90
113
  }
91
114
  export interface CreateAppInstance<TDependencies extends Record<string, unknown>> {
92
115
  Container: AppContainer<TDependencies>;
@@ -79,6 +79,22 @@ const createBuilder = (dependencies, callbacks) => {
79
79
  setCallback(callbacks, 'useServerError', callback);
80
80
  return builder;
81
81
  },
82
+ useServerFetch(callback) {
83
+ setCallback(callbacks, 'useServerFetch', callback);
84
+ return builder;
85
+ },
86
+ useServerValidationError(callback) {
87
+ setCallback(callbacks, 'useServerValidationError', callback);
88
+ return builder;
89
+ },
90
+ useReroute(callback) {
91
+ setCallback(callbacks, 'useReroute', callback);
92
+ return builder;
93
+ },
94
+ useTransport(transport) {
95
+ setCallback(callbacks, 'useTransport', transport);
96
+ return builder;
97
+ },
82
98
  dependencies(nextDependencies) {
83
99
  for (const [key, factory] of Object.entries(nextDependencies)) {
84
100
  dependencies.set(key, factory);
@@ -195,6 +211,27 @@ export const createApp = (callback, name = DEFAULT_APP_NAME) => {
195
211
  message
196
212
  });
197
213
  };
214
+ const runServerFetch = (input) => {
215
+ if (!callbacks.useServerFetch)
216
+ return input.fetch(input.request);
217
+ return callbacks.useServerFetch({
218
+ ...input,
219
+ Container,
220
+ requestContext: getServerRequestContext()
221
+ });
222
+ };
223
+ const runServerValidationError = (input) => {
224
+ if (!callbacks.useServerValidationError) {
225
+ console.error('Remote function schema validation failed:', input.issues);
226
+ return { message: 'Bad Request' };
227
+ }
228
+ return callbacks.useServerValidationError({
229
+ ...input,
230
+ Container,
231
+ requestContext: getServerRequestContext()
232
+ });
233
+ };
234
+ const reroute = (input) => callbacks.useReroute?.(input);
198
235
  const handle = async ({ event, resolve }) => {
199
236
  const requestContext = getServerRequestContext();
200
237
  const flags = getLifecycleFlags(appKey, requestContext);
@@ -227,7 +264,11 @@ export const createApp = (callback, name = DEFAULT_APP_NAME) => {
227
264
  clientError: runClientError,
228
265
  serverInit: runServerInit,
229
266
  serverError: runServerError,
230
- handle
267
+ serverFetch: runServerFetch,
268
+ serverValidationError: runServerValidationError,
269
+ handle,
270
+ reroute,
271
+ transport: callbacks.useTransport ?? {}
231
272
  });
232
273
  return {
233
274
  Container,
@@ -1,4 +1,4 @@
1
- export { createApp, type AppClientErrorCallback, type AppClientErrorContext, type AppClientInitLifecycleCallback, type AppClientInitLifecycleContext, type AppClientLifecycleCallback, type AppClientLifecycleContext, type AppContainer, type AppRegistrar, type AppServerErrorCallback, type AppServerErrorContext, type AppServerInitLifecycleCallback, type AppServerInitLifecycleContext, type AppServerLifecycleCallback, type AppServerLifecycleContext, type AppUniversalLifecycleCallback, type AppUniversalLifecycleContext, type CreateAppInstance } from './App.js';
1
+ export { createApp, type AppClientErrorCallback, type AppClientErrorContext, type AppClientInitLifecycleCallback, type AppClientInitLifecycleContext, type AppClientLifecycleCallback, type AppClientLifecycleContext, type AppContainer, type AppRegistrar, type AppServerErrorCallback, type AppServerErrorContext, type AppServerFetchCallback, type AppServerFetchContext, type AppServerInitLifecycleCallback, type AppServerInitLifecycleContext, type AppServerLifecycleCallback, type AppServerLifecycleContext, type AppServerValidationErrorCallback, type AppServerValidationErrorContext, type AppRerouteCallback, type AppUniversalLifecycleCallback, type AppUniversalLifecycleContext, type CreateAppInstance } from './App.js';
2
2
  export type { IClientMiddleware } from './middleware/ClientMiddleware.js';
3
3
  export type { IServerMiddleware } from './middleware/ServerMiddleware.js';
4
4
  export type { EnsureRoute, EnsureRouteSource, EnsureRouteTarget } from './middleware/Shared.js';
@@ -0,0 +1,16 @@
1
+ type MaybePromise<T> = T | PromiseLike<T>;
2
+ type WaitUntil = (promise: Promise<unknown>) => void;
3
+ type WaitUntilTarget = WaitUntil | {
4
+ waitUntil: WaitUntil;
5
+ };
6
+ export declare class BackgroundTask<T = void> implements Promise<T> {
7
+ #private;
8
+ readonly [Symbol.toStringTag] = "Promise";
9
+ private constructor();
10
+ static run<T = void>(task: () => MaybePromise<T>): BackgroundTask<T>;
11
+ waitUntil(target: WaitUntilTarget): this;
12
+ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
13
+ catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null): Promise<T | TResult>;
14
+ finally(onfinally?: (() => void) | null): Promise<T>;
15
+ }
16
+ export {};
@@ -0,0 +1,34 @@
1
+ export class BackgroundTask {
2
+ [Symbol.toStringTag] = 'Promise';
3
+ #promise;
4
+ constructor(task) {
5
+ try {
6
+ this.#promise = Promise.resolve(task());
7
+ }
8
+ catch (error) {
9
+ this.#promise = Promise.reject(error);
10
+ }
11
+ void this.#promise.catch(() => undefined);
12
+ }
13
+ static run(task) {
14
+ return new BackgroundTask(task);
15
+ }
16
+ waitUntil(target) {
17
+ if (typeof target === 'function') {
18
+ target(this.#promise);
19
+ }
20
+ else {
21
+ target.waitUntil(this.#promise);
22
+ }
23
+ return this;
24
+ }
25
+ then(onfulfilled, onrejected) {
26
+ return this.#promise.then(onfulfilled, onrejected);
27
+ }
28
+ catch(onrejected) {
29
+ return this.#promise.catch(onrejected);
30
+ }
31
+ finally(onfinally) {
32
+ return this.#promise.finally(onfinally);
33
+ }
34
+ }
@@ -0,0 +1 @@
1
+ export { BackgroundTask } from './BackgroundTask.js';
@@ -0,0 +1 @@
1
+ export { BackgroundTask } from './BackgroundTask.js';
@@ -17,5 +17,5 @@ export interface ProviderSettings<T extends ServiceMap, D extends Record<string,
17
17
  register: ProviderFactory<T, D>;
18
18
  }
19
19
  export declare const createBoundaryProvider: <T extends ServiceMap, D extends Record<string, ProviderWithType<ServiceMap>>>(name: string, settings: ProviderSettings<T, D>) => ProviderWithType<T>;
20
- export declare function cleanupProvider(name: string): void;
20
+ export declare function cleanupProvider(name: string): Promise<void>;
21
21
  export {};
@@ -152,14 +152,17 @@ export const createBoundaryProvider = (name, settings) => {
152
152
  };
153
153
  return providerFn;
154
154
  };
155
- export function cleanupProvider(name) {
155
+ export async function cleanupProvider(name) {
156
156
  const cleanupCache = async (cache) => {
157
157
  const promises = [];
158
158
  for (const [key, service] of cache.entries()) {
159
159
  if (service && typeof service === 'object' && 'dispose' in service) {
160
- const disposeResult = service.dispose();
161
- if (disposeResult instanceof Promise) {
162
- promises.push(disposeResult.catch((err) => console.error(`Error disposing ${name}.${key}:`, err)));
160
+ try {
161
+ const disposeResult = service.dispose();
162
+ promises.push(Promise.resolve(disposeResult).catch((err) => console.error(`Error disposing ${name}.${key}:`, err)));
163
+ }
164
+ catch (err) {
165
+ console.error(`Error disposing ${name}.${key}:`, err);
163
166
  }
164
167
  }
165
168
  }
@@ -168,18 +171,19 @@ export function cleanupProvider(name) {
168
171
  };
169
172
  if (BROWSER) {
170
173
  const cache = clientCache.get(name);
171
- if (cache) {
172
- void cleanupCache(cache);
173
- }
174
+ clientCache.delete(name);
174
175
  providerProxyCache.delete(name);
175
176
  clientBootFlags.delete(name);
177
+ if (cache)
178
+ await cleanupCache(cache);
176
179
  }
177
180
  else {
178
181
  const context = RequestContext.current();
179
182
  const providers = context.data.providers;
180
183
  if (providers?.has(name)) {
181
184
  const cache = providers.get(name);
182
- void cleanupCache(cache);
185
+ providers.delete(name);
186
+ await cleanupCache(cache);
183
187
  }
184
188
  }
185
189
  }
@@ -26,12 +26,6 @@ export class ClassMirror {
26
26
  },
27
27
  has(proxyTarget, prop) {
28
28
  return Reflect.has(proxyTarget, prop) || Reflect.has(proxyTarget[MIRROR_TARGET], prop);
29
- },
30
- ownKeys(proxyTarget) {
31
- return [...new Set([...Reflect.ownKeys(proxyTarget), ...Reflect.ownKeys(proxyTarget[MIRROR_TARGET])])];
32
- },
33
- getOwnPropertyDescriptor(proxyTarget, prop) {
34
- return Reflect.getOwnPropertyDescriptor(proxyTarget, prop) ?? Reflect.getOwnPropertyDescriptor(proxyTarget[MIRROR_TARGET], prop);
35
29
  }
36
30
  });
37
31
  }
@@ -9,12 +9,42 @@ export type CookieOptions = {
9
9
  priority?: 'low' | 'medium' | 'high' | undefined;
10
10
  partitioned?: boolean | undefined;
11
11
  };
12
+ /** Cookie operations with shared defaults and request-local resolution at call time. */
13
+ export declare class UniversalCookieInstance {
14
+ private readonly defaults;
15
+ constructor(options?: CookieOptions);
16
+ set<T>(name: string, value: T, options?: CookieOptions): void;
17
+ get<T = string>(name: string): T | undefined;
18
+ getAll<T = Record<string, unknown>>(): T | undefined;
19
+ has(name: string): boolean;
20
+ delete(name: string, options?: CookieOptions): void;
21
+ clear(options?: CookieOptions): void;
22
+ toCredentials(): string;
23
+ createNamedInstance<T = string>(name: string): UniversalNamedCookieInstance<T>;
24
+ }
25
+ /** Type-safe operations for one cookie name. */
26
+ export declare class UniversalNamedCookieInstance<T = string> {
27
+ readonly name: string;
28
+ private readonly cookies;
29
+ constructor(name: string, cookies: UniversalCookieInstance);
30
+ set(value: T, options?: CookieOptions): void;
31
+ get(): T | undefined;
32
+ has(): boolean;
33
+ delete(options?: CookieOptions): void;
34
+ clear(options?: CookieOptions): void;
35
+ }
12
36
  /**
13
37
  * Utility class for managing cookies in svelte ssr (browser and server).
14
38
  * Supports setting, getting, deleting, checking, and clearing cookies.
15
39
  * All methods are static and operate without creating instances.
16
40
  */
17
41
  export declare class UniversalCookie {
42
+ /** Creates reusable cookie operations with shared defaults. */
43
+ static createInstance(options?: CookieOptions): UniversalCookieInstance;
44
+ /** Creates reusable operations bound to one cookie name. */
45
+ static createNamedInstance<T = string>(name: string, options?: CookieOptions): UniversalNamedCookieInstance<T>;
46
+ private static serialize;
47
+ private static deserialize;
18
48
  /**
19
49
  * Sets a cookie with the specified key, value, and options.
20
50
  * Serializes non-string values as JSON.
@@ -1,11 +1,111 @@
1
1
  import { BROWSER, Cookies } from '../../external/tools/index.js';
2
2
  import { RequestContext } from '../../external/edges/ServerContext.js';
3
+ const cloneCookieOptions = (options) => ({
4
+ ...options,
5
+ expires: options.expires instanceof Date ? new Date(options.expires) : options.expires
6
+ });
7
+ const mergeCookieOptions = (defaults, overrides) => overrides ? { ...defaults, ...overrides } : defaults;
8
+ /** Cookie operations with shared defaults and request-local resolution at call time. */
9
+ export class UniversalCookieInstance {
10
+ defaults;
11
+ constructor(options = {}) {
12
+ this.defaults = cloneCookieOptions(options);
13
+ }
14
+ set(name, value, options) {
15
+ UniversalCookie.set(name, value, mergeCookieOptions(this.defaults, options));
16
+ }
17
+ get(name) {
18
+ return UniversalCookie.get(name);
19
+ }
20
+ getAll() {
21
+ return UniversalCookie.getAll();
22
+ }
23
+ has(name) {
24
+ return UniversalCookie.has(name);
25
+ }
26
+ delete(name, options) {
27
+ UniversalCookie.delete(name, mergeCookieOptions(this.defaults, options));
28
+ }
29
+ clear(options) {
30
+ UniversalCookie.clear(mergeCookieOptions(this.defaults, options));
31
+ }
32
+ toCredentials() {
33
+ return UniversalCookie.toCredentials();
34
+ }
35
+ createNamedInstance(name) {
36
+ return new UniversalNamedCookieInstance(name, this);
37
+ }
38
+ }
39
+ /** Type-safe operations for one cookie name. */
40
+ export class UniversalNamedCookieInstance {
41
+ name;
42
+ cookies;
43
+ constructor(name, cookies) {
44
+ if (!name)
45
+ throw new TypeError('Cookie name must not be empty.');
46
+ this.name = name;
47
+ this.cookies = cookies;
48
+ }
49
+ set(value, options) {
50
+ this.cookies.set(this.name, value, options);
51
+ }
52
+ get() {
53
+ return this.cookies.get(this.name);
54
+ }
55
+ has() {
56
+ return this.cookies.has(this.name);
57
+ }
58
+ delete(options) {
59
+ this.cookies.delete(this.name, options);
60
+ }
61
+ clear(options) {
62
+ this.delete(options);
63
+ }
64
+ }
3
65
  /**
4
66
  * Utility class for managing cookies in svelte ssr (browser and server).
5
67
  * Supports setting, getting, deleting, checking, and clearing cookies.
6
68
  * All methods are static and operate without creating instances.
7
69
  */
8
70
  export class UniversalCookie {
71
+ /** Creates reusable cookie operations with shared defaults. */
72
+ static createInstance(options = {}) {
73
+ return new UniversalCookieInstance(options);
74
+ }
75
+ /** Creates reusable operations bound to one cookie name. */
76
+ static createNamedInstance(name, options = {}) {
77
+ return this.createInstance(options).createNamedInstance(name);
78
+ }
79
+ static serialize(value) {
80
+ if (typeof value === 'string')
81
+ return value;
82
+ const serialized = JSON.stringify(value);
83
+ if (serialized === undefined)
84
+ throw new TypeError('Cookie value is not JSON-serializable.');
85
+ return serialized;
86
+ }
87
+ static deserialize(value, decodeLegacy = false) {
88
+ try {
89
+ return JSON.parse(value);
90
+ }
91
+ catch {
92
+ if (!decodeLegacy)
93
+ return value;
94
+ }
95
+ let decodedValue;
96
+ try {
97
+ decodedValue = decodeURIComponent(value);
98
+ }
99
+ catch {
100
+ return value;
101
+ }
102
+ try {
103
+ return JSON.parse(decodedValue);
104
+ }
105
+ catch {
106
+ return decodedValue;
107
+ }
108
+ }
9
109
  /**
10
110
  * Sets a cookie with the specified key, value, and options.
11
111
  * Serializes non-string values as JSON.
@@ -17,21 +117,14 @@ export class UniversalCookie {
17
117
  * @returns {void}
18
118
  */
19
119
  static set(name, value, options) {
120
+ const serializedValue = this.serialize(value);
20
121
  if (BROWSER) {
21
- Cookies.set(name, value, options);
122
+ Cookies.set(name, serializedValue, options);
22
123
  return;
23
124
  }
24
125
  else {
25
126
  const event = RequestContext.current().event;
26
127
  if (event) {
27
- const encodedKey = encodeURIComponent(name);
28
- let serializedValue;
29
- if (typeof value === 'string') {
30
- serializedValue = encodeURIComponent(value);
31
- }
32
- else {
33
- serializedValue = encodeURIComponent(JSON.stringify(value));
34
- }
35
128
  let expires;
36
129
  if (options?.expires !== undefined) {
37
130
  if (typeof options.expires === 'number') {
@@ -43,7 +136,7 @@ export class UniversalCookie {
43
136
  expires = options.expires;
44
137
  }
45
138
  }
46
- event.cookies.set(encodedKey, serializedValue, {
139
+ event.cookies.set(name, serializedValue, {
47
140
  ...options,
48
141
  path: options?.path ?? '/',
49
142
  httpOnly: options?.httpOnly ?? false,
@@ -64,22 +157,15 @@ export class UniversalCookie {
64
157
  */
65
158
  static get(name) {
66
159
  if (BROWSER) {
67
- return Cookies.get(name) ?? undefined;
160
+ const value = Cookies.get(name, { parse: false });
161
+ return value === null ? undefined : this.deserialize(value);
68
162
  }
69
163
  else {
70
164
  const event = RequestContext.current().event;
71
165
  if (event) {
72
- const encodedKey = encodeURIComponent(name);
73
- const cookieValue = event.cookies.get(encodedKey);
74
- if (cookieValue) {
75
- const decodedValue = decodeURIComponent(cookieValue);
76
- try {
77
- return JSON.parse(decodedValue);
78
- }
79
- catch {
80
- return decodedValue;
81
- }
82
- }
166
+ const cookieValue = event.cookies.get(name) ?? event.cookies.get(encodeURIComponent(name));
167
+ if (cookieValue !== undefined)
168
+ return this.deserialize(cookieValue, true);
83
169
  return undefined;
84
170
  }
85
171
  return undefined;
@@ -94,7 +180,8 @@ export class UniversalCookie {
94
180
  */
95
181
  static getAll() {
96
182
  if (BROWSER) {
97
- return Cookies.getAll();
183
+ const allCookies = Cookies.getAll({ parse: false });
184
+ return Object.fromEntries(Object.entries(allCookies).map(([name, value]) => [name, this.deserialize(String(value))]));
98
185
  }
99
186
  else {
100
187
  const event = RequestContext.current().event;
@@ -102,12 +189,7 @@ export class UniversalCookie {
102
189
  if (event) {
103
190
  const allCookies = event.cookies.getAll();
104
191
  for (const singleCookie of allCookies) {
105
- try {
106
- result[singleCookie.name] = JSON.parse(singleCookie.value);
107
- }
108
- catch {
109
- result[singleCookie.name] = singleCookie.value;
110
- }
192
+ result[singleCookie.name] = this.deserialize(singleCookie.value, true);
111
193
  }
112
194
  }
113
195
  return result;
@@ -154,7 +236,10 @@ export class UniversalCookie {
154
236
  static toCredentials() {
155
237
  const all = this.getAll() ?? {};
156
238
  return Object.entries(all)
157
- .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
239
+ .map(([key, value]) => {
240
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
241
+ return `${encodeURIComponent(key)}=${encodeURIComponent(serialized)}`;
242
+ })
158
243
  .join('; ');
159
244
  }
160
245
  }
@@ -1,2 +1,12 @@
1
1
  import { EventBus } from '../../external/tools/index.js';
2
- export declare const AppEvents: () => EventBus<any>;
2
+ declare global {
3
+ namespace App {
4
+ interface CustomEvents {
5
+ readonly __azureNetEvents__?: never;
6
+ }
7
+ }
8
+ }
9
+ export type AppEventsList = App.CustomEvents extends {
10
+ list: infer TEvents extends object;
11
+ } ? TEvents : Record<never, never>;
12
+ export declare const AppEvents: () => EventBus<AppEventsList>;
@@ -1,6 +1,3 @@
1
1
  import { EventBus } from '../../external/tools/index.js';
2
2
  import { createPresenter } from '../../external/edges/Edges.js';
3
- export const AppEvents = createPresenter(() => {
4
- const appEventBus = new EventBus({});
5
- return appEventBus;
6
- });
3
+ export const AppEvents = createPresenter(() => new EventBus({}));
@@ -1,7 +1,8 @@
1
- export { UniversalCookie, type CookieOptions } from './cookie/index.js';
1
+ export { UniversalCookie, UniversalCookieInstance, UniversalNamedCookieInstance, type CookieOptions } from './cookie/index.js';
2
+ export { BackgroundTask } from './background-task/index.js';
2
3
  export { AppEvents } from './event-bus/index.js';
3
4
  export { ClassMirror } from './class-mirror/index.js';
4
5
  export { createBoundaryProvider, cleanupProvider, type ProviderSettings } from './boundary-provider/index.js';
5
- export { createApp, type AppContainer, type CreateAppInstance, type IServerMiddleware, type IClientMiddleware } from './app/index.js';
6
+ export * from './app/index.js';
6
7
  export { useLogger, LoggerErrors, type ILoggerError, type ILoggerSettings } from './logger/index.js';
7
8
  export { useRedirect, type IRedirectSettings, type RedirectStatus } from './redirect/index.js';
@@ -1,7 +1,8 @@
1
- export { UniversalCookie } from './cookie/index.js';
1
+ export { UniversalCookie, UniversalCookieInstance, UniversalNamedCookieInstance } from './cookie/index.js';
2
+ export { BackgroundTask } from './background-task/index.js';
2
3
  export { AppEvents } from './event-bus/index.js';
3
4
  export { ClassMirror } from './class-mirror/index.js';
4
5
  export { createBoundaryProvider, cleanupProvider } from './boundary-provider/index.js';
5
- export { createApp } from './app/index.js';
6
+ export * from './app/index.js';
6
7
  export { useLogger, LoggerErrors } from './logger/index.js';
7
8
  export { useRedirect } from './redirect/index.js';
@@ -1,4 +1,5 @@
1
1
  import { AppError, type IAppError } from '../app-error/index.js';
2
+ import { BackgroundTask } from '../background-task/index.js';
2
3
  export declare enum LoggerErrors {
3
4
  AzureNetKitInternal = 0,
4
5
  AzureNetAppError = 1,
@@ -20,4 +21,4 @@ export interface ILoggerSettings {
20
21
  filterLog?: (error: ILoggerError) => boolean;
21
22
  includeOnly?: LoggerErrors[];
22
23
  }
23
- export declare const useLogger: (error: unknown, settings: ILoggerSettings) => void;
24
+ export declare const useLogger: (error: unknown, settings: ILoggerSettings) => BackgroundTask<void>;
@@ -1,6 +1,7 @@
1
1
  import { AppError, AzureNetKitInternalError } from '../app-error/index.js';
2
2
  import { BROWSER } from '../../external/tools/index.js';
3
3
  import { RequestContext } from '../../external/edges/ServerContext.js';
4
+ import { BackgroundTask } from '../background-task/index.js';
4
5
  export var LoggerErrors;
5
6
  (function (LoggerErrors) {
6
7
  LoggerErrors[LoggerErrors["AzureNetKitInternal"] = 0] = "AzureNetKitInternal";
@@ -50,24 +51,28 @@ const getFetcher = () => {
50
51
  export const useLogger = (error, settings) => {
51
52
  const preparedError = prepareError(error);
52
53
  if (settings.filterLog && settings.filterLog(preparedError)) {
53
- return;
54
+ return BackgroundTask.run(() => undefined);
54
55
  }
55
56
  if (settings.includeOnly && !settings.includeOnly.includes(preparedError.type)) {
56
- return;
57
+ return BackgroundTask.run(() => undefined);
57
58
  }
58
59
  const prefix = settings?.prefix ?? '[Logger]';
59
60
  const errorMessage = prefix + preparedError.message + (settings?.joinToLog ? settings.joinToLog(preparedError) : '');
60
61
  console.log(errorMessage);
61
- if (settings.collector) {
62
- const fetcher = getFetcher();
62
+ if (!settings.collector)
63
+ return BackgroundTask.run(() => undefined);
64
+ const { collector } = settings;
65
+ return BackgroundTask.run(async () => {
63
66
  try {
64
- void fetcher(settings.collector.request(preparedError)).catch((err) => {
65
- settings.collector?.onError?.(err);
66
- return;
67
- });
67
+ await getFetcher()(collector.request(preparedError));
68
68
  }
69
69
  catch (err) {
70
- settings.collector.onError?.(err);
70
+ try {
71
+ collector.onError?.(err);
72
+ }
73
+ catch {
74
+ return;
75
+ }
71
76
  }
72
- }
77
+ });
73
78
  };
@@ -1,10 +1,11 @@
1
1
  import { ObjectUtil } from '../../external/tools/index.js';
2
+ import { cloneStateValue } from '../shared/cloneStateValue.js';
2
3
  export const createActiveForm = (onSubmit, config) => {
3
4
  const isPromise = (value) => {
4
5
  return typeof value?.then === 'function';
5
6
  };
6
7
  let initial = {};
7
- let formData = $state(ObjectUtil.deepClone(initial));
8
+ let formData = $state(cloneStateValue(initial));
8
9
  let formErrors = $state({});
9
10
  let pending = $state(false);
10
11
  let submitRunId = 0;
@@ -15,18 +16,18 @@ export const createActiveForm = (onSubmit, config) => {
15
16
  const source = config?.initialData;
16
17
  if (!source) {
17
18
  initial = {};
18
- formData = ObjectUtil.deepClone(initial);
19
+ formData = cloneStateValue(initial);
19
20
  formErrors = {};
20
- return initial;
21
+ return cloneStateValue(initial);
21
22
  }
22
23
  const value = source();
23
24
  const nextInitial = isPromise(value) ? await value : value;
24
25
  if (runId !== initialLoadRunId)
25
- return initial;
26
- initial = (nextInitial ?? {});
27
- formData = ObjectUtil.deepClone(initial);
26
+ return cloneStateValue(initial);
27
+ initial = cloneStateValue((nextInitial ?? {}));
28
+ formData = cloneStateValue(initial);
28
29
  formErrors = {};
29
- return initial;
30
+ return cloneStateValue(initial);
30
31
  };
31
32
  const ready = loadInitialData();
32
33
  const abortedResponse = () => ({
@@ -45,7 +46,7 @@ export const createActiveForm = (onSubmit, config) => {
45
46
  formData = {};
46
47
  break;
47
48
  case 'initial':
48
- formData = ObjectUtil.deepClone(initial);
49
+ formData = cloneStateValue(initial);
49
50
  break;
50
51
  case 'reloadInitial':
51
52
  await loadInitialData();
@@ -70,7 +71,7 @@ export const createActiveForm = (onSubmit, config) => {
70
71
  if (aborted || runId !== submitRunId)
71
72
  return abortedResponse();
72
73
  }
73
- const result = await onSubmit($state.snapshot(formData));
74
+ const result = await onSubmit(cloneStateValue(formData));
74
75
  if (runId !== submitRunId)
75
76
  return result;
76
77
  if (result.success) {
@@ -124,6 +124,9 @@ export const createAsyncSignal = (handler, options = {}) => {
124
124
  }
125
125
  await start('manual');
126
126
  };
127
+ const refresh = async () => {
128
+ await start('manual');
129
+ };
127
130
  if (BROWSER) {
128
131
  const signalKey = key ?? asyncSignalManager.generateKey();
129
132
  const callback = (source) => start(source);
@@ -163,7 +166,7 @@ export const createAsyncSignal = (handler, options = {}) => {
163
166
  return start('auto');
164
167
  },
165
168
  execute,
166
- refresh: execute,
169
+ refresh,
167
170
  reset: () => {
168
171
  currentRunId += 1;
169
172
  abortController?.abort();
@@ -1,24 +1,55 @@
1
- import { ObjectUtil } from '../../external/tools/index.js';
2
1
  import { untrack } from 'svelte';
2
+ import { ObjectUtil } from '../../external/tools/index.js';
3
+ import { cloneStateValue } from '../shared/cloneStateValue.js';
4
+ const trackDeep = (value, seen = new WeakSet()) => {
5
+ if (value === null || typeof value !== 'object' || seen.has(value))
6
+ return;
7
+ seen.add(value);
8
+ if (Array.isArray(value)) {
9
+ for (const item of value)
10
+ trackDeep(item, seen);
11
+ return;
12
+ }
13
+ if (value instanceof Map) {
14
+ for (const [key, item] of value) {
15
+ trackDeep(key, seen);
16
+ trackDeep(item, seen);
17
+ }
18
+ return;
19
+ }
20
+ if (value instanceof Set) {
21
+ for (const item of value)
22
+ trackDeep(item, seen);
23
+ return;
24
+ }
25
+ const prototype = Object.getPrototypeOf(value);
26
+ if (prototype !== Object.prototype && prototype !== null)
27
+ return;
28
+ for (const key of Reflect.ownKeys(value)) {
29
+ trackDeep(Reflect.get(value, key), seen);
30
+ }
31
+ };
3
32
  export const createQuery = (initialValue, options) => {
4
- const baseInitial = ObjectUtil.deepClone(initialValue);
5
- let data = $state(ObjectUtil.deepClone(baseInitial));
6
- const includeKeys = options?.onChange?.include ?? [];
33
+ const baseInitial = cloneStateValue(initialValue);
34
+ let data = $state(cloneStateValue(baseInitial));
35
+ const includeKeys = [...new Set(options?.onChange?.include ?? [])];
7
36
  const hasWatch = Boolean(options?.onChange?.handler) && includeKeys.length > 0;
8
- const lastValues = new Map();
37
+ const lastSnapshots = new Map();
9
38
  let onChangeController = null;
10
39
  let onChangeRunId = 0;
11
40
  if (hasWatch) {
12
41
  includeKeys.forEach((key) => {
13
- lastValues.set(key, data[key]);
42
+ lastSnapshots.set(key, cloneStateValue(data[key]));
14
43
  });
15
44
  $effect(() => {
16
45
  const changedKeys = [];
17
46
  includeKeys.forEach((key) => {
18
47
  const nextValue = data[key];
19
- if (!Object.is(lastValues.get(key), nextValue)) {
48
+ trackDeep(nextValue);
49
+ const nextSnapshot = cloneStateValue(nextValue);
50
+ if (!ObjectUtil.equals(lastSnapshots.get(key), nextSnapshot)) {
20
51
  changedKeys.push(key);
21
- lastValues.set(key, nextValue);
52
+ lastSnapshots.set(key, nextSnapshot);
22
53
  }
23
54
  });
24
55
  if (!changedKeys.length)
@@ -33,7 +64,7 @@ export const createQuery = (initialValue, options) => {
33
64
  await options.onChange.handler({
34
65
  changedKeys,
35
66
  signal: localController.signal,
36
- snapshot: ObjectUtil.deepClone(data)
67
+ snapshot: cloneStateValue(data)
37
68
  });
38
69
  };
39
70
  untrack(() => {
@@ -54,13 +85,13 @@ export const createQuery = (initialValue, options) => {
54
85
  });
55
86
  }
56
87
  const reset = () => {
57
- data = ObjectUtil.deepClone(baseInitial);
88
+ data = cloneStateValue(baseInitial);
58
89
  };
59
90
  const patch = (value) => {
60
91
  data = { ...data, ...value };
61
92
  };
62
- const snapshot = () => ObjectUtil.deepClone(data);
63
- const initial = () => ObjectUtil.deepClone(baseInitial);
93
+ const snapshot = () => cloneStateValue(data);
94
+ const initial = () => cloneStateValue(baseInitial);
64
95
  return {
65
96
  get data() {
66
97
  return data;
@@ -0,0 +1,2 @@
1
+ /** Creates a detached graph from plain state while preserving opaque values. */
2
+ export declare const cloneStateValue: <T>(value: T) => T;
@@ -0,0 +1,100 @@
1
+ const preserveExtensibility = (source, target) => {
2
+ if (!Object.isExtensible(source))
3
+ Object.preventExtensions(target);
4
+ };
5
+ const cloneProperties = (source, target, seen, excluded) => {
6
+ for (const key of Reflect.ownKeys(source)) {
7
+ if (key === excluded)
8
+ continue;
9
+ const descriptor = Reflect.getOwnPropertyDescriptor(source, key);
10
+ if (!descriptor)
11
+ continue;
12
+ if ('value' in descriptor)
13
+ descriptor.value = cloneValue(descriptor.value, seen);
14
+ Reflect.defineProperty(target, key, descriptor);
15
+ }
16
+ };
17
+ const cloneValue = (value, seen) => {
18
+ if (value === null || typeof value !== 'object')
19
+ return value;
20
+ const cached = seen.get(value);
21
+ if (cached !== undefined)
22
+ return cached;
23
+ if (Array.isArray(value)) {
24
+ const result = new Array(value.length);
25
+ seen.set(value, result);
26
+ cloneProperties(value, result, seen, 'length');
27
+ const lengthDescriptor = Reflect.getOwnPropertyDescriptor(value, 'length');
28
+ if (lengthDescriptor)
29
+ Reflect.defineProperty(result, 'length', lengthDescriptor);
30
+ preserveExtensibility(value, result);
31
+ return result;
32
+ }
33
+ if (value instanceof Date) {
34
+ const result = new Date(value.getTime());
35
+ seen.set(value, result);
36
+ return result;
37
+ }
38
+ if (value instanceof Map) {
39
+ const result = new Map();
40
+ seen.set(value, result);
41
+ for (const [key, entryValue] of value) {
42
+ result.set(cloneValue(key, seen), cloneValue(entryValue, seen));
43
+ }
44
+ return result;
45
+ }
46
+ if (value instanceof Set) {
47
+ const result = new Set();
48
+ seen.set(value, result);
49
+ for (const entryValue of value)
50
+ result.add(cloneValue(entryValue, seen));
51
+ return result;
52
+ }
53
+ if (value instanceof RegExp) {
54
+ const result = new RegExp(value.source, value.flags);
55
+ result.lastIndex = value.lastIndex;
56
+ seen.set(value, result);
57
+ return result;
58
+ }
59
+ if (typeof URL !== 'undefined' && value instanceof URL) {
60
+ const result = new URL(value.href);
61
+ seen.set(value, result);
62
+ return result;
63
+ }
64
+ if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) {
65
+ const result = new URLSearchParams(value);
66
+ seen.set(value, result);
67
+ return result;
68
+ }
69
+ if (value instanceof ArrayBuffer) {
70
+ const result = value.slice(0);
71
+ seen.set(value, result);
72
+ return result;
73
+ }
74
+ if (typeof SharedArrayBuffer !== 'undefined' && value instanceof SharedArrayBuffer) {
75
+ const result = value.slice(0);
76
+ seen.set(value, result);
77
+ return result;
78
+ }
79
+ if (ArrayBuffer.isView(value)) {
80
+ const buffer = cloneValue(value.buffer, seen);
81
+ const result = value instanceof DataView
82
+ ? new DataView(buffer, value.byteOffset, value.byteLength)
83
+ : new value.constructor(buffer, value.byteOffset, value.length);
84
+ seen.set(value, result);
85
+ return result;
86
+ }
87
+ const prototype = Object.getPrototypeOf(value);
88
+ if (prototype !== Object.prototype && prototype !== null) {
89
+ // File, Blob, Promise and class instances cannot be cloned generically without
90
+ // losing internal slots or behavior. Keeping them is lossless and preserves DX.
91
+ return value;
92
+ }
93
+ const result = Object.create(prototype);
94
+ seen.set(value, result);
95
+ cloneProperties(value, result, seen);
96
+ preserveExtensibility(value, result);
97
+ return result;
98
+ };
99
+ /** Creates a detached graph from plain state while preserving opaque values. */
100
+ export const cloneStateValue = (value) => cloneValue(value, new WeakMap());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-net/kit",
3
- "version": "5.1.1",
3
+ "version": "5.3.0",
4
4
  "license": "MIT",
5
5
  "author": "Pixel1917",
6
6
  "description": "Universal toolkit and library collection, developed as part of the Azure-net ecosystem",
@@ -73,15 +73,15 @@
73
73
  }
74
74
  },
75
75
  "peerDependencies": {
76
- "@sveltejs/kit": ">=2.16.0",
77
- "svelte": ">=5.0.0"
76
+ "@sveltejs/kit": ">=2.57.1",
77
+ "svelte": ">=5.55.4"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@eslint/compat": "^2.0.5",
81
81
  "@eslint/js": "^10.0.1",
82
82
  "@playwright/test": "^1.59.1",
83
83
  "@sveltejs/adapter-auto": "^7.0.1",
84
- "@sveltejs/kit": "^2.57.1",
84
+ "@sveltejs/kit": "^2.70.2",
85
85
  "@sveltejs/package": "^2.5.7",
86
86
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
87
87
  "@testing-library/jest-dom": "^6.9.1",
@@ -95,7 +95,7 @@
95
95
  "prettier": "^3.8.3",
96
96
  "prettier-plugin-svelte": "^3.5.1",
97
97
  "publint": "^0.3.18",
98
- "svelte": "^5.55.4",
98
+ "svelte": "^5.56.9",
99
99
  "svelte-check": "^4.4.6",
100
100
  "typescript": "^5.0.0",
101
101
  "typescript-eslint": "^8.58.2",
@@ -126,9 +126,10 @@
126
126
  "format": "prettier --write .",
127
127
  "lint": "prettier --check . && eslint .",
128
128
  "test:unit": "vitest --config vitest.config.ts",
129
+ "test:client": "vitest --config vitest.client.config.ts",
129
130
  "test:integration": "vitest --config vitest.integration.config.ts --run",
130
- "test": "npm run test:unit -- --run && npm run test:e2e",
131
- "test:e2e": "playwright test --pass-with-no-tests",
131
+ "test": "pnpm test:unit -- --run && pnpm test:client -- --run && pnpm test:integration && pnpm test:e2e",
132
+ "test:e2e": "playwright test",
132
133
  "commit": "pnpm format && git add ."
133
134
  }
134
135
  }