akanjs 3.0.0-alpha.90 → 3.0.0-alpha.91
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/common/types.ts +7 -0
- package/fetch/client/fetchClient.ts +21 -7
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/package.json +1 -1
- package/server/di/diLifecycle.ts +1 -1
- package/server/resolver/signal.resolver.ts +12 -13
- package/signal/endpointInfo.ts +16 -0
- package/store/action.ts +16 -18
- package/types/common/types.d.ts +7 -0
- package/types/signal/endpointInfo.d.ts +10 -0
package/common/types.ts
CHANGED
|
@@ -6,6 +6,13 @@ export interface FetchPolicy<Returns = unknown> {
|
|
|
6
6
|
token?: string;
|
|
7
7
|
partial?: string[];
|
|
8
8
|
timeout?: number;
|
|
9
|
+
/**
|
|
10
|
+
* A `pubsub` subscription only: called after the room has been resubscribed following a dropped connection.
|
|
11
|
+
*
|
|
12
|
+
* Whatever was published while the socket was down is gone, and a room cannot say which messages those were, so
|
|
13
|
+
* a subscriber that has to stay correct reloads here instead of carrying on from a gap it cannot see.
|
|
14
|
+
*/
|
|
15
|
+
onResync?: () => void;
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
export type SnakeCase<S extends string> = S extends `${infer T}_${infer U}` ? `${Lowercase<T>}_${SnakeCase<U>}` : S;
|
|
@@ -373,12 +373,15 @@ export class FetchClient {
|
|
|
373
373
|
};
|
|
374
374
|
wrappedListeners.set(handleEvent, wrapped);
|
|
375
375
|
const ws = this.#resolveWs(fetchPolicy?.origin);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
376
|
+
const handleResync = fetchPolicy?.onResync;
|
|
377
|
+
ws.subscribe({ key, data, handleEvent: wrapped, handleResync });
|
|
378
|
+
return () =>
|
|
379
|
+
ws.unsubscribe({
|
|
380
|
+
key,
|
|
381
|
+
data,
|
|
382
|
+
handleEvent: wrappedListeners.get(handleEvent) ?? handleEvent,
|
|
383
|
+
handleResync,
|
|
384
|
+
});
|
|
382
385
|
};
|
|
383
386
|
});
|
|
384
387
|
return;
|
|
@@ -624,6 +627,7 @@ export class FetchClient {
|
|
|
624
627
|
const names = {
|
|
625
628
|
list: `${refName}List${capSuffix}`,
|
|
626
629
|
insight: `${refName}Insight${capSuffix}`,
|
|
630
|
+
live: `${refName}Live${capSuffix}`,
|
|
627
631
|
};
|
|
628
632
|
|
|
629
633
|
const mcp = slice.mcp === false ? { mcp: false as const } : {};
|
|
@@ -643,6 +647,15 @@ export class FetchClient {
|
|
|
643
647
|
...mcp,
|
|
644
648
|
},
|
|
645
649
|
};
|
|
650
|
+
|
|
651
|
+
if (slice.live)
|
|
652
|
+
endpoint[names.live] = {
|
|
653
|
+
type: "pubsub",
|
|
654
|
+
args: slice.args.map((arg) => ({ ...arg, type: "room" as const })),
|
|
655
|
+
returns: { refName: "Any" },
|
|
656
|
+
guards: slice.guards,
|
|
657
|
+
mcp: false,
|
|
658
|
+
};
|
|
646
659
|
return endpoint;
|
|
647
660
|
}
|
|
648
661
|
#registerSlice(refName: string, suffix: string, slice: SerializedSlice, prefix?: string) {
|
|
@@ -657,8 +670,9 @@ export class FetchClient {
|
|
|
657
670
|
};
|
|
658
671
|
|
|
659
672
|
const endpoint = FetchClient.getEndpointFromSlice(refName, suffix, slice);
|
|
673
|
+
|
|
660
674
|
Object.entries(endpoint).forEach(([key, value]) => {
|
|
661
|
-
this.#
|
|
675
|
+
this.#registerEndpoint(key, value, prefix);
|
|
662
676
|
});
|
|
663
677
|
|
|
664
678
|
const argLength = slice.args.length;
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/server/di/diLifecycle.ts
CHANGED
|
@@ -662,7 +662,7 @@ export class DiLifecycle {
|
|
|
662
662
|
},
|
|
663
663
|
})),
|
|
664
664
|
);
|
|
665
|
-
if (liveKeys.length) this.logger.info(`Live sync: ${liveKeys.length}
|
|
665
|
+
if (liveKeys.length) this.logger.info(`Live sync: ${liveKeys.length} live slice(s) — ${liveKeys.join(", ")}`);
|
|
666
666
|
return { routes, wsRoutes, routeOptions };
|
|
667
667
|
}
|
|
668
668
|
|
|
@@ -375,7 +375,7 @@ export class SignalResolver {
|
|
|
375
375
|
);
|
|
376
376
|
SignalResolver.#assertLiveSort(refName, key, sliceInfo);
|
|
377
377
|
const liveKey = `${refName}Live${capitalizedKey}`;
|
|
378
|
-
|
|
378
|
+
const liveBuilder = (builder as any).pubsub(Any, {
|
|
379
379
|
guards: sliceCls.getGuards,
|
|
380
380
|
mcp: false,
|
|
381
381
|
live: {
|
|
@@ -386,18 +386,17 @@ export class SignalResolver {
|
|
|
386
386
|
payload: sliceInfo.liveOption.payload,
|
|
387
387
|
} satisfies LiveEndpointOption,
|
|
388
388
|
});
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
...requestArgs: any
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
);
|
|
400
|
-
});
|
|
389
|
+
endpointObj[liveKey] = liveBuilder
|
|
390
|
+
._addRoomArgs(sliceInfo.args)
|
|
391
|
+
._addInternalArgs(sliceInfo.internalArgs)
|
|
392
|
+
.exec(async function (this: any, ...requestArgs: any) {
|
|
393
|
+
const args = requestArgs.slice(0, argLength);
|
|
394
|
+
const internalArgs = requestArgs.slice(argLength);
|
|
395
|
+
return assertSliceQuery(
|
|
396
|
+
await sliceInfo.execFn?.apply(this, [...args, ...internalArgs, documentQueryHelper]),
|
|
397
|
+
key,
|
|
398
|
+
);
|
|
399
|
+
});
|
|
401
400
|
}
|
|
402
401
|
|
|
403
402
|
const insightKey = `${refName}Insight${capitalizedKey}`;
|
package/signal/endpointInfo.ts
CHANGED
|
@@ -299,6 +299,22 @@ export class EndpointInfo<
|
|
|
299
299
|
Nullable
|
|
300
300
|
>;
|
|
301
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Retypes a slice's own arguments as this room's arguments, keeping each one's nullability.
|
|
304
|
+
*
|
|
305
|
+
* Not `_addArgs`, which would route a `search` argument back to `.search()`. And not `.room()` per argument:
|
|
306
|
+
* that refuses a nullable argument in anything but the last position, which is the right rule for a URL and a
|
|
307
|
+
* meaningless one for a room — the arguments travel as a positional array with explicit nulls, so a missing one
|
|
308
|
+
* is unambiguous. Nullability is preserved because dropping it would make an absent optional argument fail to
|
|
309
|
+
* deserialize on the way in.
|
|
310
|
+
*/
|
|
311
|
+
_addRoomArgs(args: ArgInfo<EndpointArgProps<boolean>>[]) {
|
|
312
|
+
for (const arg of args) {
|
|
313
|
+
this.argNames.push(arg.name);
|
|
314
|
+
this.args.push({ ...arg, type: "room" });
|
|
315
|
+
}
|
|
316
|
+
return this;
|
|
317
|
+
}
|
|
302
318
|
_addInternalArgs(args: InternalArgInfo<boolean>[]) {
|
|
303
319
|
for (const arg of args) this.with(arg.argRef, arg.option);
|
|
304
320
|
return this;
|
package/store/action.ts
CHANGED
|
@@ -1017,12 +1017,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
|
|
|
1017
1017
|
|
|
1018
1018
|
const requests = new SliceRequest();
|
|
1019
1019
|
|
|
1020
|
-
let liveWatch: {
|
|
1021
|
-
key: string;
|
|
1022
|
-
data: unknown[];
|
|
1023
|
-
handleEvent: (data: unknown) => void;
|
|
1024
|
-
handleResync: () => void;
|
|
1025
|
-
} | null = null;
|
|
1020
|
+
let liveWatch: { signature: string; dispose: () => void } | null = null;
|
|
1026
1021
|
const namesOfSlice: { [key in SliceActionKey | SliceStateKey | "modelList"]: string } = {
|
|
1027
1022
|
defaultModel: SliceName.replace(names.Model, names.defaultModel),
|
|
1028
1023
|
modelInsight: sliceName.replace(names.model, names.modelInsight),
|
|
@@ -1399,24 +1394,27 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
|
|
|
1399
1394
|
*/
|
|
1400
1395
|
[namesOfSlice.watchLiveModel]: function (this: SetGet, queryArgs: unknown[] | null) {
|
|
1401
1396
|
if (!slice.live) return;
|
|
1402
|
-
const
|
|
1403
|
-
const
|
|
1404
|
-
if (liveWatch &&
|
|
1405
|
-
|
|
1397
|
+
const args = queryArgs ? expandQueryArgs(normalizeQueryArgs(queryArgs, slice.args), slice.args) : null;
|
|
1398
|
+
const signature = args ? JSON.stringify(args) : null;
|
|
1399
|
+
if (liveWatch && signature !== liveWatch.signature) {
|
|
1400
|
+
liveWatch.dispose();
|
|
1406
1401
|
liveWatch = null;
|
|
1407
1402
|
}
|
|
1408
|
-
if (!
|
|
1403
|
+
if (!args || liveWatch) return;
|
|
1409
1404
|
const self = this as unknown as DynamicRecord;
|
|
1410
1405
|
const apply = self[namesOfSlice.applyLiveModel] as (event: unknown) => void;
|
|
1411
1406
|
const refresh = self[namesOfSlice.refreshModel] as (form: object) => Promise<void>;
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1407
|
+
|
|
1408
|
+
const subscribe = fetch[`subscribe${capitalize(sliceName.replace(names.model, `${names.model}Live`))}`] as
|
|
1409
|
+
| ((...args: unknown[]) => () => void)
|
|
1410
|
+
| undefined;
|
|
1411
|
+
if (!subscribe) return;
|
|
1412
|
+
const dispose = subscribe(...args, (event: unknown) => apply(event), {
|
|
1413
|
+
crystalize: false,
|
|
1416
1414
|
|
|
1417
|
-
|
|
1418
|
-
};
|
|
1419
|
-
|
|
1415
|
+
onResync: () => void refresh({ invalidate: true }),
|
|
1416
|
+
});
|
|
1417
|
+
liveWatch = { signature: signature ?? "", dispose };
|
|
1420
1418
|
},
|
|
1421
1419
|
};
|
|
1422
1420
|
return Object.assign(acc, singleSliceAction);
|
package/types/common/types.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ export interface FetchPolicy<Returns = unknown> {
|
|
|
6
6
|
token?: string;
|
|
7
7
|
partial?: string[];
|
|
8
8
|
timeout?: number;
|
|
9
|
+
/**
|
|
10
|
+
* A `pubsub` subscription only: called after the room has been resubscribed following a dropped connection.
|
|
11
|
+
*
|
|
12
|
+
* Whatever was published while the socket was down is gone, and a room cannot say which messages those were, so
|
|
13
|
+
* a subscriber that has to stay correct reloads here instead of carrying on from a gap it cannot see.
|
|
14
|
+
*/
|
|
15
|
+
onResync?: () => void;
|
|
9
16
|
}
|
|
10
17
|
export type SnakeCase<S extends string> = S extends `${infer T}_${infer U}` ? `${Lowercase<T>}_${SnakeCase<U>}` : S;
|
|
11
18
|
export type SnakeCaseObj<T> = {
|
|
@@ -64,6 +64,16 @@ export declare class EndpointInfo<ReqType extends EndpointType = EndpointType, S
|
|
|
64
64
|
search<ArgName extends string, ExplicitType = unknown, Arg extends ConstantFieldTypeInput = PlainTypeToFieldType<ExplicitType>, _ArgType = unknown extends ExplicitType ? FieldToValue<Arg> : ExplicitType, _ClientArg = PurifiedModel<_ArgType>, _ServerArg = DocumentModel<_ArgType>>(name: string, arg: Arg, option?: Omit<EndpointArgProps, "nullable">): EndpointInfo<ReqType, Srvs, [...ArgNames, ArgName], [...Args, arg?: _ClientArg | null], InternalArgs, [...ServerArgs, arg: _ServerArg | undefined], Returns, ClientReturns, ServerReturns, Nullable>;
|
|
65
65
|
_addArgs(args: ArgInfo<EndpointArgProps<boolean>>[]): this;
|
|
66
66
|
with<ArgType, Optional extends boolean = false>(argRef: InternalArgCls<ArgType>, option?: InternalArgProps<Optional>): EndpointInfo<ReqType, Srvs, ArgNames, Args, [...InternalArgs, arg: NonNullable<ArgType> | (Optional extends true ? null : never)], ServerArgs, Returns, ClientReturns, ServerReturns, Nullable>;
|
|
67
|
+
/**
|
|
68
|
+
* Retypes a slice's own arguments as this room's arguments, keeping each one's nullability.
|
|
69
|
+
*
|
|
70
|
+
* Not `_addArgs`, which would route a `search` argument back to `.search()`. And not `.room()` per argument:
|
|
71
|
+
* that refuses a nullable argument in anything but the last position, which is the right rule for a URL and a
|
|
72
|
+
* meaningless one for a room — the arguments travel as a positional array with explicit nulls, so a missing one
|
|
73
|
+
* is unambiguous. Nullability is preserved because dropping it would make an absent optional argument fail to
|
|
74
|
+
* deserialize on the way in.
|
|
75
|
+
*/
|
|
76
|
+
_addRoomArgs(args: ArgInfo<EndpointArgProps<boolean>>[]): this;
|
|
67
77
|
_addInternalArgs(args: InternalArgInfo<boolean>[]): this;
|
|
68
78
|
exec<ExecFn extends (this: Srvs, ...args: [...ServerArgs, ...InternalArgs]) => ReqType extends "pubsub" ? Promise<void> | void : ReqType extends "prompt" ? PromiseOrObject<PromptResult> : PromiseOrObject<DocumentModel<FieldToValue<Returns>> | (Nullable extends true ? null | undefined : never)>>(execFn: ExecFn): EndpointInfo<ReqType, Srvs, ArgNames, Args, InternalArgs, ServerArgs, Returns, FieldToValue<Returns>, ReturnType<ExecFn>, Nullable>;
|
|
69
79
|
getPath(key: string): string;
|