@azure-net/kit 5.4.0 → 5.5.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.
@@ -23,6 +23,16 @@ export interface AsyncSignalSvelte<TData, TError = Error> {
23
23
  reset: () => void;
24
24
  abort: () => void;
25
25
  }
26
+ export interface AsyncSignalBatchOptions {
27
+ parallel?: boolean;
28
+ }
29
+ export interface AsyncSignalBatchFactory {
30
+ <TData, TError = Error>(handler: (signal?: AbortSignal) => Promise<TData>, options?: Omit<AsyncSignalOptions<TData, TError>, 'immediate'>): AsyncSignalSvelte<TData, TError>;
31
+ }
32
+ export interface AsyncSignalBatch<TSignals extends readonly AsyncSignalSvelte<unknown, unknown>[]> {
33
+ execute: () => TSignals;
34
+ }
26
35
  export declare const createAsyncSignal: <TData, TError = Error>(handler: (signal?: AbortSignal) => Promise<TData>, options?: AsyncSignalOptions<TData, TError>) => AsyncSignalSvelte<TData, TError>;
36
+ export declare const createAsyncSignalBatch: <const TSignals extends readonly AsyncSignalSvelte<unknown, unknown>[]>(factory: (createSignal: AsyncSignalBatchFactory) => TSignals, options?: AsyncSignalBatchOptions) => AsyncSignalBatch<TSignals>;
27
37
  export declare const refreshAsyncSignal: (key: string) => Promise<void>;
28
38
  export declare const refreshAllAsyncSignals: () => Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import { untrack } from 'svelte';
2
2
  import { BROWSER } from '../../external/tools/index.js';
3
+ const ASYNC_SIGNAL_BATCH_INTERNAL = Symbol('async-signal-batch-internal');
3
4
  const createAsyncSignalManager = () => {
4
5
  const instances = BROWSER ? new Map() : undefined;
5
6
  const generateUid = () => {
@@ -56,6 +57,9 @@ export const createAsyncSignal = (handler, options = {}) => {
56
57
  let currentPromise = null;
57
58
  let currentRunId = 0;
58
59
  let started = false;
60
+ let batchLocked = false;
61
+ let batchGate = null;
62
+ let batchSkipRequested = false;
59
63
  const run = async (runId, source) => {
60
64
  const initial = !started;
61
65
  started = true;
@@ -117,7 +121,16 @@ export const createAsyncSignal = (handler, options = {}) => {
117
121
  currentPromise = localPromise;
118
122
  return localPromise;
119
123
  };
124
+ const startWhenUnlocked = (source) => {
125
+ if (batchLocked)
126
+ return batchGate ?? Promise.resolve(data);
127
+ return start(source);
128
+ };
120
129
  const execute = async () => {
130
+ if (batchLocked) {
131
+ await batchGate;
132
+ return;
133
+ }
121
134
  if (currentPromise) {
122
135
  await currentPromise;
123
136
  return;
@@ -125,11 +138,15 @@ export const createAsyncSignal = (handler, options = {}) => {
125
138
  await start('manual');
126
139
  };
127
140
  const refresh = async () => {
141
+ if (batchLocked) {
142
+ await batchGate;
143
+ return;
144
+ }
128
145
  await start('manual');
129
146
  };
130
147
  if (BROWSER) {
131
148
  const signalKey = key ?? asyncSignalManager.generateKey();
132
- const callback = (source) => start(source);
149
+ const callback = (source) => startWhenUnlocked(source);
133
150
  asyncSignalManager.register(signalKey, callback);
134
151
  $effect(() => {
135
152
  return () => {
@@ -147,7 +164,7 @@ export const createAsyncSignal = (handler, options = {}) => {
147
164
  void start('auto');
148
165
  }
149
166
  }
150
- return {
167
+ const asyncSignal = {
151
168
  get data() {
152
169
  return data;
153
170
  },
@@ -161,6 +178,8 @@ export const createAsyncSignal = (handler, options = {}) => {
161
178
  return pending;
162
179
  },
163
180
  get ready() {
181
+ if (batchLocked)
182
+ return (batchGate ?? Promise.resolve(data));
164
183
  if (currentPromise)
165
184
  return currentPromise;
166
185
  return start('auto');
@@ -168,6 +187,8 @@ export const createAsyncSignal = (handler, options = {}) => {
168
187
  execute,
169
188
  refresh,
170
189
  reset: () => {
190
+ if (batchLocked)
191
+ batchSkipRequested = true;
171
192
  currentRunId += 1;
172
193
  abortController?.abort();
173
194
  abortController = null;
@@ -177,6 +198,8 @@ export const createAsyncSignal = (handler, options = {}) => {
177
198
  status = 'idle';
178
199
  },
179
200
  abort: () => {
201
+ if (batchLocked)
202
+ batchSkipRequested = true;
180
203
  currentRunId += 1;
181
204
  abortController?.abort();
182
205
  abortController = null;
@@ -185,6 +208,100 @@ export const createAsyncSignal = (handler, options = {}) => {
185
208
  status = 'idle';
186
209
  }
187
210
  };
211
+ Object.defineProperty(asyncSignal, ASYNC_SIGNAL_BATCH_INTERNAL, {
212
+ value: {
213
+ lock: (gate) => {
214
+ batchLocked = true;
215
+ batchGate = gate ?? null;
216
+ batchSkipRequested = false;
217
+ },
218
+ unlock: () => {
219
+ batchLocked = false;
220
+ batchGate = null;
221
+ },
222
+ start: (source) => {
223
+ if (batchSkipRequested)
224
+ return Promise.resolve(undefined);
225
+ return start(source);
226
+ }
227
+ }
228
+ });
229
+ return asyncSignal;
230
+ };
231
+ const createDeferred = () => {
232
+ let resolve;
233
+ const promise = new Promise((promiseResolve) => {
234
+ resolve = promiseResolve;
235
+ });
236
+ return { promise, resolve };
237
+ };
238
+ export const createAsyncSignalBatch = (factory, options = {}) => {
239
+ const internals = new Map();
240
+ const createSignal = (handler, signalOptions = {}) => {
241
+ const signal = createAsyncSignal(handler, { ...signalOptions, immediate: false });
242
+ const internal = signal[ASYNC_SIGNAL_BATCH_INTERNAL];
243
+ internal.lock();
244
+ internals.set(signal, internal);
245
+ return signal;
246
+ };
247
+ const signals = factory(createSignal);
248
+ const uniqueSignals = new Set(signals);
249
+ if (uniqueSignals.size !== signals.length || signals.some((signal) => !internals.has(signal))) {
250
+ throw new Error('[AsyncSignalBatch] Factory must return each signal created by createSignal exactly once.');
251
+ }
252
+ if (internals.size !== signals.length) {
253
+ throw new Error('[AsyncSignalBatch] Factory must return every signal created by createSignal.');
254
+ }
255
+ let running = false;
256
+ const execute = () => {
257
+ if (running || signals.length === 0)
258
+ return signals;
259
+ running = true;
260
+ const gates = signals.map(() => createDeferred());
261
+ signals.forEach((signal, index) => {
262
+ internals.get(signal)?.lock(gates[index].promise);
263
+ });
264
+ queueMicrotask(() => {
265
+ if (options.parallel !== false) {
266
+ let remaining = signals.length;
267
+ signals.forEach((signal, index) => {
268
+ const internal = internals.get(signal);
269
+ void internal
270
+ .start('manual')
271
+ .catch(() => undefined)
272
+ .then((result) => {
273
+ gates[index].resolve(result);
274
+ internal.unlock();
275
+ remaining -= 1;
276
+ if (remaining === 0)
277
+ running = false;
278
+ });
279
+ });
280
+ return;
281
+ }
282
+ void (async () => {
283
+ for (let index = 0; index < signals.length; index += 1) {
284
+ const signal = signals[index];
285
+ const internal = internals.get(signal);
286
+ const result = await internal.start('manual').catch(() => undefined);
287
+ gates[index].resolve(result);
288
+ internal.unlock();
289
+ if (signal.status !== 'success') {
290
+ for (let skippedIndex = index + 1; skippedIndex < signals.length; skippedIndex += 1) {
291
+ const skippedSignal = signals[skippedIndex];
292
+ gates[skippedIndex].resolve(undefined);
293
+ internals.get(skippedSignal)?.unlock();
294
+ }
295
+ break;
296
+ }
297
+ }
298
+ })().finally(() => {
299
+ running = false;
300
+ });
301
+ });
302
+ return signals;
303
+ };
304
+ return { execute };
188
305
  };
189
306
  export const refreshAsyncSignal = async (key) => {
190
307
  return await asyncSignalManager.refreshByKey(key);
@@ -0,0 +1,4 @@
1
+ export type EffectCleanup = () => void;
2
+ export type EffectCallback = () => EffectCleanup | Promise<unknown> | void;
3
+ export type EffectDependency<T = unknown> = () => T;
4
+ export declare const createEffect: (callback: EffectCallback, dependencies: readonly EffectDependency[]) => void;
@@ -0,0 +1,27 @@
1
+ import { untrack } from 'svelte';
2
+ export const createEffect = (callback, dependencies) => {
3
+ let cleanup;
4
+ let initialized = false;
5
+ let previousValues = [];
6
+ $effect(() => {
7
+ const values = dependencies.map((dependency) => dependency());
8
+ const changed = !initialized || values.some((value, index) => !Object.is(value, previousValues[index]));
9
+ if (!changed)
10
+ return;
11
+ initialized = true;
12
+ previousValues = values;
13
+ untrack(() => {
14
+ cleanup?.();
15
+ cleanup = undefined;
16
+ const result = callback();
17
+ if (typeof result === 'function')
18
+ cleanup = result;
19
+ });
20
+ });
21
+ $effect(() => {
22
+ return () => {
23
+ untrack(() => cleanup?.());
24
+ cleanup = undefined;
25
+ };
26
+ });
27
+ };
@@ -0,0 +1 @@
1
+ export * from './Effect.svelte.js';
@@ -0,0 +1 @@
1
+ export * from './Effect.svelte.js';
@@ -1,3 +1,4 @@
1
- export { createAsyncSignal, refreshAsyncSignal, refreshAllAsyncSignals, type AsyncSignalOptions, type AsyncSignalSvelte, type AsyncStatus, type AsyncSignalSource } from './async-signal/AsyncSignal.svelte.js';
1
+ export { createAsyncSignal, createAsyncSignalBatch, refreshAsyncSignal, refreshAllAsyncSignals, type AsyncSignalBatch, type AsyncSignalBatchFactory, type AsyncSignalBatchOptions, type AsyncSignalOptions, type AsyncSignalSvelte, type AsyncStatus, type AsyncSignalSource } from './async-signal/AsyncSignal.svelte.js';
2
2
  export { createActiveForm, type ActiveForm, type ActiveFormCallbackContext, type ActiveFormController, type ActiveFormResponse, type ActiveFormStatus, type FormConfig, type InitialValuesHelpers, type LinkPromise, type LinkSignal, type LinkSignalOptions, type ResetBehaviors } from './active-form/ActiveForm.svelte.js';
3
- export { createQuery, type QueryController, type CreateQueryOptions } from './query/Query.svelte.js';
3
+ export { createEffect, type EffectCallback, type EffectCleanup, type EffectDependency } from './effect/Effect.svelte.js';
4
+ export { createQuery, type CreateQueryOptions, type QueryController, type QueryPath, type QuerySearchParamsSync } from './query/Query.svelte.js';
@@ -1,3 +1,4 @@
1
- export { createAsyncSignal, refreshAsyncSignal, refreshAllAsyncSignals } from './async-signal/AsyncSignal.svelte.js';
1
+ export { createAsyncSignal, createAsyncSignalBatch, refreshAsyncSignal, refreshAllAsyncSignals } from './async-signal/AsyncSignal.svelte.js';
2
2
  export { createActiveForm } from './active-form/ActiveForm.svelte.js';
3
+ export { createEffect } from './effect/Effect.svelte.js';
3
4
  export { createQuery } from './query/Query.svelte.js';
@@ -1,20 +1,23 @@
1
- export interface QueryOnChangeContext<T extends Record<string, unknown>> {
2
- snapshot: T;
3
- changedKeys: (keyof T)[];
4
- signal: AbortSignal;
5
- }
6
- export interface QueryOnChangeOptions<T extends Record<string, unknown>> {
7
- include: (keyof T)[];
8
- handler: (ctx: QueryOnChangeContext<T>) => Promise<void> | void;
1
+ import { type EffectCallback } from '../effect/Effect.svelte.js';
2
+ type QueryPathDepth = readonly [unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown];
3
+ type QueryLeaf = bigint | boolean | Date | File | Blob | ((...args: never[]) => unknown) | Map<unknown, unknown> | null | number | readonly unknown[] | RegExp | Set<unknown> | string | symbol | undefined | URL | URLSearchParams;
4
+ export type QueryPath<T, Depth extends readonly unknown[] = QueryPathDepth> = Depth extends readonly [unknown, ...infer Rest] ? T extends QueryLeaf ? never : T extends object ? {
5
+ [K in Extract<keyof T, string>]: NonNullable<T[K]> extends QueryLeaf ? K : K | `${K}.${QueryPath<NonNullable<T[K]>, Rest>}`;
6
+ }[Extract<keyof T, string>] : never : never;
7
+ export interface QuerySearchParamsSync<T extends Record<string, unknown>> {
8
+ fromSearchParams?: (params: URLSearchParams, initial: Readonly<T>) => Partial<T>;
9
+ toSearchParams?: (data: Readonly<T>, initial: Readonly<T>) => URLSearchParams;
9
10
  }
10
11
  export interface CreateQueryOptions<T extends Record<string, unknown>> {
11
- onChange?: QueryOnChangeOptions<T>;
12
+ syncWithSearchParams?: boolean | QuerySearchParamsSync<T>;
12
13
  }
13
14
  export interface QueryController<T extends Record<string, unknown>> {
14
15
  data: T;
15
16
  patch: (value: Partial<T>) => void;
16
17
  reset: () => void;
17
- snapshot: () => T;
18
- initial: () => T;
18
+ createSnapshot: () => T;
19
+ getInitial: () => T;
20
+ createEffect: (callback: EffectCallback, dependencies?: readonly QueryPath<T>[]) => void;
19
21
  }
20
22
  export declare const createQuery: <T extends Record<string, unknown>>(initialValue: T, options?: CreateQueryOptions<T>) => QueryController<T>;
23
+ export {};
@@ -1,97 +1,106 @@
1
- import { untrack } from 'svelte';
2
- import { ObjectUtil } from '../../external/tools/index.js';
1
+ import { replaceState } from '$app/navigation';
2
+ import { page } from '$app/state';
3
+ import { createEffect } from '../effect/Effect.svelte.js';
3
4
  import { cloneStateValue } from '../shared/cloneStateValue.js';
4
- const trackDeep = (value, seen = new WeakSet()) => {
5
- if (value === null || typeof value !== 'object' || seen.has(value))
5
+ import { fromSearchParams, toSearchParams } from './SearchParamsSync.js';
6
+ const trackDeep = (value, seen) => {
7
+ if (value === null || typeof value !== 'object')
6
8
  return;
7
- seen.add(value);
9
+ const visited = seen ?? new WeakSet();
10
+ if (visited.has(value))
11
+ return;
12
+ visited.add(value);
8
13
  if (Array.isArray(value)) {
9
14
  for (const item of value)
10
- trackDeep(item, seen);
15
+ trackDeep(item, visited);
11
16
  return;
12
17
  }
13
18
  if (value instanceof Map) {
14
19
  for (const [key, item] of value) {
15
- trackDeep(key, seen);
16
- trackDeep(item, seen);
20
+ trackDeep(key, visited);
21
+ trackDeep(item, visited);
17
22
  }
18
23
  return;
19
24
  }
20
25
  if (value instanceof Set) {
21
26
  for (const item of value)
22
- trackDeep(item, seen);
27
+ trackDeep(item, visited);
23
28
  return;
24
29
  }
25
30
  const prototype = Object.getPrototypeOf(value);
26
31
  if (prototype !== Object.prototype && prototype !== null)
27
32
  return;
28
- for (const key of Reflect.ownKeys(value)) {
29
- trackDeep(Reflect.get(value, key), seen);
33
+ for (const key of Reflect.ownKeys(value))
34
+ trackDeep(Reflect.get(value, key), visited);
35
+ };
36
+ const readPath = (source, path) => {
37
+ let value = source;
38
+ for (const key of path.split('.')) {
39
+ if (value === null || typeof value !== 'object')
40
+ return undefined;
41
+ value = Reflect.get(value, key);
30
42
  }
43
+ return value;
44
+ };
45
+ const createDeepDependency = (read) => {
46
+ let version = 0;
47
+ return () => {
48
+ const value = read();
49
+ if (value === null || typeof value !== 'object')
50
+ return value;
51
+ trackDeep(value);
52
+ return ++version;
53
+ };
31
54
  };
32
55
  export const createQuery = (initialValue, options) => {
33
56
  const baseInitial = cloneStateValue(initialValue);
34
- let data = $state(cloneStateValue(baseInitial));
35
- const includeKeys = [...new Set(options?.onChange?.include ?? [])];
36
- const hasWatch = Boolean(options?.onChange?.handler) && includeKeys.length > 0;
37
- const lastSnapshots = new Map();
38
- let onChangeController = null;
39
- let onChangeRunId = 0;
40
- if (hasWatch) {
41
- includeKeys.forEach((key) => {
42
- lastSnapshots.set(key, cloneStateValue(data[key]));
43
- });
44
- $effect(() => {
45
- const changedKeys = [];
46
- includeKeys.forEach((key) => {
47
- const nextValue = data[key];
48
- trackDeep(nextValue);
49
- const nextSnapshot = cloneStateValue(nextValue);
50
- if (!ObjectUtil.equals(lastSnapshots.get(key), nextSnapshot)) {
51
- changedKeys.push(key);
52
- lastSnapshots.set(key, nextSnapshot);
53
- }
54
- });
55
- if (!changedKeys.length)
56
- return;
57
- const previousController = onChangeController;
58
- if (previousController)
59
- previousController.abort();
60
- const localController = new AbortController();
61
- onChangeController = localController;
62
- const localRunId = ++onChangeRunId;
63
- const run = async () => {
64
- await options.onChange.handler({
65
- changedKeys,
66
- signal: localController.signal,
67
- snapshot: cloneStateValue(data)
68
- });
69
- };
70
- untrack(() => {
71
- void run()
72
- .catch(() => undefined)
73
- .finally(() => {
74
- if (onChangeRunId === localRunId && onChangeController === localController) {
75
- onChangeController = null;
76
- }
77
- });
78
- });
79
- });
80
- $effect(() => {
81
- return () => {
82
- if (onChangeController)
83
- onChangeController.abort();
84
- };
85
- });
86
- }
57
+ const searchParamsSync = options?.syncWithSearchParams;
58
+ const syncSettings = typeof searchParamsSync === 'object' ? searchParamsSync : undefined;
59
+ const syncEnabled = Boolean(searchParamsSync);
60
+ const parseSearchParams = syncSettings?.fromSearchParams ?? (fromSearchParams);
61
+ const serializeSearchParams = syncSettings?.toSearchParams ?? (toSearchParams);
62
+ const parsedInitial = syncEnabled ? parseSearchParams(new URLSearchParams(page.url.searchParams), cloneStateValue(baseInitial)) : undefined;
63
+ let data = $state(parsedInitial ? { ...cloneStateValue(baseInitial), ...cloneStateValue(parsedInitial) } : cloneStateValue(baseInitial));
87
64
  const reset = () => {
88
65
  data = cloneStateValue(baseInitial);
89
66
  };
90
67
  const patch = (value) => {
91
68
  data = { ...data, ...value };
92
69
  };
93
- const snapshot = () => cloneStateValue(data);
94
- const initial = () => cloneStateValue(baseInitial);
70
+ const createSnapshot = () => cloneStateValue(data);
71
+ const getInitial = () => cloneStateValue(baseInitial);
72
+ const createQueryEffect = (callback, dependencies) => {
73
+ const effectDependencies = dependencies === undefined
74
+ ? [createDeepDependency(() => data)]
75
+ : [...new Set(dependencies)].map((path) => createDeepDependency(() => readPath(data, path)));
76
+ createEffect(callback, effectDependencies);
77
+ };
78
+ if (syncEnabled) {
79
+ const managedKeys = new Set(Object.keys(baseInitial));
80
+ const captureInitialSyncedData = () => cloneStateValue(data);
81
+ const initialSyncedData = captureInitialSyncedData();
82
+ let initialized = false;
83
+ createEffect(() => {
84
+ const nextParams = serializeSearchParams(data, baseInitial);
85
+ for (const key of nextParams.keys())
86
+ managedKeys.add(key);
87
+ if (!initialized) {
88
+ initialized = true;
89
+ const initialParams = serializeSearchParams(initialSyncedData, baseInitial);
90
+ for (const key of initialParams.keys())
91
+ managedKeys.add(key);
92
+ if (nextParams.toString() === initialParams.toString())
93
+ return;
94
+ }
95
+ const url = new URL(page.url);
96
+ for (const key of managedKeys)
97
+ url.searchParams.delete(key);
98
+ for (const [key, value] of nextParams)
99
+ url.searchParams.append(key, value);
100
+ if (url.search !== page.url.search)
101
+ replaceState(url, page.state);
102
+ }, [createDeepDependency(() => data)]);
103
+ }
95
104
  return {
96
105
  get data() {
97
106
  return data;
@@ -101,7 +110,8 @@ export const createQuery = (initialValue, options) => {
101
110
  },
102
111
  patch,
103
112
  reset,
104
- snapshot,
105
- initial
113
+ createSnapshot,
114
+ getInitial,
115
+ createEffect: createQueryEffect
106
116
  };
107
117
  };
@@ -0,0 +1,2 @@
1
+ export declare const fromSearchParams: <T extends Record<string, unknown>>(params: URLSearchParams, initial: Readonly<T>) => Partial<T>;
2
+ export declare const toSearchParams: <T extends Record<string, unknown>>(data: Readonly<T>) => URLSearchParams;
@@ -0,0 +1,119 @@
1
+ const INVALID_VALUE = Symbol('invalid-query-search-param');
2
+ const isPlainObject = (value) => {
3
+ if (value === null || typeof value !== 'object')
4
+ return false;
5
+ const prototype = Object.getPrototypeOf(value);
6
+ return prototype === Object.prototype || prototype === null;
7
+ };
8
+ const parseScalar = (value, initial) => {
9
+ if (typeof initial === 'string')
10
+ return value;
11
+ if (typeof initial === 'number') {
12
+ if (!value.trim())
13
+ return INVALID_VALUE;
14
+ const parsed = Number(value);
15
+ return Number.isFinite(parsed) ? parsed : INVALID_VALUE;
16
+ }
17
+ if (typeof initial === 'boolean') {
18
+ const normalized = value.toLowerCase();
19
+ if (normalized === 'true' || normalized === '1')
20
+ return true;
21
+ if (normalized === 'false' || normalized === '0')
22
+ return false;
23
+ return INVALID_VALUE;
24
+ }
25
+ if (typeof initial === 'bigint') {
26
+ if (!value.trim())
27
+ return INVALID_VALUE;
28
+ try {
29
+ return BigInt(value);
30
+ }
31
+ catch {
32
+ return INVALID_VALUE;
33
+ }
34
+ }
35
+ if (initial instanceof Date) {
36
+ const parsed = new Date(value);
37
+ return Number.isNaN(parsed.getTime()) ? INVALID_VALUE : parsed;
38
+ }
39
+ if (initial instanceof URL) {
40
+ try {
41
+ return new URL(value, initial);
42
+ }
43
+ catch {
44
+ return INVALID_VALUE;
45
+ }
46
+ }
47
+ if (initial instanceof URLSearchParams)
48
+ return new URLSearchParams(value);
49
+ if (isPlainObject(initial)) {
50
+ try {
51
+ const parsed = JSON.parse(value);
52
+ return isPlainObject(parsed) ? parsed : INVALID_VALUE;
53
+ }
54
+ catch {
55
+ return INVALID_VALUE;
56
+ }
57
+ }
58
+ return INVALID_VALUE;
59
+ };
60
+ const parseValue = (params, key, initial) => {
61
+ if (Array.isArray(initial)) {
62
+ const values = params.getAll(key);
63
+ if (!values.length)
64
+ return INVALID_VALUE;
65
+ if (!initial.length)
66
+ return values;
67
+ const parsed = values.map((value) => parseScalar(value, initial[0]));
68
+ return parsed.some((value) => value === INVALID_VALUE) ? INVALID_VALUE : parsed;
69
+ }
70
+ const value = params.get(key);
71
+ return value === null ? INVALID_VALUE : parseScalar(value, initial);
72
+ };
73
+ const serializeScalar = (value) => {
74
+ if (typeof value === 'string' || typeof value === 'boolean' || typeof value === 'bigint')
75
+ return String(value);
76
+ if (typeof value === 'number')
77
+ return Number.isFinite(value) ? String(value) : undefined;
78
+ if (value instanceof Date)
79
+ return Number.isNaN(value.getTime()) ? undefined : value.toISOString();
80
+ if (value instanceof URL || value instanceof URLSearchParams)
81
+ return value.toString();
82
+ if (isPlainObject(value)) {
83
+ try {
84
+ return JSON.stringify(value);
85
+ }
86
+ catch {
87
+ return undefined;
88
+ }
89
+ }
90
+ return undefined;
91
+ };
92
+ export const fromSearchParams = (params, initial) => {
93
+ const result = {};
94
+ for (const key of Object.keys(initial)) {
95
+ if (!params.has(key))
96
+ continue;
97
+ const parsed = parseValue(params, key, initial[key]);
98
+ if (parsed !== INVALID_VALUE)
99
+ Reflect.set(result, key, parsed);
100
+ }
101
+ return result;
102
+ };
103
+ export const toSearchParams = (data) => {
104
+ const params = new URLSearchParams();
105
+ for (const [key, value] of Object.entries(data)) {
106
+ if (Array.isArray(value)) {
107
+ for (const item of value) {
108
+ const serialized = serializeScalar(item);
109
+ if (serialized !== undefined)
110
+ params.append(key, serialized);
111
+ }
112
+ continue;
113
+ }
114
+ const serialized = serializeScalar(value);
115
+ if (serialized !== undefined)
116
+ params.set(key, serialized);
117
+ }
118
+ return params;
119
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-net/kit",
3
- "version": "5.4.0",
3
+ "version": "5.5.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",