@wovin/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/applog/applog-utils.d.ts +15 -0
- package/dist/applog/applog-utils.d.ts.map +1 -1
- package/dist/applog/datom-types.d.ts +63 -7
- package/dist/applog/datom-types.d.ts.map +1 -1
- package/dist/applog.js +7 -1
- package/dist/blockstore.js +2 -0
- package/dist/blockstore.js.map +1 -1
- package/dist/{chunk-L5EEEGE6.js → chunk-2OXLPZQI.js} +747 -679
- package/dist/chunk-2OXLPZQI.js.map +1 -0
- package/dist/{chunk-QZXKQCAY.js → chunk-2PJFLZRC.js} +7 -2
- package/dist/{chunk-QZXKQCAY.js.map → chunk-2PJFLZRC.js.map} +1 -1
- package/dist/chunk-64EJIJAJ.js +17 -0
- package/dist/chunk-64EJIJAJ.js.map +1 -0
- package/dist/chunk-7QEGHKR4.js +17 -0
- package/dist/chunk-7QEGHKR4.js.map +1 -0
- package/dist/{chunk-PD3C7XUM.js → chunk-EHO2BFFY.js} +2 -2
- package/dist/chunk-ICBK7NC4.js +27 -0
- package/dist/chunk-ICBK7NC4.js.map +1 -0
- package/dist/{chunk-CPSDKFBG.js → chunk-OKXRRWNS.js} +5 -14
- package/dist/chunk-OKXRRWNS.js.map +1 -0
- package/dist/{chunk-3WZVG277.js → chunk-Q4EMPWA3.js} +17 -9
- package/dist/chunk-Q4EMPWA3.js.map +1 -0
- package/dist/{chunk-J2FDHGOZ.js → chunk-VGIACGWX.js} +3 -3
- package/dist/{chunk-3JZMOEOD.js → chunk-WVW4YXB5.js} +2 -2
- package/dist/chunk-XF4DWOAE.js +25 -0
- package/dist/chunk-XF4DWOAE.js.map +1 -0
- package/dist/index.js +17 -9
- package/dist/ipfs/car.d.ts.map +1 -1
- package/dist/ipfs.js +4 -4
- package/dist/ipns/gateway-resolver.d.ts +21 -0
- package/dist/ipns/gateway-resolver.d.ts.map +1 -0
- package/dist/ipns/ipns-record.d.ts +28 -7
- package/dist/ipns/ipns-record.d.ts.map +1 -1
- package/dist/ipns/ipns-w3name.d.ts +15 -0
- package/dist/ipns/ipns-w3name.d.ts.map +1 -0
- package/dist/ipns/ipns-watcher.d.ts +190 -0
- package/dist/ipns/ipns-watcher.d.ts.map +1 -0
- package/dist/ipns.d.ts +3 -0
- package/dist/ipns.d.ts.map +1 -1
- package/dist/ipns.js +488 -8
- package/dist/ipns.js.map +1 -1
- package/dist/pubsub/snap-push.d.ts +2 -2
- package/dist/pubsub/snap-push.d.ts.map +1 -1
- package/dist/pubsub.js +4 -4
- package/dist/query/basic.d.ts +3 -3
- package/dist/query/basic.d.ts.map +1 -1
- package/dist/query/entity-collection.d.ts.map +1 -1
- package/dist/query/matchers.d.ts +12 -1
- package/dist/query/matchers.d.ts.map +1 -1
- package/dist/query.js +7 -5
- package/dist/retrieve.js +4 -4
- package/dist/thread/indexes.d.ts +3 -2
- package/dist/thread/indexes.d.ts.map +1 -1
- package/dist/thread.js +1 -1
- package/dist/viewmodel/adapters/arktype.d.ts +33 -0
- package/dist/viewmodel/adapters/arktype.d.ts.map +1 -0
- package/dist/viewmodel/adapters/arktype.js +7 -0
- package/dist/viewmodel/adapters/arktype.js.map +1 -0
- package/dist/viewmodel/adapters/typebox.d.ts +35 -0
- package/dist/viewmodel/adapters/typebox.d.ts.map +1 -0
- package/dist/viewmodel/adapters/typebox.js +7 -0
- package/dist/viewmodel/adapters/typebox.js.map +1 -0
- package/dist/viewmodel/adapters/typia.d.ts +40 -0
- package/dist/viewmodel/adapters/typia.d.ts.map +1 -0
- package/dist/viewmodel/adapters/typia.js +7 -0
- package/dist/viewmodel/adapters/typia.js.map +1 -0
- package/dist/viewmodel/adapters/zod.d.ts +30 -0
- package/dist/viewmodel/adapters/zod.d.ts.map +1 -0
- package/dist/viewmodel/adapters/zod.js +7 -0
- package/dist/viewmodel/adapters/zod.js.map +1 -0
- package/dist/viewmodel/builder.d.ts +40 -0
- package/dist/viewmodel/builder.d.ts.map +1 -0
- package/dist/viewmodel/examples/all-adapters.d.ts +26 -0
- package/dist/viewmodel/examples/all-adapters.d.ts.map +1 -0
- package/dist/viewmodel/factory.d.ts +38 -0
- package/dist/viewmodel/factory.d.ts.map +1 -0
- package/dist/viewmodel/index.d.ts +10 -0
- package/dist/viewmodel/index.d.ts.map +1 -0
- package/dist/viewmodel/index.js +313 -0
- package/dist/viewmodel/index.js.map +1 -0
- package/dist/viewmodel/schema-adapter.d.ts +16 -0
- package/dist/viewmodel/schema-adapter.d.ts.map +1 -0
- package/dist/viewmodel/types.d.ts +97 -0
- package/dist/viewmodel/types.d.ts.map +1 -0
- package/package.json +29 -3
- package/src/applog/applog-utils.ts +48 -4
- package/src/applog/datom-types.ts +24 -5
- package/src/applog/object-values.test.ts +106 -0
- package/src/ipfs/car.ts +8 -2
- package/src/ipns/gateway-resolver.ts +63 -0
- package/src/ipns/ipns-record.ts +68 -17
- package/src/ipns/ipns-w3name.ts +103 -0
- package/src/ipns/ipns-watcher.ts +607 -0
- package/src/ipns.ts +3 -0
- package/src/pubsub/snap-push.ts +8 -6
- package/src/query/entity-collection.ts +2 -1
- package/src/query/matchers.ts +23 -1
- package/src/thread/basic.ts +2 -2
- package/src/thread/indexes.ts +15 -9
- package/src/viewmodel/adapters/arktype.ts +44 -0
- package/src/viewmodel/adapters/typebox.ts +59 -0
- package/src/viewmodel/adapters/typia.ts +50 -0
- package/src/viewmodel/adapters/zod.ts +55 -0
- package/src/viewmodel/builder.ts +71 -0
- package/src/viewmodel/examples/all-adapters.ts +206 -0
- package/src/viewmodel/factory.ts +330 -0
- package/src/viewmodel/index.ts +22 -0
- package/src/viewmodel/schema-adapter.ts +27 -0
- package/src/viewmodel/types.ts +152 -0
- package/dist/chunk-3WZVG277.js.map +0 -1
- package/dist/chunk-CPSDKFBG.js.map +0 -1
- package/dist/chunk-L5EEEGE6.js.map +0 -1
- /package/dist/{chunk-PD3C7XUM.js.map → chunk-EHO2BFFY.js.map} +0 -0
- /package/dist/{chunk-J2FDHGOZ.js.map → chunk-VGIACGWX.js.map} +0 -0
- /package/dist/{chunk-3JZMOEOD.js.map → chunk-WVW4YXB5.js.map} +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createArkTypeAdapter
|
|
3
|
+
} from "../chunk-64EJIJAJ.js";
|
|
4
|
+
import {
|
|
5
|
+
createTypeBoxAdapter
|
|
6
|
+
} from "../chunk-ICBK7NC4.js";
|
|
7
|
+
import {
|
|
8
|
+
createTypiaAdapter
|
|
9
|
+
} from "../chunk-7QEGHKR4.js";
|
|
10
|
+
import {
|
|
11
|
+
createZodAdapter
|
|
12
|
+
} from "../chunk-XF4DWOAE.js";
|
|
13
|
+
import {
|
|
14
|
+
lastWriteWins,
|
|
15
|
+
liveEntityAt
|
|
16
|
+
} from "../chunk-2PJFLZRC.js";
|
|
17
|
+
import {
|
|
18
|
+
EntityID_LENGTH,
|
|
19
|
+
SubscribableImpl,
|
|
20
|
+
dateNowIso,
|
|
21
|
+
ensureTsPvAndFinalizeApplog,
|
|
22
|
+
getHashID,
|
|
23
|
+
isInitEvent,
|
|
24
|
+
rollingFilter
|
|
25
|
+
} from "../chunk-2OXLPZQI.js";
|
|
26
|
+
import "../chunk-ZAADLBSB.js";
|
|
27
|
+
|
|
28
|
+
// src/viewmodel/builder.ts
|
|
29
|
+
var ObjectBuilder = class _ObjectBuilder {
|
|
30
|
+
constructor(_data, en = null, _atPrefix = null, _atOverrides = {}) {
|
|
31
|
+
this._data = _data;
|
|
32
|
+
this.en = en;
|
|
33
|
+
this._atPrefix = _atPrefix;
|
|
34
|
+
this._atOverrides = _atOverrides;
|
|
35
|
+
}
|
|
36
|
+
_data;
|
|
37
|
+
en;
|
|
38
|
+
_atPrefix;
|
|
39
|
+
_atOverrides;
|
|
40
|
+
/**
|
|
41
|
+
* Create a new builder with initial data.
|
|
42
|
+
*/
|
|
43
|
+
static create(init = {}, en, atPrefix, atOverrides) {
|
|
44
|
+
return new _ObjectBuilder(init, en, atPrefix, atOverrides);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Update the data being built.
|
|
48
|
+
*/
|
|
49
|
+
update(updateObj) {
|
|
50
|
+
Object.assign(this._data, updateObj);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the applogs for this entity, optionally resolving against a thread.
|
|
55
|
+
*/
|
|
56
|
+
build(thread) {
|
|
57
|
+
const atPrefix = this._atPrefix;
|
|
58
|
+
const applogs = [];
|
|
59
|
+
for (const [key, value] of Object.entries(this._data)) {
|
|
60
|
+
if (value === void 0) continue;
|
|
61
|
+
const atOverride = this._atOverrides[key];
|
|
62
|
+
const at = atOverride ?? (atPrefix ? `${atPrefix}/${String(key)}` : String(key));
|
|
63
|
+
applogs.push({ en: this.en, at, vl: value });
|
|
64
|
+
}
|
|
65
|
+
return applogs;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get the raw data being built.
|
|
69
|
+
*/
|
|
70
|
+
get data() {
|
|
71
|
+
return { ...this._data };
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/viewmodel/types.ts
|
|
76
|
+
var GlobalVMInstances = /* @__PURE__ */ new WeakMap();
|
|
77
|
+
function getInstancesForThread(thread) {
|
|
78
|
+
let threadMap = GlobalVMInstances.get(thread);
|
|
79
|
+
if (!threadMap) {
|
|
80
|
+
threadMap = /* @__PURE__ */ new Map();
|
|
81
|
+
GlobalVMInstances.set(thread, threadMap);
|
|
82
|
+
}
|
|
83
|
+
return threadMap;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/viewmodel/factory.ts
|
|
87
|
+
var DefaultSignalAdapter = {
|
|
88
|
+
createGetter(subscribable) {
|
|
89
|
+
subscribable.subscribe(() => {
|
|
90
|
+
});
|
|
91
|
+
return () => subscribable.value;
|
|
92
|
+
},
|
|
93
|
+
createWritable(initial) {
|
|
94
|
+
let current = initial;
|
|
95
|
+
return [() => current, (v) => {
|
|
96
|
+
current = v;
|
|
97
|
+
}];
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
function makeCurrent(thread, fn) {
|
|
101
|
+
if (fn) return fn(thread);
|
|
102
|
+
return lastWriteWins(thread, { tolerateAlreadyFiltered: true });
|
|
103
|
+
}
|
|
104
|
+
function defaultPersistApplogs(thread, applogs) {
|
|
105
|
+
const finalized = applogs.map((log) => ensureTsPvAndFinalizeApplog(log, thread));
|
|
106
|
+
const writable = thread;
|
|
107
|
+
if (typeof writable.insertRaw === "function") {
|
|
108
|
+
writable.insertRaw(finalized);
|
|
109
|
+
} else if (typeof writable.insert === "function") {
|
|
110
|
+
writable.insert(applogs);
|
|
111
|
+
} else {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function createViewModelFactory(options) {
|
|
115
|
+
const {
|
|
116
|
+
adapter,
|
|
117
|
+
entityPrefix = adapter.getEntityPrefix(),
|
|
118
|
+
entityIdLength = EntityID_LENGTH,
|
|
119
|
+
generateEntityId,
|
|
120
|
+
vmName = entityPrefix,
|
|
121
|
+
signalAdapter = DefaultSignalAdapter
|
|
122
|
+
} = options;
|
|
123
|
+
const attrDefs = adapter.getAttributeDefs();
|
|
124
|
+
const defaults = adapter.getDefaults();
|
|
125
|
+
const genId = generateEntityId ?? ((data) => getHashID({ ...data, ts: data.ts ?? dateNowIso() }, entityIdLength));
|
|
126
|
+
const persistFn = (thread, applogs) => {
|
|
127
|
+
defaultPersistApplogs(thread, applogs);
|
|
128
|
+
};
|
|
129
|
+
class ViewModel {
|
|
130
|
+
constructor(en, thread, skipInit) {
|
|
131
|
+
this.en = en;
|
|
132
|
+
this.thread = thread;
|
|
133
|
+
if (skipInit) return;
|
|
134
|
+
const currentThread = makeCurrent(thread, options.makeCurrentThread);
|
|
135
|
+
for (const attr of attrDefs) {
|
|
136
|
+
if (attr.name === "en") continue;
|
|
137
|
+
const attrName = attr.name;
|
|
138
|
+
const atPath = attr.atPath;
|
|
139
|
+
const defaultValue = attr.defaultValue ?? defaults?.[attrName];
|
|
140
|
+
Object.defineProperty(this, attrName, {
|
|
141
|
+
get() {
|
|
142
|
+
let signal = this._signals.get(attrName);
|
|
143
|
+
if (!signal) {
|
|
144
|
+
const subscribable = liveEntityAt(currentThread, this.en, atPath);
|
|
145
|
+
signal = this._signalAdapter.createGetter(subscribable);
|
|
146
|
+
this._signals.set(attrName, signal);
|
|
147
|
+
}
|
|
148
|
+
const value = signal();
|
|
149
|
+
return value ?? defaultValue ?? value;
|
|
150
|
+
},
|
|
151
|
+
set(v) {
|
|
152
|
+
const applog = { en: this.en, at: atPath, vl: v };
|
|
153
|
+
persistFn(this.thread, [applog]);
|
|
154
|
+
},
|
|
155
|
+
enumerable: true,
|
|
156
|
+
configurable: true
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const targets = /* @__PURE__ */ new Map();
|
|
160
|
+
this._targetApplogs = rollingFilter(currentThread, { vl: this.en });
|
|
161
|
+
this._targetMap = new SubscribableImpl(
|
|
162
|
+
targets,
|
|
163
|
+
() => this._targetApplogs.subscribe((event) => {
|
|
164
|
+
if (isInitEvent(event)) {
|
|
165
|
+
targets.clear();
|
|
166
|
+
for (const log of event.init) {
|
|
167
|
+
let ats = targets.get(log.en);
|
|
168
|
+
if (!ats) {
|
|
169
|
+
ats = /* @__PURE__ */ new Set();
|
|
170
|
+
targets.set(log.en, ats);
|
|
171
|
+
}
|
|
172
|
+
ats.add(log.at);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
for (const log of event.added) {
|
|
176
|
+
let ats = targets.get(log.en);
|
|
177
|
+
if (!ats) {
|
|
178
|
+
ats = /* @__PURE__ */ new Set();
|
|
179
|
+
targets.set(log.en, ats);
|
|
180
|
+
}
|
|
181
|
+
ats.add(log.at);
|
|
182
|
+
}
|
|
183
|
+
if (event.removed) {
|
|
184
|
+
for (const log of event.removed) {
|
|
185
|
+
const ats = targets.get(log.en);
|
|
186
|
+
if (ats) {
|
|
187
|
+
ats.delete(log.at);
|
|
188
|
+
if (ats.size === 0) targets.delete(log.en);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
this._targetMap._set(targets);
|
|
194
|
+
}, "derived")
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
en;
|
|
198
|
+
thread;
|
|
199
|
+
/** Per-instance signal storage */
|
|
200
|
+
_signals = /* @__PURE__ */ new Map();
|
|
201
|
+
_signalAdapter = signalAdapter;
|
|
202
|
+
/** Reactive map for applog tracking: vl === this.en */
|
|
203
|
+
_targetMap;
|
|
204
|
+
/** Applogs in the current thread where vl === this.en */
|
|
205
|
+
_targetApplogs;
|
|
206
|
+
/** Thread scoped to this entity */
|
|
207
|
+
get entityThread() {
|
|
208
|
+
return rollingFilter(this.thread, { en: this.en });
|
|
209
|
+
}
|
|
210
|
+
/** Applogs in the current thread where vl === this.en */
|
|
211
|
+
get targetApplogs() {
|
|
212
|
+
return this._targetApplogs;
|
|
213
|
+
}
|
|
214
|
+
/** Set of entities that have an applog with this.en as the vl */
|
|
215
|
+
get targettedBy() {
|
|
216
|
+
return new Set(this._targetMap.value.keys());
|
|
217
|
+
}
|
|
218
|
+
/** Set of at strings from applogs where vl === this.en */
|
|
219
|
+
get targettedVia() {
|
|
220
|
+
const via = /* @__PURE__ */ new Set();
|
|
221
|
+
for (const ats of this._targetMap.value.values()) {
|
|
222
|
+
for (const at of ats) via.add(at);
|
|
223
|
+
}
|
|
224
|
+
return via;
|
|
225
|
+
}
|
|
226
|
+
/** Whether this entity is soft-deleted */
|
|
227
|
+
get isDeleted() {
|
|
228
|
+
let signal = this._signals.get("__isDeleted");
|
|
229
|
+
if (!signal) {
|
|
230
|
+
const currentThread = makeCurrent(this.thread, options.makeCurrentThread);
|
|
231
|
+
const subscribable = liveEntityAt(currentThread, this.en, "isDeleted");
|
|
232
|
+
signal = this._signalAdapter.createGetter(subscribable);
|
|
233
|
+
this._signals.set("__isDeleted", signal);
|
|
234
|
+
}
|
|
235
|
+
return !!signal();
|
|
236
|
+
}
|
|
237
|
+
/** Soft-delete this entity */
|
|
238
|
+
setDeleted(thread = this.thread) {
|
|
239
|
+
const applog = { en: this.en, at: "isDeleted", vl: true };
|
|
240
|
+
persistFn(thread, [applog]);
|
|
241
|
+
}
|
|
242
|
+
/** Get a builder for updating this entity */
|
|
243
|
+
buildUpdate(init = {}) {
|
|
244
|
+
return new ObjectBuilder(init, this.en, entityPrefix);
|
|
245
|
+
}
|
|
246
|
+
/** Description for debugging */
|
|
247
|
+
get description() {
|
|
248
|
+
return `${vmName}VM(en=${this.en})`;
|
|
249
|
+
}
|
|
250
|
+
/** Get the full entity state as a plain object */
|
|
251
|
+
toJSON() {
|
|
252
|
+
const result = {};
|
|
253
|
+
for (const attr of attrDefs) {
|
|
254
|
+
const value = this[attr.name];
|
|
255
|
+
if (value !== void 0 && value !== null) {
|
|
256
|
+
result[attr.name] = value;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function getVMClass(en, thread) {
|
|
263
|
+
if (!en || typeof en !== "string") throw new Error(`[${vmName}VM.get] invalid en: ${en}`);
|
|
264
|
+
if (!thread) throw new Error(`[${vmName}VM.get] no thread provided`);
|
|
265
|
+
const threadMap = getInstancesForThread(thread);
|
|
266
|
+
let entityMap = threadMap.get(vmName);
|
|
267
|
+
if (!entityMap) {
|
|
268
|
+
entityMap = /* @__PURE__ */ new Map();
|
|
269
|
+
threadMap.set(vmName, entityMap);
|
|
270
|
+
}
|
|
271
|
+
const existing = entityMap.get(en);
|
|
272
|
+
if (existing) return existing;
|
|
273
|
+
const vm = new ViewModel(en, thread, false);
|
|
274
|
+
entityMap.set(en, vm);
|
|
275
|
+
return vm;
|
|
276
|
+
}
|
|
277
|
+
function buildNewEntity(init = {}, en) {
|
|
278
|
+
const entityId = en ?? genId(init);
|
|
279
|
+
return ObjectBuilder.create(init, entityId, entityPrefix);
|
|
280
|
+
}
|
|
281
|
+
const VMClass = ViewModel;
|
|
282
|
+
VMClass.get = getVMClass;
|
|
283
|
+
VMClass.buildNew = buildNewEntity;
|
|
284
|
+
VMClass.vmName = vmName;
|
|
285
|
+
VMClass.entityPrefix = entityPrefix;
|
|
286
|
+
return VMClass;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/viewmodel/schema-adapter.ts
|
|
290
|
+
function createAdapterFromAttributes(config) {
|
|
291
|
+
return {
|
|
292
|
+
getAttributeDefs: () => config.attributes,
|
|
293
|
+
getDefaults: () => config.defaults ?? {},
|
|
294
|
+
getEntityPrefix: () => config.entityPrefix
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function buildAtPath(entityPrefix, attrName) {
|
|
298
|
+
return `${entityPrefix}/${attrName}`;
|
|
299
|
+
}
|
|
300
|
+
export {
|
|
301
|
+
DefaultSignalAdapter,
|
|
302
|
+
GlobalVMInstances,
|
|
303
|
+
ObjectBuilder,
|
|
304
|
+
buildAtPath,
|
|
305
|
+
createAdapterFromAttributes,
|
|
306
|
+
createArkTypeAdapter,
|
|
307
|
+
createTypeBoxAdapter,
|
|
308
|
+
createTypiaAdapter,
|
|
309
|
+
createViewModelFactory,
|
|
310
|
+
createZodAdapter,
|
|
311
|
+
getInstancesForThread
|
|
312
|
+
};
|
|
313
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/viewmodel/builder.ts","../../src/viewmodel/types.ts","../../src/viewmodel/factory.ts","../../src/viewmodel/schema-adapter.ts"],"sourcesContent":["import type { ApplogForInsertOptionalAgent, ApplogValue, EntityID } from '../applog/datom-types.ts'\n\n/**\n * Generic ObjectBuilder for type-safe entity creation and updates.\n *\n * Provides a fluent API to build a set of applogs for an entity.\n * Modeled after the note3 builder pattern.\n *\n * Usage:\n * ```ts\n * ObjectBuilder.create({ name: 'foo' })\n * .update({ type: 'bar' })\n * .build(thread)\n * ```\n */\nexport class ObjectBuilder<\n\tTARGET extends Record<string, ApplogValue> = Record<string, ApplogValue>,\n> {\n\tconstructor(\n\t\tprivate _data: Partial<TARGET>,\n\t\tpublic en: EntityID | null = null,\n\t\tprivate _atPrefix: string | null = null,\n\t\tprivate _atOverrides: Partial<Record<keyof TARGET, string>> = {},\n\t) {}\n\n\t/**\n\t * Create a new builder with initial data.\n\t */\n\tstatic create<TARGET extends Record<string, ApplogValue>>(\n\t\tinit: Partial<TARGET> = {},\n\t\ten?: EntityID,\n\t\tatPrefix?: string,\n\t\tatOverrides?: Partial<Record<keyof TARGET, string>>,\n\t): ObjectBuilder<TARGET> {\n\t\treturn new ObjectBuilder<TARGET>(init, en, atPrefix, atOverrides)\n\t}\n\n\t/**\n\t * Update the data being built.\n\t */\n\tupdate(updateObj: Partial<TARGET>): this {\n\t\tObject.assign(this._data, updateObj)\n\t\treturn this\n\t}\n\n\t/**\n\t * Build the applogs for this entity, optionally resolving against a thread.\n\t */\n\tbuild(thread?: {\n\t\tinsert(applogs: ApplogForInsertOptionalAgent[]): ApplogForInsertOptionalAgent[]\n\t}): ApplogForInsertOptionalAgent[] {\n\t\tconst atPrefix = this._atPrefix\n\t\tconst applogs: ApplogForInsertOptionalAgent[] = []\n\n\t\tfor (const [key, value] of Object.entries(this._data) as [keyof TARGET, ApplogValue][]) {\n\t\t\tif (value === undefined) continue\n\t\t\tconst atOverride = this._atOverrides[key]\n\t\t\tconst at = atOverride ?? (atPrefix ? `${atPrefix}/${String(key)}` : String(key))\n\t\t\tapplogs.push({ en: this.en!, at, vl: value })\n\t\t}\n\n\t\treturn applogs\n\t}\n\n\t/**\n\t * Get the raw data being built.\n\t */\n\tget data(): Partial<TARGET> {\n\t\treturn { ...this._data }\n\t}\n}\n","import type { ApplogValue, EntityID } from '../applog/datom-types.ts'\nimport type { Thread } from '../thread/basic.ts'\nimport type { ObjectBuilder } from './builder.ts'\n\n// ═══════════════════════════════════════════════════════════════\n// Attribute Definition\n// ═══════════════════════════════════════════════════════════════\n\nexport interface VMAttributeDef {\n\t/** Attribute name (short: e.g. 'name', 'content') */\n\tname: string\n\t/** The wovin at-path used in applogs (e.g. 'block/content', 'speaker/name') */\n\tatPath: string\n\t/** Whether the attribute is optional */\n\toptional: boolean\n\t/** Default value if not present in applogs */\n\tdefaultValue?: ApplogValue\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Schema Adapter Interface\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * A schema adapter converts a type-system schema (TypeBox, Zod, ArkType, Typia)\n * into a uniform representation that the VM factory can consume.\n */\nexport interface ISchemaAdapter<T extends Record<string, ApplogValue> = Record<string, ApplogValue>> {\n\t/** Get the attribute definitions for this schema */\n\tgetAttributeDefs(): VMAttributeDef[]\n\n\t/** Get default values for all attributes */\n\tgetDefaults(): Partial<T>\n\n\t/** Optional: runtime validator */\n\tcreateValidator?(): (value: unknown) => value is T\n\n\t/** The entity prefix used for wovin at-paths (e.g. 'block', 'speaker') */\n\tgetEntityPrefix(): string\n}\n\n// ═══════════════════════════════════════════════════════════════\n// ViewModel Factory Options\n// ═══════════════════════════════════════════════════════════════\n\nexport interface ViewModelFactoryOptions<T extends Record<string, ApplogValue>> {\n\t/** The schema adapter for this entity type */\n\tadapter: ISchemaAdapter<T>\n\n\t/** How to get the default thread context */\n\tgetDefaultThread?: () => Thread\n\n\t/** EntityID generation length (default: 7) */\n\tentityIdLength?: number\n\n\t/** Function to generate entity IDs */\n\tgenerateEntityId?: (data: Partial<T> & { ts?: string }) => EntityID\n\n\t/** Override the entity prefix for at-paths */\n\tentityPrefix?: string\n\n\t/** Optional function to make thread current-state (lastWriteWins) */\n\tmakeCurrentThread?: (thread: Thread) => Thread\n\n\t/** Name for the VM type (used in logging and debugging) */\n\tvmName?: string\n\n\t/**\n\t * Signal adapter for framework-specific reactivity.\n\t * Default: non-reactive (direct Subscribable reads).\n\t * For SolidJS, pass createSolidSignalAdapter() from @wovin/ui-solid/viewmodel.\n\t */\n\tsignalAdapter?: ISignalAdapter\n}\n\n// ═══════════════════════════════════════════════════════════════\n// ViewModel Instance Interface\n// ═══════════════════════════════════════════════════════════════\n\nexport interface IVMInstance<T extends Record<string, ApplogValue> = Record<string, ApplogValue>> {\n\treadonly en: EntityID\n\treadonly thread: Thread\n\n\t/** Get a builder for updating this entity */\n\tbuildUpdate(init?: Partial<T>): ObjectBuilder<T>\n\n\t/** Soft-delete this entity */\n\tsetDeleted(thread?: Thread): void\n\n\t/** Whether this entity is deleted */\n\treadonly isDeleted: boolean\n\n\t/** The entity thread filtered to this entity */\n\treadonly entityThread: Thread\n}\n\n// ═══════════════════════════════════════════════════════════════\n// ViewModel Class Interface (what the factory returns)\n// ═══════════════════════════════════════════════════════════════\n\nexport interface IVMClass<T extends Record<string, ApplogValue> = Record<string, ApplogValue>> {\n\tnew(en: EntityID, thread: Thread, ...args: any[]): IVMInstance<T>\n\n\t/** Get or create a VM instance for the given entity ID and thread */\n\tget(en: EntityID, thread: Thread): IVMInstance<T>\n\n\t/** Create a new entity builder */\n\tbuildNew(init?: Partial<T>, en?: EntityID): ObjectBuilder<T>\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Signal Adapter Interface (framework-specific reactivity)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * A signal adapter abstracts how the VM creates reactive signals for each attribute.\n * The core factory uses a default (non-reactive) implementation.\n * Framework-specific extensions (Solid, Vue, etc.) override this.\n */\nexport interface ISignalAdapter {\n\t/**\n\t * Create a reactive getter for a subscribable value.\n\t * Returns a function that returns the current value.\n\t * In the default implementation, this is just () => subscribable.value.\n\t */\n\tcreateGetter<T>(subscribable: { value: T; subscribe(cb: () => void): () => void }): () => T\n\n\t/**\n\t * Create a writable signal for an attribute.\n\t * Returns [get, set] pair.\n\t * Default implementation stores and returns value directly.\n\t */\n\tcreateWritable<T>(initial: T): [() => T, (v: T) => void]\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Shared state maps\n// ═══════════════════════════════════════════════════════════════\n\nexport type VMInstanceMap<T> = Map<EntityID, T>\n\n/** WeakMap from Thread → Map<VMName, Map<EntityID, VMInstance>> */\nexport const GlobalVMInstances = new WeakMap<Thread, Map<string, Map<EntityID, unknown>>>()\n\nexport function getInstancesForThread(thread: Thread): Map<string, Map<EntityID, unknown>> {\n\tlet threadMap = GlobalVMInstances.get(thread)\n\tif (!threadMap) {\n\t\tthreadMap = new Map()\n\t\tGlobalVMInstances.set(thread, threadMap)\n\t}\n\treturn threadMap\n}\n","import type { ApplogForInsertOptionalAgent, ApplogValue, EntityID } from '../applog/datom-types.ts'\nimport { EntityID_LENGTH } from '../applog/datom-types.ts'\nimport { dateNowIso, getHashID } from '../applog/applog-utils.ts'\nimport { ensureTsPvAndFinalizeApplog } from '../applog/applog-helpers.ts'\nimport { lastWriteWins, liveEntityAt } from '../query/basic.ts'\nimport { SubscribableImpl } from '../query/subscribable.ts'\nimport type { Thread } from '../thread/basic.ts'\nimport { isInitEvent } from '../thread/basic.ts'\nimport { rollingFilter } from '../thread/filters.ts'\nimport { ObjectBuilder } from './builder.ts'\nimport {\n\ttype ISignalAdapter,\n\ttype IVMInstance,\n\ttype ViewModelFactoryOptions,\n\ttype VMInstanceMap,\n\tgetInstancesForThread,\n} from './types.ts'\n\n// ═══════════════════════════════════════════════════════════════\n// Default (non-reactive) Signal Adapter\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Default signal adapter — reads Subscribable values directly (snapshot).\n * Subscribe-once to keep .value current. No framework reactivity.\n */\nexport const DefaultSignalAdapter: ISignalAdapter = {\n\tcreateGetter<T>(subscribable: { value: T; subscribe(cb: () => void): () => void }): () => T {\n\t\t// Subscribe once to activate upstream (lazy activation)\n\t\tsubscribable.subscribe(() => {})\n\t\treturn () => subscribable.value\n\t},\n\tcreateWritable<T>(initial: T): [() => T, (v: T) => void] {\n\t\tlet current = initial\n\t\treturn [() => current, (v: T) => { current = v }]\n\t},\n}\n\n// ═══════════════════════════════════════════════════════════════\n// makeCurrentThread - convenience wrapper\n// ═══════════════════════════════════════════════════════════════\n\nfunction makeCurrent(thread: Thread, fn?: ViewModelFactoryOptions<any>['makeCurrentThread']): Thread {\n\tif (fn) return fn(thread)\n\treturn lastWriteWins(thread, { tolerateAlreadyFiltered: true })\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Default persist function\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Default applog persistence: finalize applogs and push into thread.\n * Works with WriteableThread (which has insertRaw).\n */\nfunction defaultPersistApplogs(thread: Thread, applogs: ApplogForInsertOptionalAgent[]): void {\n\tconst finalized = applogs.map(log => ensureTsPvAndFinalizeApplog(log as any, thread))\n\tconst writable = thread as any\n\tif (typeof writable.insertRaw === 'function') {\n\t\twritable.insertRaw(finalized)\n\t} else if (typeof writable.insert === 'function') {\n\t\twritable.insert(applogs)\n\t} else {\n\t\t// Read-only thread — just finalize but don't persist\n\t\t// This is fine for optimistic UI or read-only scenarios\n\t}\n}\n\n// ═══════════════════════════════════════════════════════════════\n// createViewModelFactory - THE MAIN FACTORY\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Create a ViewModel factory for a given schema.\n *\n * Returns a base class that can be extended or used directly.\n * The class provides:\n * - Static `get(en, thread)` for singleton access\n * - Static `buildNew(init, en)` for creating entities\n * - Instance `buildUpdate(init)` for updating entities\n * - Lazy reactive property accessors per attribute\n * - `setDeleted()` for soft deletion\n *\n * Framework-specific reactivity (Solid, Vue, etc.) can be added by\n * providing a custom `ISignalAdapter` in the options.\n *\n * @param options - Factory configuration\n * @returns A VM class constructor with static methods\n */\nexport function createViewModelFactory<T extends Record<string, ApplogValue>>(\n\toptions: ViewModelFactoryOptions<T>,\n) {\n\tconst {\n\t\tadapter,\n\t\tentityPrefix = adapter.getEntityPrefix(),\n\t\tentityIdLength = EntityID_LENGTH,\n\t\tgenerateEntityId,\n\t\tvmName = entityPrefix,\n\t\tsignalAdapter = DefaultSignalAdapter,\n\t} = options\n\n\tconst attrDefs = adapter.getAttributeDefs()\n\tconst defaults = adapter.getDefaults()\n\n\t// Entity ID generation\n\tconst genId = generateEntityId ?? ((data: Partial<T> & { ts?: string }) =>\n\t\tgetHashID({ ...data, ts: data.ts ?? dateNowIso() }, entityIdLength) as EntityID)\n\n\t// Persist function (can be overridden by extending class)\n\tconst persistFn = (thread: Thread, applogs: ApplogForInsertOptionalAgent[]) => {\n\t\tdefaultPersistApplogs(thread, applogs)\n\t}\n\n\t// ═══════════════════════════════════════════════════════════════\n\t// The ViewModel Class\n\t// ═══════════════════════════════════════════════════════════════\n\tclass ViewModel {\n\t\t/** Per-instance signal storage */\n\t\t_signals = new Map<string, () => ApplogValue>()\n\t\t_signalAdapter: ISignalAdapter = signalAdapter\n\n\t\t/** Reactive map for applog tracking: vl === this.en */\n\t\t_targetMap: SubscribableImpl<Map<EntityID, Set<string>>>\n\n\t\t/** Applogs in the current thread where vl === this.en */\n\t\t_targetApplogs: Thread\n\n\t\tconstructor(\n\t\t\tpublic en: EntityID,\n\t\t\tpublic thread: Thread,\n\t\t\tskipInit?: boolean,\n\t\t) {\n\t\t\tif (skipInit) return\n\n\t\t\tconst currentThread = makeCurrent(thread, options.makeCurrentThread)\n\n\t\t\t// Define lazy getters/setters for each attribute\n\t\t\tfor (const attr of attrDefs) {\n\t\t\t\tif (attr.name === 'en') continue\n\n\t\t\t\tconst attrName = attr.name\n\t\t\t\tconst atPath = attr.atPath\n\t\t\t\tconst defaultValue = attr.defaultValue ?? defaults?.[attrName as keyof T]\n\n\t\t\t\tObject.defineProperty(this, attrName, {\n\t\t\t\t\tget(this: ViewModel) {\n\t\t\t\t\t\tlet signal = this._signals.get(attrName)\n\t\t\t\t\t\tif (!signal) {\n\t\t\t\t\t\t\tconst subscribable = liveEntityAt(currentThread, this.en, atPath)\n\t\t\t\t\t\t\tsignal = this._signalAdapter.createGetter(subscribable)\n\t\t\t\t\t\t\tthis._signals.set(attrName, signal)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst value = signal()\n\t\t\t\t\t\treturn value ?? (defaultValue as ApplogValue | undefined) ?? value\n\t\t\t\t\t},\n\t\t\t\t\tset(this: ViewModel, v: ApplogValue) {\n\t\t\t\t\t\tconst applog: ApplogForInsertOptionalAgent = { en: this.en, at: atPath, vl: v }\n\t\t\t\t\t\tpersistFn(this.thread, [applog])\n\t\t\t\t\t},\n\t\t\t\t\tenumerable: true,\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// ── Applog tracking: applogs where vl === this.en ────────────\n\t\t\tconst targets = new Map<EntityID, Set<string>>()\n\n\t\t\tthis._targetApplogs = rollingFilter(currentThread, { vl: this.en })\n\t\t\tthis._targetMap = new SubscribableImpl(\n\t\t\t\ttargets,\n\t\t\t\t() => this._targetApplogs.subscribe((event) => {\n\t\t\t\t\tif (isInitEvent(event)) {\n\t\t\t\t\t\ttargets.clear()\n\t\t\t\t\t\tfor (const log of event.init) {\n\t\t\t\t\t\t\tlet ats = targets.get(log.en)\n\t\t\t\t\t\t\tif (!ats) {\n\t\t\t\t\t\t\t\tats = new Set()\n\t\t\t\t\t\t\t\ttargets.set(log.en, ats)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tats.add(log.at)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor (const log of event.added) {\n\t\t\t\t\t\t\tlet ats = targets.get(log.en)\n\t\t\t\t\t\t\tif (!ats) {\n\t\t\t\t\t\t\t\tats = new Set()\n\t\t\t\t\t\t\t\ttargets.set(log.en, ats)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tats.add(log.at)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (event.removed) {\n\t\t\t\t\t\t\tfor (const log of event.removed) {\n\t\t\t\t\t\t\t\tconst ats = targets.get(log.en)\n\t\t\t\t\t\t\t\tif (ats) {\n\t\t\t\t\t\t\t\t\tats.delete(log.at)\n\t\t\t\t\t\t\t\t\tif (ats.size === 0) targets.delete(log.en)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis._targetMap._set(targets)\n\t\t\t\t}, 'derived'),\n\t\t\t)\n\t\t}\n\n\t\t/** Thread scoped to this entity */\n\t\tget entityThread(): Thread {\n\t\t\treturn rollingFilter(this.thread, { en: this.en })\n\t\t}\n\n\t\t/** Applogs in the current thread where vl === this.en */\n\t\tget targetApplogs(): Thread {\n\t\t\treturn this._targetApplogs\n\t\t}\n\n\t\t/** Set of entities that have an applog with this.en as the vl */\n\t\tget targettedBy(): Set<EntityID> {\n\t\t\treturn new Set(this._targetMap.value.keys())\n\t\t}\n\n\t\t/** Set of at strings from applogs where vl === this.en */\n\t\tget targettedVia(): Set<string> {\n\t\t\tconst via = new Set<string>()\n\t\t\tfor (const ats of this._targetMap.value.values()) {\n\t\t\t\tfor (const at of ats) via.add(at)\n\t\t\t}\n\t\t\treturn via\n\t\t}\n\n\t\t/** Whether this entity is soft-deleted */\n\t\tget isDeleted(): boolean {\n\t\t\tlet signal = this._signals.get('__isDeleted')\n\t\t\tif (!signal) {\n\t\t\t\tconst currentThread = makeCurrent(this.thread, options.makeCurrentThread)\n\t\t\t\tconst subscribable = liveEntityAt(currentThread, this.en, 'isDeleted')\n\t\t\t\tsignal = this._signalAdapter.createGetter(subscribable)\n\t\t\t\tthis._signals.set('__isDeleted', signal)\n\t\t\t}\n\t\t\treturn !!signal()\n\t\t}\n\n\t\t/** Soft-delete this entity */\n\t\tsetDeleted(thread = this.thread): void {\n\t\t\tconst applog: ApplogForInsertOptionalAgent = { en: this.en, at: 'isDeleted', vl: true }\n\t\t\tpersistFn(thread, [applog])\n\t\t}\n\n\t\t/** Get a builder for updating this entity */\n\t\tbuildUpdate(init: Partial<T> = {}): ObjectBuilder<T> {\n\t\t\treturn new ObjectBuilder<T>(init, this.en, entityPrefix)\n\t\t}\n\n\t\t/** Description for debugging */\n\t\tget description(): string {\n\t\t\treturn `${vmName}VM(en=${this.en})`\n\t\t}\n\n\t\t/** Get the full entity state as a plain object */\n\t\ttoJSON(): Partial<T> {\n\t\t\tconst result: Record<string, ApplogValue> = {}\n\t\t\tfor (const attr of attrDefs) {\n\t\t\t\tconst value = (this as any)[attr.name]\n\t\t\t\tif (value !== undefined && value !== null) {\n\t\t\t\t\tresult[attr.name] = value\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result as Partial<T>\n\t\t}\n\t}\n\n\t// ── Static Methods ────────────────────────────────────────────\n\n\t/**\n\t * Get or create a VM instance for the given entity and thread.\n\t * Implements the singleton pattern — returns cached instance if one exists.\n\t */\n\tfunction getVMClass(en: EntityID, thread: Thread): InstanceType<typeof ViewModel> {\n\t\tif (!en || typeof en !== 'string') throw new Error(`[${vmName}VM.get] invalid en: ${en}`)\n\t\tif (!thread) throw new Error(`[${vmName}VM.get] no thread provided`)\n\n\t\tconst threadMap = getInstancesForThread(thread)\n\t\tlet entityMap = threadMap.get(vmName) as VMInstanceMap<InstanceType<typeof ViewModel>> | undefined\n\t\tif (!entityMap) {\n\t\t\tentityMap = new Map()\n\t\t\tthreadMap.set(vmName, entityMap)\n\t\t}\n\n\t\tconst existing = entityMap.get(en)\n\t\tif (existing) return existing\n\n\t\tconst vm = new ViewModel(en, thread, false) as InstanceType<typeof ViewModel>\n\t\tentityMap.set(en, vm)\n\t\treturn vm\n\t}\n\n\t/**\n\t * Create a builder for constructing a new entity.\n\t */\n\tfunction buildNewEntity(init: Partial<T> = {}, en?: EntityID): ObjectBuilder<T> {\n\t\tconst entityId = en ?? genId(init as any)\n\t\treturn ObjectBuilder.create<T>(init, entityId, entityPrefix)\n\t}\n\n\t// Attach static methods to the class\n\tconst VMClass: any = ViewModel\n\tVMClass.get = getVMClass\n\tVMClass.buildNew = buildNewEntity\n\tVMClass.vmName = vmName\n\tVMClass.entityPrefix = entityPrefix\n\n\treturn VMClass as unknown as {\n\t\tnew(\n\t\t\ten: EntityID,\n\t\t\tthread: Thread,\n\t\t\tskipInit?: boolean,\n\t\t): IVMInstance<T> & T\n\n\t\t/** Get or create a VM instance for the given entity ID */\n\t\tget(en: EntityID, thread: Thread): IVMInstance<T> & T\n\n\t\t/** Create a builder for a new entity */\n\t\tbuildNew(init?: Partial<T>, en?: EntityID): ObjectBuilder<T>\n\n\t\t/** The VM name (for debugging) */\n\t\treadonly vmName: string\n\n\t\t/** The entity prefix used for at-paths */\n\t\treadonly entityPrefix: string\n\t}\n}\n","import type { ApplogValue } from '../applog/datom-types.ts'\nimport type { ISchemaAdapter, VMAttributeDef } from './types.ts'\n\n/**\n * Helper to create a simple schema adapter from a plain attribute list.\n * Useful for quick VM definitions or when you don't have a formal schema library.\n */\nexport function createAdapterFromAttributes<T extends Record<string, ApplogValue>>(\n\tconfig: {\n\t\tattributes: VMAttributeDef[]\n\t\tentityPrefix: string\n\t\tdefaults?: Partial<T>\n\t},\n): ISchemaAdapter<T> {\n\treturn {\n\t\tgetAttributeDefs: () => config.attributes,\n\t\tgetDefaults: () => (config.defaults ?? {}) as Partial<T>,\n\t\tgetEntityPrefix: () => config.entityPrefix,\n\t}\n}\n\n/**\n * Helper to build at-paths from attribute names and an entity prefix.\n */\nexport function buildAtPath(entityPrefix: string, attrName: string): string {\n\treturn `${entityPrefix}/${attrName}`\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeO,IAAM,gBAAN,MAAM,eAEX;AAAA,EACD,YACS,OACD,KAAsB,MACrB,YAA2B,MAC3B,eAAsD,CAAC,GAC9D;AAJO;AACD;AACC;AACA;AAAA,EACN;AAAA,EAJM;AAAA,EACD;AAAA,EACC;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAMT,OAAO,OACN,OAAwB,CAAC,GACzB,IACA,UACA,aACwB;AACxB,WAAO,IAAI,eAAsB,MAAM,IAAI,UAAU,WAAW;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,WAAkC;AACxC,WAAO,OAAO,KAAK,OAAO,SAAS;AACnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAE6B;AAClC,UAAM,WAAW,KAAK;AACtB,UAAM,UAA0C,CAAC;AAEjD,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAoC;AACvF,UAAI,UAAU,OAAW;AACzB,YAAM,aAAa,KAAK,aAAa,GAAG;AACxC,YAAM,KAAK,eAAe,WAAW,GAAG,QAAQ,IAAI,OAAO,GAAG,CAAC,KAAK,OAAO,GAAG;AAC9E,cAAQ,KAAK,EAAE,IAAI,KAAK,IAAK,IAAI,IAAI,MAAM,CAAC;AAAA,IAC7C;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAwB;AAC3B,WAAO,EAAE,GAAG,KAAK,MAAM;AAAA,EACxB;AACD;;;ACwEO,IAAM,oBAAoB,oBAAI,QAAqD;AAEnF,SAAS,sBAAsB,QAAqD;AAC1F,MAAI,YAAY,kBAAkB,IAAI,MAAM;AAC5C,MAAI,CAAC,WAAW;AACf,gBAAY,oBAAI,IAAI;AACpB,sBAAkB,IAAI,QAAQ,SAAS;AAAA,EACxC;AACA,SAAO;AACR;;;AC7HO,IAAM,uBAAuC;AAAA,EACnD,aAAgB,cAA4E;AAE3F,iBAAa,UAAU,MAAM;AAAA,IAAC,CAAC;AAC/B,WAAO,MAAM,aAAa;AAAA,EAC3B;AAAA,EACA,eAAkB,SAAuC;AACxD,QAAI,UAAU;AACd,WAAO,CAAC,MAAM,SAAS,CAAC,MAAS;AAAE,gBAAU;AAAA,IAAE,CAAC;AAAA,EACjD;AACD;AAMA,SAAS,YAAY,QAAgB,IAAgE;AACpG,MAAI,GAAI,QAAO,GAAG,MAAM;AACxB,SAAO,cAAc,QAAQ,EAAE,yBAAyB,KAAK,CAAC;AAC/D;AAUA,SAAS,sBAAsB,QAAgB,SAA+C;AAC7F,QAAM,YAAY,QAAQ,IAAI,SAAO,4BAA4B,KAAY,MAAM,CAAC;AACpF,QAAM,WAAW;AACjB,MAAI,OAAO,SAAS,cAAc,YAAY;AAC7C,aAAS,UAAU,SAAS;AAAA,EAC7B,WAAW,OAAO,SAAS,WAAW,YAAY;AACjD,aAAS,OAAO,OAAO;AAAA,EACxB,OAAO;AAAA,EAGP;AACD;AAuBO,SAAS,uBACf,SACC;AACD,QAAM;AAAA,IACL;AAAA,IACA,eAAe,QAAQ,gBAAgB;AAAA,IACvC,iBAAiB;AAAA,IACjB;AAAA,IACA,SAAS;AAAA,IACT,gBAAgB;AAAA,EACjB,IAAI;AAEJ,QAAM,WAAW,QAAQ,iBAAiB;AAC1C,QAAM,WAAW,QAAQ,YAAY;AAGrC,QAAM,QAAQ,qBAAqB,CAAC,SACnC,UAAU,EAAE,GAAG,MAAM,IAAI,KAAK,MAAM,WAAW,EAAE,GAAG,cAAc;AAGnE,QAAM,YAAY,CAAC,QAAgB,YAA4C;AAC9E,0BAAsB,QAAQ,OAAO;AAAA,EACtC;AAAA,EAKA,MAAM,UAAU;AAAA,IAWf,YACQ,IACA,QACP,UACC;AAHM;AACA;AAGP,UAAI,SAAU;AAEd,YAAM,gBAAgB,YAAY,QAAQ,QAAQ,iBAAiB;AAGnE,iBAAW,QAAQ,UAAU;AAC5B,YAAI,KAAK,SAAS,KAAM;AAExB,cAAM,WAAW,KAAK;AACtB,cAAM,SAAS,KAAK;AACpB,cAAM,eAAe,KAAK,gBAAgB,WAAW,QAAmB;AAExE,eAAO,eAAe,MAAM,UAAU;AAAA,UACrC,MAAqB;AACpB,gBAAI,SAAS,KAAK,SAAS,IAAI,QAAQ;AACvC,gBAAI,CAAC,QAAQ;AACZ,oBAAM,eAAe,aAAa,eAAe,KAAK,IAAI,MAAM;AAChE,uBAAS,KAAK,eAAe,aAAa,YAAY;AACtD,mBAAK,SAAS,IAAI,UAAU,MAAM;AAAA,YACnC;AACA,kBAAM,QAAQ,OAAO;AACrB,mBAAO,SAAU,gBAA4C;AAAA,UAC9D;AAAA,UACA,IAAqB,GAAgB;AACpC,kBAAM,SAAuC,EAAE,IAAI,KAAK,IAAI,IAAI,QAAQ,IAAI,EAAE;AAC9E,sBAAU,KAAK,QAAQ,CAAC,MAAM,CAAC;AAAA,UAChC;AAAA,UACA,YAAY;AAAA,UACZ,cAAc;AAAA,QACf,CAAC;AAAA,MACF;AAGA,YAAM,UAAU,oBAAI,IAA2B;AAE/C,WAAK,iBAAiB,cAAc,eAAe,EAAE,IAAI,KAAK,GAAG,CAAC;AAClE,WAAK,aAAa,IAAI;AAAA,QACrB;AAAA,QACA,MAAM,KAAK,eAAe,UAAU,CAAC,UAAU;AAC9C,cAAI,YAAY,KAAK,GAAG;AACvB,oBAAQ,MAAM;AACd,uBAAW,OAAO,MAAM,MAAM;AAC7B,kBAAI,MAAM,QAAQ,IAAI,IAAI,EAAE;AAC5B,kBAAI,CAAC,KAAK;AACT,sBAAM,oBAAI,IAAI;AACd,wBAAQ,IAAI,IAAI,IAAI,GAAG;AAAA,cACxB;AACA,kBAAI,IAAI,IAAI,EAAE;AAAA,YACf;AAAA,UACD,OAAO;AACN,uBAAW,OAAO,MAAM,OAAO;AAC9B,kBAAI,MAAM,QAAQ,IAAI,IAAI,EAAE;AAC5B,kBAAI,CAAC,KAAK;AACT,sBAAM,oBAAI,IAAI;AACd,wBAAQ,IAAI,IAAI,IAAI,GAAG;AAAA,cACxB;AACA,kBAAI,IAAI,IAAI,EAAE;AAAA,YACf;AACA,gBAAI,MAAM,SAAS;AAClB,yBAAW,OAAO,MAAM,SAAS;AAChC,sBAAM,MAAM,QAAQ,IAAI,IAAI,EAAE;AAC9B,oBAAI,KAAK;AACR,sBAAI,OAAO,IAAI,EAAE;AACjB,sBAAI,IAAI,SAAS,EAAG,SAAQ,OAAO,IAAI,EAAE;AAAA,gBAC1C;AAAA,cACD;AAAA,YACD;AAAA,UACD;AACA,eAAK,WAAW,KAAK,OAAO;AAAA,QAC7B,GAAG,SAAS;AAAA,MACb;AAAA,IACD;AAAA,IA3EQ;AAAA,IACA;AAAA;AAAA,IAXR,WAAW,oBAAI,IAA+B;AAAA,IAC9C,iBAAiC;AAAA;AAAA,IAGjC;AAAA;AAAA,IAGA;AAAA;AAAA,IAiFA,IAAI,eAAuB;AAC1B,aAAO,cAAc,KAAK,QAAQ,EAAE,IAAI,KAAK,GAAG,CAAC;AAAA,IAClD;AAAA;AAAA,IAGA,IAAI,gBAAwB;AAC3B,aAAO,KAAK;AAAA,IACb;AAAA;AAAA,IAGA,IAAI,cAA6B;AAChC,aAAO,IAAI,IAAI,KAAK,WAAW,MAAM,KAAK,CAAC;AAAA,IAC5C;AAAA;AAAA,IAGA,IAAI,eAA4B;AAC/B,YAAM,MAAM,oBAAI,IAAY;AAC5B,iBAAW,OAAO,KAAK,WAAW,MAAM,OAAO,GAAG;AACjD,mBAAW,MAAM,IAAK,KAAI,IAAI,EAAE;AAAA,MACjC;AACA,aAAO;AAAA,IACR;AAAA;AAAA,IAGA,IAAI,YAAqB;AACxB,UAAI,SAAS,KAAK,SAAS,IAAI,aAAa;AAC5C,UAAI,CAAC,QAAQ;AACZ,cAAM,gBAAgB,YAAY,KAAK,QAAQ,QAAQ,iBAAiB;AACxE,cAAM,eAAe,aAAa,eAAe,KAAK,IAAI,WAAW;AACrE,iBAAS,KAAK,eAAe,aAAa,YAAY;AACtD,aAAK,SAAS,IAAI,eAAe,MAAM;AAAA,MACxC;AACA,aAAO,CAAC,CAAC,OAAO;AAAA,IACjB;AAAA;AAAA,IAGA,WAAW,SAAS,KAAK,QAAc;AACtC,YAAM,SAAuC,EAAE,IAAI,KAAK,IAAI,IAAI,aAAa,IAAI,KAAK;AACtF,gBAAU,QAAQ,CAAC,MAAM,CAAC;AAAA,IAC3B;AAAA;AAAA,IAGA,YAAY,OAAmB,CAAC,GAAqB;AACpD,aAAO,IAAI,cAAiB,MAAM,KAAK,IAAI,YAAY;AAAA,IACxD;AAAA;AAAA,IAGA,IAAI,cAAsB;AACzB,aAAO,GAAG,MAAM,SAAS,KAAK,EAAE;AAAA,IACjC;AAAA;AAAA,IAGA,SAAqB;AACpB,YAAM,SAAsC,CAAC;AAC7C,iBAAW,QAAQ,UAAU;AAC5B,cAAM,QAAS,KAAa,KAAK,IAAI;AACrC,YAAI,UAAU,UAAa,UAAU,MAAM;AAC1C,iBAAO,KAAK,IAAI,IAAI;AAAA,QACrB;AAAA,MACD;AACA,aAAO;AAAA,IACR;AAAA,EACD;AAQA,WAAS,WAAW,IAAc,QAAgD;AACjF,QAAI,CAAC,MAAM,OAAO,OAAO,SAAU,OAAM,IAAI,MAAM,IAAI,MAAM,uBAAuB,EAAE,EAAE;AACxF,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,IAAI,MAAM,4BAA4B;AAEnE,UAAM,YAAY,sBAAsB,MAAM;AAC9C,QAAI,YAAY,UAAU,IAAI,MAAM;AACpC,QAAI,CAAC,WAAW;AACf,kBAAY,oBAAI,IAAI;AACpB,gBAAU,IAAI,QAAQ,SAAS;AAAA,IAChC;AAEA,UAAM,WAAW,UAAU,IAAI,EAAE;AACjC,QAAI,SAAU,QAAO;AAErB,UAAM,KAAK,IAAI,UAAU,IAAI,QAAQ,KAAK;AAC1C,cAAU,IAAI,IAAI,EAAE;AACpB,WAAO;AAAA,EACR;AAKA,WAAS,eAAe,OAAmB,CAAC,GAAG,IAAiC;AAC/E,UAAM,WAAW,MAAM,MAAM,IAAW;AACxC,WAAO,cAAc,OAAU,MAAM,UAAU,YAAY;AAAA,EAC5D;AAGA,QAAM,UAAe;AACrB,UAAQ,MAAM;AACd,UAAQ,WAAW;AACnB,UAAQ,SAAS;AACjB,UAAQ,eAAe;AAEvB,SAAO;AAmBR;;;AClUO,SAAS,4BACf,QAKoB;AACpB,SAAO;AAAA,IACN,kBAAkB,MAAM,OAAO;AAAA,IAC/B,aAAa,MAAO,OAAO,YAAY,CAAC;AAAA,IACxC,iBAAiB,MAAM,OAAO;AAAA,EAC/B;AACD;AAKO,SAAS,YAAY,cAAsB,UAA0B;AAC3E,SAAO,GAAG,YAAY,IAAI,QAAQ;AACnC;","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ApplogValue } from '../applog/datom-types.ts';
|
|
2
|
+
import type { ISchemaAdapter, VMAttributeDef } from './types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Helper to create a simple schema adapter from a plain attribute list.
|
|
5
|
+
* Useful for quick VM definitions or when you don't have a formal schema library.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createAdapterFromAttributes<T extends Record<string, ApplogValue>>(config: {
|
|
8
|
+
attributes: VMAttributeDef[];
|
|
9
|
+
entityPrefix: string;
|
|
10
|
+
defaults?: Partial<T>;
|
|
11
|
+
}): ISchemaAdapter<T>;
|
|
12
|
+
/**
|
|
13
|
+
* Helper to build at-paths from attribute names and an entity prefix.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildAtPath(entityPrefix: string, attrName: string): string;
|
|
16
|
+
//# sourceMappingURL=schema-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-adapter.d.ts","sourceRoot":"","sources":["../../src/viewmodel/schema-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhE;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAChF,MAAM,EAAE;IACP,UAAU,EAAE,cAAc,EAAE,CAAA;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CACrB,GACC,cAAc,CAAC,CAAC,CAAC,CAMnB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1E"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ApplogValue, EntityID } from '../applog/datom-types.ts';
|
|
2
|
+
import type { Thread } from '../thread/basic.ts';
|
|
3
|
+
import type { ObjectBuilder } from './builder.ts';
|
|
4
|
+
export interface VMAttributeDef {
|
|
5
|
+
/** Attribute name (short: e.g. 'name', 'content') */
|
|
6
|
+
name: string;
|
|
7
|
+
/** The wovin at-path used in applogs (e.g. 'block/content', 'speaker/name') */
|
|
8
|
+
atPath: string;
|
|
9
|
+
/** Whether the attribute is optional */
|
|
10
|
+
optional: boolean;
|
|
11
|
+
/** Default value if not present in applogs */
|
|
12
|
+
defaultValue?: ApplogValue;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A schema adapter converts a type-system schema (TypeBox, Zod, ArkType, Typia)
|
|
16
|
+
* into a uniform representation that the VM factory can consume.
|
|
17
|
+
*/
|
|
18
|
+
export interface ISchemaAdapter<T extends Record<string, ApplogValue> = Record<string, ApplogValue>> {
|
|
19
|
+
/** Get the attribute definitions for this schema */
|
|
20
|
+
getAttributeDefs(): VMAttributeDef[];
|
|
21
|
+
/** Get default values for all attributes */
|
|
22
|
+
getDefaults(): Partial<T>;
|
|
23
|
+
/** Optional: runtime validator */
|
|
24
|
+
createValidator?(): (value: unknown) => value is T;
|
|
25
|
+
/** The entity prefix used for wovin at-paths (e.g. 'block', 'speaker') */
|
|
26
|
+
getEntityPrefix(): string;
|
|
27
|
+
}
|
|
28
|
+
export interface ViewModelFactoryOptions<T extends Record<string, ApplogValue>> {
|
|
29
|
+
/** The schema adapter for this entity type */
|
|
30
|
+
adapter: ISchemaAdapter<T>;
|
|
31
|
+
/** How to get the default thread context */
|
|
32
|
+
getDefaultThread?: () => Thread;
|
|
33
|
+
/** EntityID generation length (default: 7) */
|
|
34
|
+
entityIdLength?: number;
|
|
35
|
+
/** Function to generate entity IDs */
|
|
36
|
+
generateEntityId?: (data: Partial<T> & {
|
|
37
|
+
ts?: string;
|
|
38
|
+
}) => EntityID;
|
|
39
|
+
/** Override the entity prefix for at-paths */
|
|
40
|
+
entityPrefix?: string;
|
|
41
|
+
/** Optional function to make thread current-state (lastWriteWins) */
|
|
42
|
+
makeCurrentThread?: (thread: Thread) => Thread;
|
|
43
|
+
/** Name for the VM type (used in logging and debugging) */
|
|
44
|
+
vmName?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Signal adapter for framework-specific reactivity.
|
|
47
|
+
* Default: non-reactive (direct Subscribable reads).
|
|
48
|
+
* For SolidJS, pass createSolidSignalAdapter() from @wovin/ui-solid/viewmodel.
|
|
49
|
+
*/
|
|
50
|
+
signalAdapter?: ISignalAdapter;
|
|
51
|
+
}
|
|
52
|
+
export interface IVMInstance<T extends Record<string, ApplogValue> = Record<string, ApplogValue>> {
|
|
53
|
+
readonly en: EntityID;
|
|
54
|
+
readonly thread: Thread;
|
|
55
|
+
/** Get a builder for updating this entity */
|
|
56
|
+
buildUpdate(init?: Partial<T>): ObjectBuilder<T>;
|
|
57
|
+
/** Soft-delete this entity */
|
|
58
|
+
setDeleted(thread?: Thread): void;
|
|
59
|
+
/** Whether this entity is deleted */
|
|
60
|
+
readonly isDeleted: boolean;
|
|
61
|
+
/** The entity thread filtered to this entity */
|
|
62
|
+
readonly entityThread: Thread;
|
|
63
|
+
}
|
|
64
|
+
export interface IVMClass<T extends Record<string, ApplogValue> = Record<string, ApplogValue>> {
|
|
65
|
+
new (en: EntityID, thread: Thread, ...args: any[]): IVMInstance<T>;
|
|
66
|
+
/** Get or create a VM instance for the given entity ID and thread */
|
|
67
|
+
get(en: EntityID, thread: Thread): IVMInstance<T>;
|
|
68
|
+
/** Create a new entity builder */
|
|
69
|
+
buildNew(init?: Partial<T>, en?: EntityID): ObjectBuilder<T>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A signal adapter abstracts how the VM creates reactive signals for each attribute.
|
|
73
|
+
* The core factory uses a default (non-reactive) implementation.
|
|
74
|
+
* Framework-specific extensions (Solid, Vue, etc.) override this.
|
|
75
|
+
*/
|
|
76
|
+
export interface ISignalAdapter {
|
|
77
|
+
/**
|
|
78
|
+
* Create a reactive getter for a subscribable value.
|
|
79
|
+
* Returns a function that returns the current value.
|
|
80
|
+
* In the default implementation, this is just () => subscribable.value.
|
|
81
|
+
*/
|
|
82
|
+
createGetter<T>(subscribable: {
|
|
83
|
+
value: T;
|
|
84
|
+
subscribe(cb: () => void): () => void;
|
|
85
|
+
}): () => T;
|
|
86
|
+
/**
|
|
87
|
+
* Create a writable signal for an attribute.
|
|
88
|
+
* Returns [get, set] pair.
|
|
89
|
+
* Default implementation stores and returns value directly.
|
|
90
|
+
*/
|
|
91
|
+
createWritable<T>(initial: T): [() => T, (v: T) => void];
|
|
92
|
+
}
|
|
93
|
+
export type VMInstanceMap<T> = Map<EntityID, T>;
|
|
94
|
+
/** WeakMap from Thread → Map<VMName, Map<EntityID, VMInstance>> */
|
|
95
|
+
export declare const GlobalVMInstances: WeakMap<Thread, Map<string, Map<string, unknown>>>;
|
|
96
|
+
export declare function getInstancesForThread(thread: Thread): Map<string, Map<EntityID, unknown>>;
|
|
97
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/viewmodel/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wovin/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -57,6 +57,31 @@
|
|
|
57
57
|
"import": "./dist/utils.js",
|
|
58
58
|
"types": "./dist/utils.d.ts"
|
|
59
59
|
},
|
|
60
|
+
"./viewmodel": {
|
|
61
|
+
"source": "./src/viewmodel/index.ts",
|
|
62
|
+
"import": "./dist/viewmodel/index.js",
|
|
63
|
+
"types": "./dist/viewmodel/index.d.ts"
|
|
64
|
+
},
|
|
65
|
+
"./viewmodel/adapters/typebox": {
|
|
66
|
+
"source": "./src/viewmodel/adapters/typebox.ts",
|
|
67
|
+
"import": "./dist/viewmodel/adapters/typebox.js",
|
|
68
|
+
"types": "./dist/viewmodel/adapters/typebox.d.ts"
|
|
69
|
+
},
|
|
70
|
+
"./viewmodel/adapters/zod": {
|
|
71
|
+
"source": "./src/viewmodel/adapters/zod.ts",
|
|
72
|
+
"import": "./dist/viewmodel/adapters/zod.js",
|
|
73
|
+
"types": "./dist/viewmodel/adapters/zod.d.ts"
|
|
74
|
+
},
|
|
75
|
+
"./viewmodel/adapters/arktype": {
|
|
76
|
+
"source": "./src/viewmodel/adapters/arktype.ts",
|
|
77
|
+
"import": "./dist/viewmodel/adapters/arktype.js",
|
|
78
|
+
"types": "./dist/viewmodel/adapters/arktype.d.ts"
|
|
79
|
+
},
|
|
80
|
+
"./viewmodel/adapters/typia": {
|
|
81
|
+
"source": "./src/viewmodel/adapters/typia.ts",
|
|
82
|
+
"import": "./dist/viewmodel/adapters/typia.js",
|
|
83
|
+
"types": "./dist/viewmodel/adapters/typia.d.ts"
|
|
84
|
+
},
|
|
60
85
|
"./ipns": {
|
|
61
86
|
"source": "./src/ipns.ts",
|
|
62
87
|
"import": "./dist/ipns.js",
|
|
@@ -67,7 +92,6 @@
|
|
|
67
92
|
"./dist/",
|
|
68
93
|
"./src/"
|
|
69
94
|
],
|
|
70
|
-
"packageManager": "pnpm",
|
|
71
95
|
"esm.sh": {
|
|
72
96
|
"bundle": false
|
|
73
97
|
},
|
|
@@ -88,8 +112,10 @@
|
|
|
88
112
|
"lodash-es": "^4.17.21",
|
|
89
113
|
"@libp2p/crypto": "^5.0.0",
|
|
90
114
|
"ipns": "^10.1.2",
|
|
91
|
-
"multiformats": "^
|
|
115
|
+
"multiformats": "^14.0.0",
|
|
116
|
+
"partysocket": "^1.0.2",
|
|
92
117
|
"safe-stable-stringify": "^2.5.0",
|
|
118
|
+
"w3name": "^1.1.3",
|
|
93
119
|
"zod": "^4.3.6"
|
|
94
120
|
},
|
|
95
121
|
"devDependencies": {
|
|
@@ -24,6 +24,33 @@ export const objEqualByKeys = (keys: string[], objA: object, objB: object) => {
|
|
|
24
24
|
return isEqual(pick(objA, keys), pick(objB, keys))
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Deep equality for an `ApplogValue`. `vl` can now hold JSON objects/arrays, so a bare
|
|
29
|
+
* `===` would treat structurally-identical objects as different. `isEqual` short-circuits
|
|
30
|
+
* on referential identity internally, so primitives stay on the cheap path.
|
|
31
|
+
*/
|
|
32
|
+
export const valueEq = (a: ApplogValue, b: ApplogValue): boolean => isEqual(a, b)
|
|
33
|
+
|
|
34
|
+
/** Canonical, hashable Map key for an `ApplogValue` (see {@link valueKey}). */
|
|
35
|
+
export type ValueKey = string | number | boolean | null
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Canonical key for using an `ApplogValue` as a Map key. Primitives pass through unchanged
|
|
39
|
+
* (so primitive-keyed lookups are identity-stable); objects/arrays are stably stringified
|
|
40
|
+
* (key-sorted) behind a `\0obj:` sentinel so an object value can't collide with a string
|
|
41
|
+
* value that happens to equal its serialization.
|
|
42
|
+
*/
|
|
43
|
+
export const valueKey = (vl: ApplogValue): ValueKey => {
|
|
44
|
+
switch (typeof vl) {
|
|
45
|
+
case 'string':
|
|
46
|
+
case 'number':
|
|
47
|
+
case 'boolean':
|
|
48
|
+
return vl
|
|
49
|
+
default: // null | object | array
|
|
50
|
+
return vl === null ? null : '\0obj:' + stringify(vl)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
27
54
|
/** Transitive total-order comparator over (ts, cid). Safe for `Array.sort`. */
|
|
28
55
|
export const compareApplogsByTs = (logA: Applog, logB: Applog, dir: 'asc' | 'desc' = 'asc') => {
|
|
29
56
|
const tsCmp = isoDateStrCompare(logA.ts, logB.ts, dir)
|
|
@@ -297,15 +324,22 @@ export function matchPartStatic(field: keyof Applog, patternPart: ValueOrMatcher
|
|
|
297
324
|
result = patternPart === atomPart // shortcut for most common use-case
|
|
298
325
|
} else if (typ === 'function') {
|
|
299
326
|
result = (patternPart as Function)(atomPart)
|
|
327
|
+
} else if (Array.isArray(patternPart)) {
|
|
328
|
+
// A bare array is ambiguous now that `vl` may itself be an array value: it could
|
|
329
|
+
// mean set-membership or a literal-array match. Fail loudly rather than match wrong.
|
|
330
|
+
throw ERROR(
|
|
331
|
+
`[matchPartStatic] a bare array is not a valid matcher for field '${field}'.`
|
|
332
|
+
+ ` Use anyOf(...) for set-membership, or a predicate (v) => isEqual(v, [...]) to match a literal array value.`,
|
|
333
|
+
patternPart,
|
|
334
|
+
)
|
|
300
335
|
} else if (typeof (patternPart as any).has === 'function') {
|
|
301
336
|
result = (patternPart as Set<any>).has(atomPart)
|
|
302
|
-
} else if (Array.isArray(patternPart) && !Array.isArray(atomPart) /* ? how to handle array values */) {
|
|
303
|
-
result = patternPart.includes(atomPart)
|
|
304
337
|
} // if (field === 'at' && typ === 'string' && patternPart.endsWith('*')) {
|
|
305
338
|
// return typeof atomPart === 'string' && atomPart.startsWith(patternPart.slice(0, -1))
|
|
306
339
|
// }
|
|
307
340
|
else {
|
|
308
|
-
|
|
341
|
+
// object/array values compare by deep equality; primitives via isEqual's === fast-path
|
|
342
|
+
result = valueEq(patternPart as ApplogValue, atomPart)
|
|
309
343
|
}
|
|
310
344
|
} else {
|
|
311
345
|
result = patternPart === atomPart
|
|
@@ -334,7 +368,17 @@ export function matchPart(patternPart: ValueOrMatcher<ApplogValue>, atomPart: Ap
|
|
|
334
368
|
if (typeof patternPart === 'function') {
|
|
335
369
|
return patternPart(atomPart) ? context : null
|
|
336
370
|
}
|
|
337
|
-
|
|
371
|
+
if (Array.isArray(patternPart)) {
|
|
372
|
+
throw ERROR(
|
|
373
|
+
`[matchPart] a bare array is not a valid matcher.`
|
|
374
|
+
+ ` Use anyOf(...) for set-membership, or a predicate to match a literal array value.`,
|
|
375
|
+
patternPart,
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
if (patternPart && typeof (patternPart as any).has === 'function') {
|
|
379
|
+
return (patternPart as Set<any>).has(atomPart) ? context : null
|
|
380
|
+
}
|
|
381
|
+
return valueEq(patternPart as ApplogValue, atomPart) ? context : null
|
|
338
382
|
}
|
|
339
383
|
|
|
340
384
|
/**
|