akanjs 3.0.0-alpha.70 → 3.0.0-alpha.71
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/client/router.ts +7 -7
- package/common/Logger.ts +280 -67
- package/common/deepObjectify.ts +3 -2
- package/common/index.ts +24 -1
- package/common/logContext.ts +27 -0
- package/common/plainFieldsOf.ts +13 -0
- package/constant/crystalize.ts +59 -39
- package/constant/dateSlot.ts +62 -0
- package/constant/getDefault.ts +16 -11
- package/constant/hydrationPlan.ts +72 -0
- package/constant/immerify.ts +4 -6
- package/constant/index.ts +2 -0
- package/constant/via.ts +14 -14
- package/fetch/client/fetchClient.ts +30 -12
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/package.json +11 -1
- package/server/akanApp.ts +84 -93
- package/server/akanServer.ts +84 -10
- package/server/console.ts +102 -1
- package/server/consoleSession.ts +49 -5
- package/server/logging/childOutputReader.ts +137 -0
- package/server/logging/hubFileSink.ts +39 -0
- package/server/logging/logControlSocket.ts +173 -0
- package/server/logging/logForwarder.ts +131 -0
- package/server/logging/logHub.ts +0 -0
- package/server/logging/logQuery.ts +130 -0
- package/server/logging/logStdoutWriter.ts +39 -0
- package/server/logging/logStreamRoute.ts +87 -0
- package/server/logging/logTailClient.ts +161 -0
- package/server/mcp/McpAuth.ts +4 -15
- package/server/mcp/McpDispatcher.ts +12 -8
- package/server/mcp/McpEventStream.ts +8 -60
- package/server/resolver/signal.resolver.ts +80 -53
- package/server/routing/apiRouter.ts +5 -3
- package/server/routing/eventStream.ts +93 -0
- package/server/routing/soloAppRoutes.ts +5 -1
- package/server/rscWorker.tsx +27 -9
- package/server/rscWorkerHost.ts +59 -8
- package/server/webRouter.ts +9 -0
- package/service/ipcTypes.ts +5 -0
- package/service/predefinedAdaptor/logging.adaptor.ts +1 -1
- package/signal/signalContext.ts +48 -15
- package/signal/trace.ts +268 -9
- package/store/action.ts +5 -6
- package/test/testServer.ts +6 -6
- package/types/common/Logger.d.ts +88 -6
- package/types/common/index.d.ts +3 -1
- package/types/common/logContext.d.ts +17 -0
- package/types/common/plainFieldsOf.d.ts +6 -0
- package/types/constant/crystalize.d.ts +3 -0
- package/types/constant/dateSlot.d.ts +14 -0
- package/types/constant/getDefault.d.ts +8 -0
- package/types/constant/hydrationPlan.d.ts +24 -0
- package/types/constant/index.d.ts +2 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/server/console.d.ts +10 -0
- package/types/server/consoleSession.d.ts +10 -0
- package/types/server/logging/childOutputReader.d.ts +40 -0
- package/types/server/logging/hubFileSink.d.ts +18 -0
- package/types/server/logging/logControlSocket.d.ts +60 -0
- package/types/server/logging/logForwarder.d.ts +37 -0
- package/types/server/logging/logHub.d.ts +70 -0
- package/types/server/logging/logQuery.d.ts +31 -0
- package/types/server/logging/logStdoutWriter.d.ts +35 -0
- package/types/server/logging/logStreamRoute.d.ts +20 -0
- package/types/server/logging/logTailClient.d.ts +42 -0
- package/types/server/mcp/McpAuth.d.ts +2 -6
- package/types/server/mcp/McpEventStream.d.ts +4 -5
- package/types/server/routing/eventStream.d.ts +23 -0
- package/types/server/routing/soloAppRoutes.d.ts +3 -1
- package/types/server/rscWorkerHost.d.ts +4 -1
- package/types/server/webRouter.d.ts +3 -1
- package/types/service/ipcTypes.d.ts +11 -0
- package/types/signal/signalContext.d.ts +16 -3
- package/types/signal/trace.d.ts +52 -2
- package/ui/Model/EditModal.tsx +1 -1
- package/ui/System/Client.tsx +1 -1
package/constant/crystalize.ts
CHANGED
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
} from "akanjs/base";
|
|
9
9
|
|
|
10
10
|
import type { FieldProps } from ".";
|
|
11
|
+
import { ConstantRegistry } from "./constantRegistry";
|
|
11
12
|
|
|
12
13
|
export type CrystalizeFunc<Model> = (self: GetStateObject<Model>, isChild?: boolean) => Model;
|
|
14
|
+
export type Converter = (value: unknown) => unknown;
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* The relation instances one hydration pass has already built, by model class and then document id.
|
|
@@ -34,58 +36,76 @@ export const withSharedInstances = <T>(hydrate: () => T): T => {
|
|
|
34
36
|
}
|
|
35
37
|
};
|
|
36
38
|
|
|
37
|
-
type ModelCls =
|
|
39
|
+
type ModelCls = new (obj?: object) => object;
|
|
38
40
|
|
|
39
41
|
const relationIdOf = (value: object): string | null => {
|
|
40
42
|
const id = (value as { id?: unknown }).id;
|
|
41
43
|
return typeof id === "string" && id ? id : null;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
|
-
const
|
|
45
|
-
const modelRef =
|
|
46
|
+
const modelConverterOf = (props: FieldProps): Converter => {
|
|
47
|
+
const modelRef = props.modelRef as unknown as ModelCls;
|
|
46
48
|
|
|
47
|
-
if (value instanceof modelRef
|
|
49
|
+
if (props.isScalar) return (value) => (value instanceof modelRef ? value : new modelRef(value as object));
|
|
50
|
+
return (value) => {
|
|
51
|
+
if (value instanceof modelRef) return value;
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const id = relationIdOf(value as object);
|
|
54
|
+
if (!sharedInstances || !id) return new modelRef(value as object);
|
|
55
|
+
const byId = sharedInstances.get(modelRef as unknown as Cls) ?? new Map<string, object>();
|
|
56
|
+
if (!sharedInstances.has(modelRef as unknown as Cls)) sharedInstances.set(modelRef as unknown as Cls, byId);
|
|
57
|
+
const shared = byId.get(id) ?? new modelRef(value as object);
|
|
58
|
+
byId.set(id, shared);
|
|
59
|
+
return shared;
|
|
60
|
+
};
|
|
56
61
|
};
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
isClass: !!field.of && !PrimitiveRegistry.has(field.of),
|
|
73
|
-
isScalar: !!field.of && PrimitiveRegistry.has(field.of),
|
|
74
|
-
isArray: false,
|
|
75
|
-
arrDepth: 0,
|
|
76
|
-
};
|
|
63
|
+
const mapConverterOf = (props: FieldProps): Converter => {
|
|
64
|
+
const of = props.of as Cls | undefined;
|
|
65
|
+
const valueConverter: Converter = of
|
|
66
|
+
? singleConverterOf({
|
|
67
|
+
...props,
|
|
68
|
+
modelRef: of as FieldProps["modelRef"],
|
|
69
|
+
isMap: false,
|
|
70
|
+
isClass: !PrimitiveRegistry.has(of),
|
|
71
|
+
isScalar: PrimitiveRegistry.has(of) || ConstantRegistry.isScalar(of),
|
|
72
|
+
isArray: false,
|
|
73
|
+
arrDepth: 0,
|
|
74
|
+
})
|
|
75
|
+
: (value) => value;
|
|
76
|
+
return (value) => {
|
|
77
77
|
|
|
78
78
|
const entries = value instanceof Map ? [...value.entries()] : Object.entries(value as Record<string, unknown>);
|
|
79
79
|
return new Map(
|
|
80
|
-
entries.map(([key, val]: [string, unknown]) => [
|
|
81
|
-
key,
|
|
82
|
-
field.of
|
|
83
|
-
? applyFnToArrayObjects(val, (v: never) => crystalize(mapValueField, v))
|
|
84
|
-
: applyFnToArrayObjects(val, crystalizeValue),
|
|
85
|
-
]),
|
|
80
|
+
entries.map(([key, val]: [string, unknown]) => [key, applyFnToArrayObjects(val, valueConverter as never)]),
|
|
86
81
|
);
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const singleConverterOf = (props: FieldProps): Converter => {
|
|
86
|
+
if (props.isMap) return mapConverterOf(props);
|
|
87
|
+
if (props.isClass) return modelConverterOf(props);
|
|
88
|
+
|
|
89
|
+
if ((props.modelRef as unknown) === Date) return (value) => dayjs(value as Date);
|
|
90
|
+
if (PrimitiveRegistry.has(props.modelRef as Cls))
|
|
91
|
+
return (value) => (props.modelRef as unknown as typeof PrimitiveScalar)._parse(value as never);
|
|
92
|
+
return (value) => value;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const converters = new WeakMap<FieldProps, Converter>();
|
|
96
|
+
|
|
97
|
+
/** The converter for one field, compiled once per `FieldProps` — which `getProps()` freezes and shares per field. */
|
|
98
|
+
export const converterOf = (props: FieldProps): Converter => {
|
|
99
|
+
const cached = converters.get(props);
|
|
100
|
+
if (cached) return cached;
|
|
101
|
+
const single = singleConverterOf(props);
|
|
102
|
+
let convert: Converter = (value) => (value === null || value === undefined ? value : single(value));
|
|
103
|
+
for (let depth = props.arrDepth; depth > 0; depth--) {
|
|
104
|
+
const inner: Converter = convert;
|
|
105
|
+
convert = (value: unknown): unknown => (Array.isArray(value) ? value.map(inner) : inner(value));
|
|
87
106
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return crystalizeValue(value);
|
|
107
|
+
converters.set(props, convert);
|
|
108
|
+
return convert;
|
|
91
109
|
};
|
|
110
|
+
|
|
111
|
+
export const crystalize = (field: FieldProps, value: unknown): unknown => converterOf(field)(value);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Dayjs, dayjs } from "akanjs/base";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A Date field stores a native `Date` under a per-key symbol and builds the `Dayjs` its type promises on first
|
|
5
|
+
* read, memoized by that `Date`. A dayjs instance is ~300 bytes and eight getter calls at construction; a `Date`
|
|
6
|
+
* is 40 bytes. A listing carries every row's dates and reads a handful of them, so this is most of what a
|
|
7
|
+
* hydrated list used to cost — see `crystalize`.
|
|
8
|
+
*
|
|
9
|
+
* The slot is a function of the key, not the class (`Symbol.for`): `applyMixins` copies a light model's accessors
|
|
10
|
+
* onto the full model's prototype, so an accessor and the `set()` that fills its slot must agree on the slot
|
|
11
|
+
* whichever class defined either one.
|
|
12
|
+
*/
|
|
13
|
+
const slotByKey = new Map<string, symbol>();
|
|
14
|
+
export const dateSlotOf = (key: string): symbol => {
|
|
15
|
+
const cached = slotByKey.get(key);
|
|
16
|
+
if (cached) return cached;
|
|
17
|
+
const slot = Symbol.for(`akan.date.${key}`);
|
|
18
|
+
slotByKey.set(key, slot);
|
|
19
|
+
return slot;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const dayjsByDate = new WeakMap<Date, Dayjs>();
|
|
23
|
+
const lazyDayjsOf = (date: Date): Dayjs => {
|
|
24
|
+
const cached = dayjsByDate.get(date);
|
|
25
|
+
if (cached) return cached;
|
|
26
|
+
const value = dayjs(date);
|
|
27
|
+
dayjsByDate.set(date, value);
|
|
28
|
+
return value;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** A caller's own `Date` is copied: the slot must not alias an object the caller can still mutate. */
|
|
32
|
+
export const toDateValue = (value: unknown): Date | null | undefined => {
|
|
33
|
+
if (value === null || value === undefined) return value;
|
|
34
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
35
|
+
if (dayjs.isDayjs(value)) return value.toDate();
|
|
36
|
+
return new Date(value as string | number);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type DateSlotHolder = Record<symbol, Date | null | undefined>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Enumerable so `for...in` — `plainFieldsOf`, `immerify`, `deepObjectify` — still sees the field. immer calls a
|
|
43
|
+
* prototype setter with the draft as `this`, so a write through the accessor stays copy-on-write, and the getter
|
|
44
|
+
* writes nothing, so a read never marks a draft modified.
|
|
45
|
+
*/
|
|
46
|
+
export const dateAccessorOf = (key: string): PropertyDescriptor => {
|
|
47
|
+
const slot = dateSlotOf(key);
|
|
48
|
+
return {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
get(this: DateSlotHolder) {
|
|
52
|
+
const date = this[slot];
|
|
53
|
+
return date === null || date === undefined ? date : lazyDayjsOf(date);
|
|
54
|
+
},
|
|
55
|
+
set(this: DateSlotHolder, value: unknown) {
|
|
56
|
+
this[slot] = toDateValue(value);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const isDateSlotField = (props: { modelRef: unknown; isArray: boolean; isMap: boolean }): boolean =>
|
|
62
|
+
props.modelRef === Date && !props.isArray && !props.isMap;
|
package/constant/getDefault.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { DEFAULT_VALUE, FIELD_META, type PrimitiveScalar } from "akanjs/base";
|
|
|
2
2
|
import type { FieldObject } from ".";
|
|
3
3
|
import type { DefaultOf } from "./types";
|
|
4
4
|
|
|
5
|
-
interface DefaultPlan {
|
|
5
|
+
export interface DefaultPlan {
|
|
6
6
|
/** Fields whose default is a value that can be shared: a primitive, `null`, or the field's own literal. */
|
|
7
7
|
shared: Record<string, unknown>;
|
|
8
8
|
/** Fields that have to be produced per call — a thunk, a fresh array, or a nested scalar record. */
|
|
9
|
-
perCall:
|
|
9
|
+
perCall: Map<string, () => unknown>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const planCache = new WeakMap<FieldObject, DefaultPlan>();
|
|
@@ -17,28 +17,33 @@ const planCache = new WeakMap<FieldObject, DefaultPlan>();
|
|
|
17
17
|
* sharing one. Only values that were already shared before this cache existed live in `shared`.
|
|
18
18
|
*/
|
|
19
19
|
export const getDefault = <T>(fieldObj: FieldObject): DefaultOf<T> => {
|
|
20
|
-
|
|
21
|
-
if (!plan) {
|
|
22
|
-
plan = buildPlan(fieldObj);
|
|
23
|
-
planCache.set(fieldObj, plan);
|
|
24
|
-
}
|
|
20
|
+
const plan = defaultPlanOf(fieldObj);
|
|
25
21
|
const result: Record<string, unknown> = { ...plan.shared };
|
|
26
22
|
for (const [key, make] of plan.perCall) result[key] = make();
|
|
27
23
|
return result as DefaultOf<T>;
|
|
28
24
|
};
|
|
29
25
|
|
|
26
|
+
/** The per-field default rules, split as above; `HydrationPlan` reads them per field so a present value skips its thunk. */
|
|
27
|
+
export const defaultPlanOf = (fieldObj: FieldObject): DefaultPlan => {
|
|
28
|
+
const cached = planCache.get(fieldObj);
|
|
29
|
+
if (cached) return cached;
|
|
30
|
+
const plan = buildPlan(fieldObj);
|
|
31
|
+
planCache.set(fieldObj, plan);
|
|
32
|
+
return plan;
|
|
33
|
+
};
|
|
34
|
+
|
|
30
35
|
const buildPlan = (fieldObj: FieldObject): DefaultPlan => {
|
|
31
36
|
const shared: Record<string, unknown> = {};
|
|
32
|
-
const perCall
|
|
37
|
+
const perCall = new Map<string, () => unknown>();
|
|
33
38
|
for (const [key, field] of Object.entries(fieldObj)) {
|
|
34
39
|
if (field.fieldType === "hidden" || field.fieldType === "secret") shared[key] = null;
|
|
35
40
|
else if (field.default !== undefined && field.default !== null) {
|
|
36
|
-
if (typeof field.default === "function") perCall.
|
|
41
|
+
if (typeof field.default === "function") perCall.set(key, field.default as () => unknown);
|
|
37
42
|
else shared[key] = field.default as object;
|
|
38
|
-
} else if (field.isArray) perCall.
|
|
43
|
+
} else if (field.isArray) perCall.set(key, () => []);
|
|
39
44
|
else if (field.nullable) shared[key] = null;
|
|
40
45
|
else if (field.isClass) {
|
|
41
|
-
if (field.isScalar) perCall.
|
|
46
|
+
if (field.isScalar) perCall.set(key, () => getDefault(field.modelRef[FIELD_META]));
|
|
42
47
|
else shared[key] = null;
|
|
43
48
|
} else shared[key] = (field.modelRef as unknown as typeof PrimitiveScalar)[DEFAULT_VALUE];
|
|
44
49
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { FIELD_META } from "akanjs/base";
|
|
2
|
+
|
|
3
|
+
import { type Converter, converterOf } from "./crystalize";
|
|
4
|
+
import { dateSlotOf, isDateSlotField, toDateValue } from "./dateSlot";
|
|
5
|
+
import type { FieldObject } from "./fieldInfo";
|
|
6
|
+
import { defaultPlanOf } from "./getDefault";
|
|
7
|
+
|
|
8
|
+
interface PlanEntry {
|
|
9
|
+
key: string;
|
|
10
|
+
/** The own property the value lands on: the key itself, or a Date field's slot symbol. */
|
|
11
|
+
target: string | symbol;
|
|
12
|
+
convert: Converter;
|
|
13
|
+
makeDefault: () => unknown;
|
|
14
|
+
}
|
|
15
|
+
type PlanTarget = Record<string | symbol, unknown>;
|
|
16
|
+
interface PlanOwner {
|
|
17
|
+
[FIELD_META]: FieldObject;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* One hydration plan per model class, compiled from `FIELD_META` on the first instance. Reading the field map,
|
|
22
|
+
* resolving `getProps()` and dispatching on the field kind used to happen per field per instance, and was most of
|
|
23
|
+
* what a listing paid after the dayjs objects — a plan turns a row into a plain loop over precomputed converters.
|
|
24
|
+
*
|
|
25
|
+
* Cached per class rather than per field map because `fullModelOf` extends a lib model's `FIELD_META` in place;
|
|
26
|
+
* `applyConstantStatics` resets the entry whenever it finishes wiring a class.
|
|
27
|
+
*/
|
|
28
|
+
export class HydrationPlan {
|
|
29
|
+
static #byClass = new WeakMap<object, HydrationPlan>();
|
|
30
|
+
static of(cls: PlanOwner): HydrationPlan {
|
|
31
|
+
const cached = HydrationPlan.#byClass.get(cls);
|
|
32
|
+
if (cached) return cached;
|
|
33
|
+
const plan = new HydrationPlan(cls[FIELD_META]);
|
|
34
|
+
HydrationPlan.#byClass.set(cls, plan);
|
|
35
|
+
return plan;
|
|
36
|
+
}
|
|
37
|
+
static reset(cls: object) {
|
|
38
|
+
HydrationPlan.#byClass.delete(cls);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#entries: PlanEntry[];
|
|
42
|
+
constructor(fieldObj: FieldObject) {
|
|
43
|
+
const defaults = defaultPlanOf(fieldObj);
|
|
44
|
+
this.#entries = Object.entries(fieldObj).map(([key, field]) => {
|
|
45
|
+
const props = field.getProps();
|
|
46
|
+
const isDateSlot = isDateSlotField(props);
|
|
47
|
+
const convert: Converter = isDateSlot ? toDateValue : converterOf(props);
|
|
48
|
+
const perCall = defaults.perCall.get(key);
|
|
49
|
+
const rawDefault = perCall ?? (() => defaults.shared[key]);
|
|
50
|
+
|
|
51
|
+
return { key, target: isDateSlot ? dateSlotOf(key) : key, convert, makeDefault: () => convert(rawDefault()) };
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Fills every field: the value when the source carries one, the field's default otherwise. */
|
|
56
|
+
construct(instance: object, source?: Record<string, unknown> | null) {
|
|
57
|
+
const target = instance as PlanTarget;
|
|
58
|
+
for (const entry of this.#entries) {
|
|
59
|
+
const value = source === undefined || source === null ? undefined : source[entry.key];
|
|
60
|
+
target[entry.target] = value === undefined ? entry.makeDefault() : entry.convert(value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Overwrites only the fields the source names; `in` rather than own keys, so another instance's accessors count. */
|
|
65
|
+
assign(instance: object, source: Record<string, unknown>) {
|
|
66
|
+
const target = instance as PlanTarget;
|
|
67
|
+
for (const entry of this.#entries) {
|
|
68
|
+
if (!(entry.key in source)) continue;
|
|
69
|
+
target[entry.target] = entry.convert(source[entry.key]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/constant/immerify.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { FIELD_META } from "akanjs/base";
|
|
2
|
+
import { plainFieldsOf } from "akanjs/common";
|
|
2
3
|
import { immerable } from "immer";
|
|
3
4
|
import type { ConstantModelRef } from ".";
|
|
4
5
|
|
|
5
6
|
export const immerify = <T extends object>(modelRef: ConstantModelRef, objOrArr: T): T => {
|
|
6
7
|
if (Array.isArray(objOrArr)) return objOrArr.map((val) => immerify(modelRef, val as object)) as T;
|
|
7
|
-
const immeredObj = Object.assign({}
|
|
8
|
-
[immerable]: true,
|
|
9
|
-
}) as Record<string, unknown>;
|
|
10
|
-
const objRecord = objOrArr as Record<string, unknown>;
|
|
8
|
+
const immeredObj = Object.assign(plainFieldsOf(objOrArr), { [immerable]: true }) as Record<string, unknown>;
|
|
11
9
|
Object.entries(modelRef[FIELD_META]).forEach(([key, field]) => {
|
|
12
|
-
if (field.isScalar && field.isClass &&
|
|
13
|
-
immeredObj[key] = immerify(field.modelRef,
|
|
10
|
+
if (field.isScalar && field.isClass && immeredObj[key])
|
|
11
|
+
immeredObj[key] = immerify(field.modelRef, immeredObj[key] as object);
|
|
14
12
|
});
|
|
15
13
|
return immeredObj as T;
|
|
16
14
|
};
|
package/constant/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export * from "./cascadePaths";
|
|
2
2
|
export * from "./constantRegistry";
|
|
3
3
|
export * from "./crystalize";
|
|
4
|
+
export * from "./dateSlot";
|
|
4
5
|
export * from "./deserialize";
|
|
5
6
|
export * from "./fieldInfo";
|
|
6
7
|
export * from "./fieldQueryMeta";
|
|
7
8
|
export * from "./getDefault";
|
|
9
|
+
export * from "./hydrationPlan";
|
|
8
10
|
export * from "./immerify";
|
|
9
11
|
export * from "./labelOf";
|
|
10
12
|
export * from "./mask";
|
package/constant/via.ts
CHANGED
|
@@ -10,12 +10,13 @@ import {
|
|
|
10
10
|
PURIFIED_VALUE,
|
|
11
11
|
SERVER_VALUE,
|
|
12
12
|
} from "akanjs/base";
|
|
13
|
-
import { applyMixins } from "akanjs/common";
|
|
13
|
+
import { applyMixins, plainFieldsOf } from "akanjs/common";
|
|
14
14
|
import { immerable } from "immer";
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { getDefault } from ".";
|
|
17
17
|
import { CascadePaths } from "./cascadePaths";
|
|
18
18
|
import { ConstantRegistry } from "./constantRegistry";
|
|
19
|
+
import { dateAccessorOf, isDateSlotField } from "./dateSlot";
|
|
19
20
|
import {
|
|
20
21
|
ConstantField,
|
|
21
22
|
type ExtractFieldInfoObject,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
field,
|
|
28
29
|
resolve,
|
|
29
30
|
} from "./fieldInfo";
|
|
31
|
+
import { HydrationPlan } from "./hydrationPlan";
|
|
30
32
|
import { makePurify, type PurifiedModel, type PurifyFunc } from "./purify";
|
|
31
33
|
import { TextFieldPaths } from "./textFieldPaths";
|
|
32
34
|
import type { BaseInsight, BaseObject, ConstantType, DefaultOf, DefaultOfSchema, NonFunctionalKeys } from "./types";
|
|
@@ -201,22 +203,16 @@ const getBaseConstantClass = (field: FieldObject, modelType: ConstantType = "sca
|
|
|
201
203
|
static enums: Set<EnumInstance> = new Set();
|
|
202
204
|
[immerable] = true;
|
|
203
205
|
constructor(obj?: Partial<unknown>) {
|
|
204
|
-
this.
|
|
205
|
-
...(this.constructor as ConstantCls).getDefault(),
|
|
206
|
-
...((obj ?? {}) as Partial<typeof this>),
|
|
207
|
-
});
|
|
206
|
+
HydrationPlan.of(this.constructor as ConstantCls).construct(this, obj as Record<string, unknown> | undefined);
|
|
208
207
|
}
|
|
209
208
|
set(obj: Partial<typeof this>) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (!(this.constructor as ConstantCls)[FIELD_META][key] as unknown as object | undefined) return;
|
|
213
|
-
const field = (this.constructor as ConstantCls)[FIELD_META][key];
|
|
214
|
-
if (!field) throw new Error(`Field ${key} not found`);
|
|
215
|
-
const fieldProp = field.getProps();
|
|
216
|
-
(this as Record<string, unknown>)[key] = crystalize(fieldProp, value);
|
|
217
|
-
});
|
|
209
|
+
HydrationPlan.of(this.constructor as ConstantCls).assign(this, obj as Record<string, unknown>);
|
|
218
210
|
return this;
|
|
219
211
|
}
|
|
212
|
+
|
|
213
|
+
toJSON() {
|
|
214
|
+
return plainFieldsOf(this);
|
|
215
|
+
}
|
|
220
216
|
}
|
|
221
217
|
return BaseConstant as unknown as ConstantCls;
|
|
222
218
|
};
|
|
@@ -453,6 +449,10 @@ const applyConstantStatics = <Model>(model: ConstantCls<Model>, fieldMap: FieldO
|
|
|
453
449
|
});
|
|
454
450
|
model.text.collect(fieldMap);
|
|
455
451
|
model.cascade.collect(fieldMap);
|
|
452
|
+
|
|
453
|
+
for (const [key, field] of Object.entries(model[FIELD_META]))
|
|
454
|
+
if (isDateSlotField(field.getProps())) Object.defineProperty(model.prototype, key, dateAccessorOf(key));
|
|
455
|
+
HydrationPlan.reset(model);
|
|
456
456
|
return model as unknown as ConstantCls<Model>;
|
|
457
457
|
};
|
|
458
458
|
|
|
@@ -19,7 +19,12 @@ import type {
|
|
|
19
19
|
} from "akanjs/signal";
|
|
20
20
|
import { agentTurnConstant } from "../agentTurn";
|
|
21
21
|
import type { ClientSignal, FetchClientType, FetchSignalInput, MergeAllFetchTypes, SliceMeta } from "../fetchType";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
getRequestStore,
|
|
24
|
+
memoizeRequestQuery,
|
|
25
|
+
cookies as requestCookies,
|
|
26
|
+
headers as requestHeaders,
|
|
27
|
+
} from "../requestStorage";
|
|
23
28
|
import type { GetSliceMetaObjFromDatabaseSignals } from "../types";
|
|
24
29
|
import { type ErrorConstructor, HttpClient } from "./httpClient";
|
|
25
30
|
import { WsClient } from "./wsClient";
|
|
@@ -311,8 +316,9 @@ export class FetchClient {
|
|
|
311
316
|
const response = baseUrl
|
|
312
317
|
? await requestQuery()
|
|
313
318
|
: await memoizeRequestQuery(FetchClient.#makeRequestQueryCacheKey(this.origin, url, headers), requestQuery);
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
|
|
320
|
+
const payload = !baseUrl && getRequestStore() ? FetchClient.#deepCopy(response) : response;
|
|
321
|
+
return parseReturn(payload, { crystalize: option?.crystalize ?? true });
|
|
316
322
|
};
|
|
317
323
|
return queryFn;
|
|
318
324
|
}
|
|
@@ -665,10 +671,6 @@ export class FetchClient {
|
|
|
665
671
|
listFn(...fetchQueryArgs, skip, limit, sort, { ...option, crystalize: false }),
|
|
666
672
|
fetchInsight ? insightFn(...fetchQueryArgs, { ...option, crystalize: false }) : null,
|
|
667
673
|
])) as unknown as [BaseObject[], BaseInsight];
|
|
668
|
-
const modelList = new DataList(
|
|
669
|
-
withSharedInstances(() => modelObjList.map((modelObj) => new cnst.light(modelObj))),
|
|
670
|
-
);
|
|
671
|
-
const modelInsight = new cnst.insight(modelObjInsight);
|
|
672
674
|
const lastPage = modelObjInsight?.count
|
|
673
675
|
? Math.max(Math.floor((modelObjInsight.count - 1) / (limit || 20)) + 1, 1)
|
|
674
676
|
: 1;
|
|
@@ -686,11 +688,27 @@ export class FetchClient {
|
|
|
686
688
|
[`sortOf${capRefName}`]: sort,
|
|
687
689
|
[`${refName}InitAt`]: new Date(),
|
|
688
690
|
};
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
691
|
+
|
|
692
|
+
let modelList: DataList<BaseObject> | undefined;
|
|
693
|
+
let modelInsight: object | undefined;
|
|
694
|
+
return Object.defineProperties({ [`${refName}Init${capSuffix}`]: serverInit } as Record<string, unknown>, {
|
|
695
|
+
[`${refName}List${capSuffix}`]: {
|
|
696
|
+
enumerable: true,
|
|
697
|
+
get: () => {
|
|
698
|
+
modelList ??= new DataList(
|
|
699
|
+
withSharedInstances(() => modelObjList.map((modelObj) => new cnst.light(modelObj) as BaseObject)),
|
|
700
|
+
);
|
|
701
|
+
return modelList;
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
[`${refName}Insight${capSuffix}`]: {
|
|
705
|
+
enumerable: true,
|
|
706
|
+
get: () => {
|
|
707
|
+
modelInsight ??= new cnst.insight(modelObjInsight);
|
|
708
|
+
return modelInsight;
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
});
|
|
694
712
|
});
|
|
695
713
|
this.#setHandlerFactory(names.getInit, () => async (...args: unknown[]) => {
|
|
696
714
|
const initFn = this.#requireHandler<(...args: unknown[]) => Promise<Record<string, unknown>>>(
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akanjs",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.71",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -180,6 +180,16 @@
|
|
|
180
180
|
"types": "./types/server/lifecycle/portInUse.d.ts",
|
|
181
181
|
"import": "./server/lifecycle/portInUse.ts",
|
|
182
182
|
"default": "./server/lifecycle/portInUse.ts"
|
|
183
|
+
},
|
|
184
|
+
"./server/logging/logQuery": {
|
|
185
|
+
"types": "./types/server/logging/logQuery.d.ts",
|
|
186
|
+
"import": "./server/logging/logQuery.ts",
|
|
187
|
+
"default": "./server/logging/logQuery.ts"
|
|
188
|
+
},
|
|
189
|
+
"./server/logging/logTailClient": {
|
|
190
|
+
"types": "./types/server/logging/logTailClient.d.ts",
|
|
191
|
+
"import": "./server/logging/logTailClient.ts",
|
|
192
|
+
"default": "./server/logging/logTailClient.ts"
|
|
183
193
|
}
|
|
184
194
|
},
|
|
185
195
|
"dependencies": {
|