@azure-net/kit 5.2.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/infra/response/BaseResponse.d.ts +1 -1
- package/dist/shared/cookie/UniversalCookie.d.ts +28 -0
- package/dist/shared/cookie/UniversalCookie.js +70 -0
- package/dist/shared/event-bus/EventBus.d.ts +11 -1
- package/dist/shared/event-bus/EventBus.js +1 -4
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/svelte/active-form/ActiveForm.svelte.d.ts +43 -15
- package/dist/svelte/active-form/ActiveForm.svelte.js +156 -42
- package/dist/svelte/async-signal/AsyncSignal.svelte.js +4 -1
- package/dist/svelte/index.d.ts +1 -1
- package/dist/svelte/query/Query.svelte.js +36 -5
- package/package.json +3 -2
|
@@ -20,7 +20,7 @@ export declare class ResponseBuilder<TData = unknown, TMeta = object, TWrapper =
|
|
|
20
20
|
toPlainObject(): TResource;
|
|
21
21
|
}): Omit<this, keyof ResponseBuilder<unknown, unknown, unknown>> & ResponseBuilder<TResource[], TMeta, TWrapper>;
|
|
22
22
|
extract<TPath extends DeepKeys<TData>>(path: TPath): Omit<this, keyof ResponseBuilder<unknown, unknown, unknown>> & ResponseBuilder<DeepValue<TData, TPath>, TMeta, TWrapper>;
|
|
23
|
-
addMeta<TNewMeta extends Record<string, unknown>>(metaData: TNewMeta | ((current: TMeta) => TNewMeta)): Omit<this, keyof ResponseBuilder<unknown, unknown, unknown>> & ResponseBuilder<TData, TMeta & TNewMeta, TWrapper>;
|
|
23
|
+
addMeta<TNewMeta extends Record<string, unknown>>(metaData: TNewMeta | ((current: TMeta & TNewMeta) => TMeta & TNewMeta)): Omit<this, keyof ResponseBuilder<unknown, unknown, unknown>> & ResponseBuilder<TData, TMeta & TNewMeta, TWrapper>;
|
|
24
24
|
getData(): TData;
|
|
25
25
|
get(): {
|
|
26
26
|
data: TData;
|
|
@@ -9,12 +9,40 @@ 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>;
|
|
18
46
|
private static serialize;
|
|
19
47
|
private static deserialize;
|
|
20
48
|
/**
|
|
@@ -1,11 +1,81 @@
|
|
|
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
|
+
}
|
|
9
79
|
static serialize(value) {
|
|
10
80
|
if (typeof value === 'string')
|
|
11
81
|
return value;
|
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
import { EventBus } from '../../external/tools/index.js';
|
|
2
|
-
|
|
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({}));
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { UniversalCookie, type CookieOptions } from './cookie/index.js';
|
|
1
|
+
export { UniversalCookie, UniversalCookieInstance, UniversalNamedCookieInstance, type CookieOptions } from './cookie/index.js';
|
|
2
2
|
export { BackgroundTask } from './background-task/index.js';
|
|
3
3
|
export { AppEvents } from './event-bus/index.js';
|
|
4
4
|
export { ClassMirror } from './class-mirror/index.js';
|
package/dist/shared/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { UniversalCookie } from './cookie/index.js';
|
|
1
|
+
export { UniversalCookie, UniversalCookieInstance, UniversalNamedCookieInstance } from './cookie/index.js';
|
|
2
2
|
export { BackgroundTask } from './background-task/index.js';
|
|
3
3
|
export { AppEvents } from './event-bus/index.js';
|
|
4
4
|
export { ClassMirror } from './class-mirror/index.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RequestErrors } from '../../delivery/schema/index.js';
|
|
2
2
|
import type { AsyncActionResponse } from '../../delivery/injectable-dependencies/AsyncHelpers.js';
|
|
3
|
-
type
|
|
3
|
+
import type { AsyncSignalSvelte } from '../async-signal/AsyncSignal.svelte.js';
|
|
4
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
4
5
|
type PathRequiredShape<T, P extends string> = P extends `${infer Head}.${infer Tail}` ? Head extends keyof T ? {
|
|
5
6
|
[K in Head]-?: PathRequiredShape<NonNullable<T[K]>, Tail>;
|
|
6
7
|
} : unknown : P extends keyof T ? {
|
|
@@ -8,32 +9,59 @@ type PathRequiredShape<T, P extends string> = P extends `${infer Head}.${infer T
|
|
|
8
9
|
} : unknown;
|
|
9
10
|
type UnionToIntersection<U> = (U extends unknown ? (arg: U) => void : never) extends (arg: infer I) => void ? I : never;
|
|
10
11
|
type RequiredByPaths<T, P extends string> = [P] extends [never] ? object : UnionToIntersection<PathRequiredShape<T, P>>;
|
|
11
|
-
type ResetBehaviors = 'clear' | 'initial' | 'reloadInitial' | 'default';
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
export type ResetBehaviors = 'clear' | 'initial' | 'reloadInitial' | 'default';
|
|
13
|
+
export type ActiveFormStatus = 'idle' | 'waitingForInitialValues' | 'pending' | 'success' | 'error';
|
|
14
|
+
export type ActiveFormResponse<Response, FormData, Custom = Record<never, never>> = AsyncActionResponse<Response, FormData, Custom>;
|
|
15
|
+
export interface LinkSignalOptions<TValue = unknown, MappedValues = unknown> {
|
|
16
|
+
watch?: boolean;
|
|
17
|
+
mapValues?: (data: TValue | undefined) => MappedValues;
|
|
18
|
+
}
|
|
19
|
+
export interface LinkPromise<FormData> {
|
|
20
|
+
<TValue extends Partial<FormData>>(source: () => Promise<TValue>): Promise<Partial<FormData>>;
|
|
21
|
+
<TValue>(source: () => Promise<TValue>, map: (value: TValue) => MaybePromise<Partial<FormData>>): Promise<Partial<FormData>>;
|
|
22
|
+
}
|
|
23
|
+
export interface LinkSignal<FormData> {
|
|
24
|
+
<TValue, MappedValues extends Partial<FormData>>(signal: AsyncSignalSvelte<TValue, unknown>, options: LinkSignalOptions<TValue, MappedValues> & {
|
|
25
|
+
mapValues: (data: TValue | undefined) => MappedValues;
|
|
26
|
+
}): Promise<MappedValues>;
|
|
27
|
+
<TValue extends Partial<FormData>>(signal: AsyncSignalSvelte<TValue, unknown>, options?: {
|
|
28
|
+
watch?: boolean;
|
|
29
|
+
mapValues?: never;
|
|
30
|
+
}): Promise<TValue | undefined>;
|
|
31
|
+
}
|
|
32
|
+
export interface InitialValuesHelpers<FormData> {
|
|
33
|
+
linkPromise: LinkPromise<FormData>;
|
|
34
|
+
linkSignal: LinkSignal<FormData>;
|
|
22
35
|
}
|
|
36
|
+
type InitialValues<FormData> = (helpers: InitialValuesHelpers<FormData>) => MaybePromise<Partial<FormData> | undefined>;
|
|
23
37
|
export interface ActiveForm<FormData, Response, Custom, RequiredPath extends string = never> {
|
|
24
38
|
data: Partial<FormData> & RequiredByPaths<FormData, RequiredPath>;
|
|
25
39
|
errors: RequestErrors<FormData>;
|
|
26
|
-
submit: () => Promise<
|
|
40
|
+
submit: () => Promise<ActiveFormResponse<Response, FormData, Custom>>;
|
|
27
41
|
reset: (behavior?: ResetBehaviors) => Promise<void>;
|
|
28
42
|
pending: boolean;
|
|
43
|
+
status: ActiveFormStatus;
|
|
29
44
|
dirty: boolean;
|
|
30
|
-
ready: Promise<Partial<FormData>>;
|
|
31
45
|
}
|
|
32
46
|
export interface ActiveFormController<FormData, RequiredPath extends string = never> {
|
|
33
47
|
data: Partial<FormData> & RequiredByPaths<FormData, RequiredPath>;
|
|
34
48
|
errors: RequestErrors<FormData>;
|
|
35
49
|
reset: (behavior?: ResetBehaviors) => Promise<void>;
|
|
36
50
|
}
|
|
51
|
+
export interface ActiveFormCallbackContext<FormData, Response, Custom, RequiredPath extends string = never> {
|
|
52
|
+
form: ActiveForm<FormData, Response, Custom, RequiredPath>;
|
|
53
|
+
response: ActiveFormResponse<Response, FormData, Custom>;
|
|
54
|
+
}
|
|
55
|
+
export interface FormConfig<FormData, Response, Custom = Record<never, never>, RequiredPath extends string = never> {
|
|
56
|
+
initialValues?: InitialValues<FormData>;
|
|
57
|
+
required?: readonly RequiredPath[];
|
|
58
|
+
onSuccess?: (context: ActiveFormCallbackContext<FormData, Response, Custom, RequiredPath>) => Promise<void> | void;
|
|
59
|
+
onError?: (context: ActiveFormCallbackContext<FormData, Response, Custom, RequiredPath>) => Promise<void> | void;
|
|
60
|
+
beforeSubmit?: (actions: {
|
|
61
|
+
form: ActiveFormController<FormData, RequiredPath>;
|
|
62
|
+
abort: () => void;
|
|
63
|
+
}) => Promise<void> | void;
|
|
64
|
+
}
|
|
37
65
|
type ExtractResponse<T> = T extends AsyncActionResponse<infer R, unknown, unknown> ? R : never;
|
|
38
66
|
type ExtractFormData<T> = T extends AsyncActionResponse<unknown, infer D, unknown> ? D : never;
|
|
39
67
|
type ExtractCustom<T> = T extends AsyncActionResponse<unknown, unknown, infer C> ? C : never;
|
|
@@ -43,5 +71,5 @@ type ExtractFromSubmit<T> = {
|
|
|
43
71
|
formData: ExtractFormData<UnwrapPromise<T>>;
|
|
44
72
|
custom: ExtractCustom<UnwrapPromise<T>>;
|
|
45
73
|
};
|
|
46
|
-
export declare const createActiveForm: <SubmitReturn extends Promise<AsyncActionResponse<unknown, unknown, unknown>>, RequiredPath extends string = never>(onSubmit: (formData: Partial<ExtractFromSubmit<SubmitReturn>["formData"]>) => SubmitReturn, config?: FormConfig<ExtractFromSubmit<SubmitReturn>["formData"], ExtractFromSubmit<SubmitReturn>["response"],
|
|
74
|
+
export declare const createActiveForm: <SubmitReturn extends Promise<AsyncActionResponse<unknown, unknown, unknown>>, RequiredPath extends string = never>(onSubmit: (formData: Partial<ExtractFromSubmit<SubmitReturn>["formData"]>) => SubmitReturn, config?: FormConfig<ExtractFromSubmit<SubmitReturn>["formData"], ExtractFromSubmit<SubmitReturn>["response"], ExtractFromSubmit<SubmitReturn>["custom"], RequiredPath>) => ActiveForm<ExtractFromSubmit<SubmitReturn>["formData"], ExtractFromSubmit<SubmitReturn>["response"], ExtractFromSubmit<SubmitReturn>["custom"], RequiredPath>;
|
|
47
75
|
export {};
|
|
@@ -1,35 +1,133 @@
|
|
|
1
1
|
import { ObjectUtil } from '../../external/tools/index.js';
|
|
2
2
|
import { cloneStateValue } from '../shared/cloneStateValue.js';
|
|
3
|
+
const NO_INITIAL_VALUES_ERROR = Symbol('no-initial-values-error');
|
|
4
|
+
const isPromiseLike = (value) => {
|
|
5
|
+
return typeof value?.then === 'function';
|
|
6
|
+
};
|
|
3
7
|
export const createActiveForm = (onSubmit, config) => {
|
|
4
|
-
const isPromise = (value) => {
|
|
5
|
-
return typeof value?.then === 'function';
|
|
6
|
-
};
|
|
7
8
|
let initial = {};
|
|
8
9
|
let formData = $state(cloneStateValue(initial));
|
|
9
10
|
let formErrors = $state({});
|
|
10
|
-
let
|
|
11
|
+
let status = $state('idle');
|
|
12
|
+
let signalBindings = $state.raw([]);
|
|
11
13
|
let submitRunId = 0;
|
|
12
14
|
let initialLoadRunId = 0;
|
|
15
|
+
let initialValuesTask = Promise.resolve();
|
|
16
|
+
let initialValuesError = NO_INITIAL_VALUES_ERROR;
|
|
17
|
+
let bindingsBySignal;
|
|
13
18
|
const dirty = $derived(!ObjectUtil.equals(formData, initial));
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
formData = cloneStateValue(initial);
|
|
20
|
-
formErrors = {};
|
|
21
|
-
return cloneStateValue(initial);
|
|
19
|
+
const pending = $derived(status === 'waitingForInitialValues' || status === 'pending');
|
|
20
|
+
const setValues = (values, invalidateSubmission = false) => {
|
|
21
|
+
if (invalidateSubmission) {
|
|
22
|
+
submitRunId += 1;
|
|
23
|
+
status = 'idle';
|
|
22
24
|
}
|
|
23
|
-
|
|
24
|
-
const nextInitial = isPromise(value) ? await value : value;
|
|
25
|
-
if (runId !== initialLoadRunId)
|
|
26
|
-
return cloneStateValue(initial);
|
|
27
|
-
initial = cloneStateValue((nextInitial ?? {}));
|
|
25
|
+
initial = cloneStateValue(values);
|
|
28
26
|
formData = cloneStateValue(initial);
|
|
29
27
|
formErrors = {};
|
|
30
|
-
return cloneStateValue(initial);
|
|
31
28
|
};
|
|
32
|
-
|
|
29
|
+
$effect(() => {
|
|
30
|
+
for (const binding of signalBindings) {
|
|
31
|
+
const status = binding.signal.status;
|
|
32
|
+
const data = binding.signal.data;
|
|
33
|
+
const shouldUpdate = binding.active && status === 'success' && (binding.lastStatus !== 'success' || !Object.is(binding.lastData, data));
|
|
34
|
+
binding.lastStatus = status;
|
|
35
|
+
binding.lastData = data;
|
|
36
|
+
if (shouldUpdate)
|
|
37
|
+
setValues(binding.map(data), true);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
const loadInitialValues = async () => {
|
|
41
|
+
const runId = ++initialLoadRunId;
|
|
42
|
+
const source = config?.initialValues;
|
|
43
|
+
initialValuesError = NO_INITIAL_VALUES_ERROR;
|
|
44
|
+
if (!source) {
|
|
45
|
+
bindingsBySignal?.clear();
|
|
46
|
+
bindingsBySignal = undefined;
|
|
47
|
+
signalBindings = [];
|
|
48
|
+
setValues({});
|
|
49
|
+
status = 'idle';
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const linkPromise = (async (promiseSource, map) => {
|
|
54
|
+
const value = await promiseSource();
|
|
55
|
+
return map ? await map(value) : value;
|
|
56
|
+
});
|
|
57
|
+
const linkSignal = (async (signal, options = {}) => {
|
|
58
|
+
const map = options.mapValues ?? ((value) => value);
|
|
59
|
+
let binding;
|
|
60
|
+
if (options.watch && runId === initialLoadRunId) {
|
|
61
|
+
const untypedSignal = signal;
|
|
62
|
+
const bindings = (bindingsBySignal ??= new Map());
|
|
63
|
+
binding = bindings.get(untypedSignal);
|
|
64
|
+
if (!binding) {
|
|
65
|
+
binding = {
|
|
66
|
+
signal: untypedSignal,
|
|
67
|
+
map: map,
|
|
68
|
+
active: false,
|
|
69
|
+
loadRunId: runId,
|
|
70
|
+
lastData: signal.data,
|
|
71
|
+
lastStatus: signal.status
|
|
72
|
+
};
|
|
73
|
+
bindings.set(untypedSignal, binding);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
binding.map = map;
|
|
77
|
+
binding.active = false;
|
|
78
|
+
binding.loadRunId = runId;
|
|
79
|
+
}
|
|
80
|
+
signalBindings = [...bindings.values()];
|
|
81
|
+
}
|
|
82
|
+
if (signal.status === 'idle' || signal.status === 'pending')
|
|
83
|
+
await signal.ready;
|
|
84
|
+
const value = signal.data;
|
|
85
|
+
if (binding && runId === initialLoadRunId && binding.loadRunId === runId) {
|
|
86
|
+
binding.active = true;
|
|
87
|
+
binding.lastData = value;
|
|
88
|
+
binding.lastStatus = signal.status;
|
|
89
|
+
signalBindings = bindingsBySignal ? [...bindingsBySignal.values()] : [];
|
|
90
|
+
}
|
|
91
|
+
return map(value);
|
|
92
|
+
});
|
|
93
|
+
const sourceResult = source({ linkPromise, linkSignal });
|
|
94
|
+
let nextInitial;
|
|
95
|
+
if (isPromiseLike(sourceResult)) {
|
|
96
|
+
status = 'waitingForInitialValues';
|
|
97
|
+
nextInitial = await sourceResult;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
nextInitial = sourceResult;
|
|
101
|
+
}
|
|
102
|
+
if (runId !== initialLoadRunId)
|
|
103
|
+
return;
|
|
104
|
+
if (bindingsBySignal) {
|
|
105
|
+
for (const [signal, binding] of bindingsBySignal) {
|
|
106
|
+
if (binding.loadRunId !== runId)
|
|
107
|
+
bindingsBySignal.delete(signal);
|
|
108
|
+
}
|
|
109
|
+
if (bindingsBySignal.size === 0)
|
|
110
|
+
bindingsBySignal = undefined;
|
|
111
|
+
}
|
|
112
|
+
signalBindings = bindingsBySignal ? [...bindingsBySignal.values()] : [];
|
|
113
|
+
setValues(nextInitial ?? {});
|
|
114
|
+
status = 'idle';
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (runId === initialLoadRunId) {
|
|
118
|
+
initialValuesError = error;
|
|
119
|
+
status = 'error';
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const startInitialValues = () => {
|
|
125
|
+
const task = loadInitialValues();
|
|
126
|
+
initialValuesTask = task.then(() => undefined, () => undefined);
|
|
127
|
+
return task;
|
|
128
|
+
};
|
|
129
|
+
if (config?.initialValues)
|
|
130
|
+
void startInitialValues();
|
|
33
131
|
const abortedResponse = () => ({
|
|
34
132
|
success: false,
|
|
35
133
|
response: undefined
|
|
@@ -37,10 +135,13 @@ export const createActiveForm = (onSubmit, config) => {
|
|
|
37
135
|
const resetForm = async (behavior = 'clear', invalidateSubmission = true) => {
|
|
38
136
|
if (invalidateSubmission) {
|
|
39
137
|
submitRunId += 1;
|
|
40
|
-
|
|
138
|
+
status = 'idle';
|
|
41
139
|
}
|
|
42
|
-
if (behavior !== 'reloadInitial')
|
|
140
|
+
if (behavior !== 'reloadInitial') {
|
|
43
141
|
initialLoadRunId += 1;
|
|
142
|
+
initialValuesTask = Promise.resolve();
|
|
143
|
+
initialValuesError = NO_INITIAL_VALUES_ERROR;
|
|
144
|
+
}
|
|
44
145
|
switch (behavior) {
|
|
45
146
|
case 'clear':
|
|
46
147
|
formData = {};
|
|
@@ -49,7 +150,7 @@ export const createActiveForm = (onSubmit, config) => {
|
|
|
49
150
|
formData = cloneStateValue(initial);
|
|
50
151
|
break;
|
|
51
152
|
case 'reloadInitial':
|
|
52
|
-
await
|
|
153
|
+
await startInitialValues();
|
|
53
154
|
break;
|
|
54
155
|
}
|
|
55
156
|
formErrors = {};
|
|
@@ -57,43 +158,53 @@ export const createActiveForm = (onSubmit, config) => {
|
|
|
57
158
|
const reset = async (behavior = 'clear') => resetForm(behavior, true);
|
|
58
159
|
const submit = async () => {
|
|
59
160
|
const runId = ++submitRunId;
|
|
60
|
-
pending = true;
|
|
61
161
|
try {
|
|
62
|
-
await
|
|
162
|
+
await initialValuesTask;
|
|
63
163
|
if (runId !== submitRunId)
|
|
64
164
|
return abortedResponse();
|
|
165
|
+
if (initialValuesError !== NO_INITIAL_VALUES_ERROR)
|
|
166
|
+
throw initialValuesError;
|
|
167
|
+
status = 'pending';
|
|
65
168
|
if (config?.beforeSubmit) {
|
|
66
169
|
let aborted = false;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
170
|
+
await config.beforeSubmit({
|
|
171
|
+
form: formController,
|
|
172
|
+
abort: () => {
|
|
173
|
+
aborted = true;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
if (aborted) {
|
|
177
|
+
if (runId === submitRunId)
|
|
178
|
+
status = 'idle';
|
|
179
|
+
return abortedResponse();
|
|
180
|
+
}
|
|
181
|
+
if (runId !== submitRunId)
|
|
72
182
|
return abortedResponse();
|
|
73
183
|
}
|
|
74
|
-
const result = await onSubmit(cloneStateValue(formData));
|
|
184
|
+
const result = (await onSubmit(cloneStateValue(formData)));
|
|
75
185
|
if (runId !== submitRunId)
|
|
76
186
|
return result;
|
|
77
187
|
if (result.success) {
|
|
78
|
-
await config?.onSuccess?.(result
|
|
79
|
-
if (runId
|
|
80
|
-
|
|
81
|
-
await resetForm(config?.successBehavior ?? 'default', false);
|
|
188
|
+
await config?.onSuccess?.({ form: activeForm, response: result });
|
|
189
|
+
if (runId === submitRunId)
|
|
190
|
+
status = 'success';
|
|
82
191
|
}
|
|
83
192
|
else {
|
|
84
|
-
if (result.error?.validation)
|
|
193
|
+
if (result.error?.validation)
|
|
85
194
|
formErrors = result.error.validation;
|
|
86
|
-
}
|
|
87
|
-
|
|
195
|
+
await config?.onError?.({ form: activeForm, response: result });
|
|
196
|
+
if (runId === submitRunId)
|
|
197
|
+
status = 'error';
|
|
88
198
|
}
|
|
89
199
|
return result;
|
|
90
200
|
}
|
|
91
|
-
|
|
201
|
+
catch (error) {
|
|
92
202
|
if (runId === submitRunId)
|
|
93
|
-
|
|
203
|
+
status = 'error';
|
|
204
|
+
throw error;
|
|
94
205
|
}
|
|
95
206
|
};
|
|
96
|
-
const
|
|
207
|
+
const formController = {
|
|
97
208
|
get data() {
|
|
98
209
|
return formData;
|
|
99
210
|
},
|
|
@@ -108,7 +219,7 @@ export const createActiveForm = (onSubmit, config) => {
|
|
|
108
219
|
},
|
|
109
220
|
reset
|
|
110
221
|
};
|
|
111
|
-
|
|
222
|
+
const activeForm = {
|
|
112
223
|
get data() {
|
|
113
224
|
return formData;
|
|
114
225
|
},
|
|
@@ -127,8 +238,11 @@ export const createActiveForm = (onSubmit, config) => {
|
|
|
127
238
|
get pending() {
|
|
128
239
|
return pending;
|
|
129
240
|
},
|
|
241
|
+
get status() {
|
|
242
|
+
return status;
|
|
243
|
+
},
|
|
130
244
|
submit,
|
|
131
|
-
ready,
|
|
132
245
|
reset
|
|
133
246
|
};
|
|
247
|
+
return activeForm;
|
|
134
248
|
};
|
|
@@ -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
|
|
169
|
+
refresh,
|
|
167
170
|
reset: () => {
|
|
168
171
|
currentRunId += 1;
|
|
169
172
|
abortController?.abort();
|
package/dist/svelte/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { createAsyncSignal, refreshAsyncSignal, refreshAllAsyncSignals, type AsyncSignalOptions, type AsyncSignalSvelte, type AsyncStatus, type AsyncSignalSource } from './async-signal/AsyncSignal.svelte.js';
|
|
2
|
-
export { createActiveForm } from './active-form/ActiveForm.svelte.js';
|
|
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
3
|
export { createQuery, type QueryController, type CreateQueryOptions } from './query/Query.svelte.js';
|
|
@@ -1,24 +1,55 @@
|
|
|
1
1
|
import { untrack } from 'svelte';
|
|
2
|
+
import { ObjectUtil } from '../../external/tools/index.js';
|
|
2
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
33
|
const baseInitial = cloneStateValue(initialValue);
|
|
5
34
|
let data = $state(cloneStateValue(baseInitial));
|
|
6
|
-
const includeKeys = options?.onChange?.include ?? [];
|
|
35
|
+
const includeKeys = [...new Set(options?.onChange?.include ?? [])];
|
|
7
36
|
const hasWatch = Boolean(options?.onChange?.handler) && includeKeys.length > 0;
|
|
8
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
48
|
+
trackDeep(nextValue);
|
|
49
|
+
const nextSnapshot = cloneStateValue(nextValue);
|
|
50
|
+
if (!ObjectUtil.equals(lastSnapshots.get(key), nextSnapshot)) {
|
|
20
51
|
changedKeys.push(key);
|
|
21
|
-
|
|
52
|
+
lastSnapshots.set(key, nextSnapshot);
|
|
22
53
|
}
|
|
23
54
|
});
|
|
24
55
|
if (!changedKeys.length)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@azure-net/kit",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.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",
|
|
@@ -126,8 +126,9 @@
|
|
|
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": "pnpm test:unit -- --run && pnpm test:integration && pnpm test:e2e",
|
|
131
|
+
"test": "pnpm test:unit -- --run && pnpm test:client -- --run && pnpm test:integration && pnpm test:e2e",
|
|
131
132
|
"test:e2e": "playwright test",
|
|
132
133
|
"commit": "pnpm format && git add ."
|
|
133
134
|
}
|