better-call 0.0.0-experimental.01ff4b77
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/LICENSE +21 -0
- package/README.md +265 -0
- package/dist/capability.cjs +417 -0
- package/dist/capability.cjs.map +1 -0
- package/dist/capability.d.cts +189 -0
- package/dist/capability.d.mts +189 -0
- package/dist/capability.mjs +401 -0
- package/dist/capability.mjs.map +1 -0
- package/dist/error.cjs +76 -0
- package/dist/error.cjs.map +1 -0
- package/dist/error.d.cts +51 -0
- package/dist/error.d.mts +51 -0
- package/dist/error.mjs +72 -0
- package/dist/error.mjs.map +1 -0
- package/dist/fn.cjs +350 -0
- package/dist/fn.cjs.map +1 -0
- package/dist/fn.d.cts +378 -0
- package/dist/fn.d.mts +378 -0
- package/dist/fn.mjs +350 -0
- package/dist/fn.mjs.map +1 -0
- package/dist/index.cjs +43 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +50 -0
- package/dist/index.d.mts +50 -0
- package/dist/index.mjs +24 -0
- package/dist/index.mjs.map +1 -0
- package/dist/module.cjs +176 -0
- package/dist/module.cjs.map +1 -0
- package/dist/module.d.cts +283 -0
- package/dist/module.d.mts +283 -0
- package/dist/module.mjs +166 -0
- package/dist/module.mjs.map +1 -0
- package/dist/plugins/db.cjs +24 -0
- package/dist/plugins/db.cjs.map +1 -0
- package/dist/plugins/db.d.cts +18 -0
- package/dist/plugins/db.d.mts +18 -0
- package/dist/plugins/db.mjs +19 -0
- package/dist/plugins/db.mjs.map +1 -0
- package/dist/plugins/http/attrs.cjs +91 -0
- package/dist/plugins/http/attrs.cjs.map +1 -0
- package/dist/plugins/http/attrs.d.cts +26 -0
- package/dist/plugins/http/attrs.d.mts +26 -0
- package/dist/plugins/http/attrs.mjs +87 -0
- package/dist/plugins/http/attrs.mjs.map +1 -0
- package/dist/plugins/http/cookie.cjs +117 -0
- package/dist/plugins/http/cookie.cjs.map +1 -0
- package/dist/plugins/http/cookie.d.cts +637 -0
- package/dist/plugins/http/cookie.d.mts +637 -0
- package/dist/plugins/http/cookie.mjs +113 -0
- package/dist/plugins/http/cookie.mjs.map +1 -0
- package/dist/plugins/http/error.cjs +39 -0
- package/dist/plugins/http/error.cjs.map +1 -0
- package/dist/plugins/http/error.d.cts +38 -0
- package/dist/plugins/http/error.d.mts +38 -0
- package/dist/plugins/http/error.mjs +35 -0
- package/dist/plugins/http/error.mjs.map +1 -0
- package/dist/plugins/http/handle.cjs +73 -0
- package/dist/plugins/http/handle.cjs.map +1 -0
- package/dist/plugins/http/handle.d.cts +955 -0
- package/dist/plugins/http/handle.d.mts +955 -0
- package/dist/plugins/http/handle.mjs +72 -0
- package/dist/plugins/http/handle.mjs.map +1 -0
- package/dist/plugins/http/redirect.cjs +82 -0
- package/dist/plugins/http/redirect.cjs.map +1 -0
- package/dist/plugins/http/redirect.d.cts +67 -0
- package/dist/plugins/http/redirect.d.mts +67 -0
- package/dist/plugins/http/redirect.mjs +79 -0
- package/dist/plugins/http/redirect.mjs.map +1 -0
- package/dist/plugins/http/request.cjs +63 -0
- package/dist/plugins/http/request.cjs.map +1 -0
- package/dist/plugins/http/request.d.cts +142 -0
- package/dist/plugins/http/request.d.mts +142 -0
- package/dist/plugins/http/request.mjs +61 -0
- package/dist/plugins/http/request.mjs.map +1 -0
- package/dist/plugins/http/response.cjs +13 -0
- package/dist/plugins/http/response.cjs.map +1 -0
- package/dist/plugins/http/response.d.cts +18 -0
- package/dist/plugins/http/response.d.mts +18 -0
- package/dist/plugins/http/response.mjs +13 -0
- package/dist/plugins/http/response.mjs.map +1 -0
- package/dist/plugins/http.cjs +64 -0
- package/dist/plugins/http.cjs.map +1 -0
- package/dist/plugins/http.d.cts +945 -0
- package/dist/plugins/http.d.mts +945 -0
- package/dist/plugins/http.mjs +37 -0
- package/dist/plugins/http.mjs.map +1 -0
- package/dist/plugins/read-only.cjs +19 -0
- package/dist/plugins/read-only.cjs.map +1 -0
- package/dist/plugins/read-only.d.cts +17 -0
- package/dist/plugins/read-only.d.mts +17 -0
- package/dist/plugins/read-only.mjs +19 -0
- package/dist/plugins/read-only.mjs.map +1 -0
- package/dist/schema.cjs +245 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +411 -0
- package/dist/schema.d.mts +411 -0
- package/dist/schema.mjs +235 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/scope.d.cts +31 -0
- package/dist/scope.d.mts +31 -0
- package/dist/storage.cjs +308 -0
- package/dist/storage.cjs.map +1 -0
- package/dist/storage.d.cts +220 -0
- package/dist/storage.d.mts +220 -0
- package/dist/storage.mjs +302 -0
- package/dist/storage.mjs.map +1 -0
- package/dist/types.d.cts +8 -0
- package/dist/types.d.mts +8 -0
- package/dist/var.cjs +186 -0
- package/dist/var.cjs.map +1 -0
- package/dist/var.d.cts +46 -0
- package/dist/var.d.mts +46 -0
- package/dist/var.mjs +177 -0
- package/dist/var.mjs.map +1 -0
- package/package.json +114 -0
package/dist/storage.mjs
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { asType, attrsOf, isVar } from "./schema.mjs";
|
|
2
|
+
import { matchesTarget } from "./module.mjs";
|
|
3
|
+
//#region src/storage.ts
|
|
4
|
+
const WHERE_OPS = {
|
|
5
|
+
eq: true,
|
|
6
|
+
ne: true,
|
|
7
|
+
lt: true,
|
|
8
|
+
lte: true,
|
|
9
|
+
gt: true,
|
|
10
|
+
gte: true,
|
|
11
|
+
in: true,
|
|
12
|
+
notIn: true,
|
|
13
|
+
contains: true,
|
|
14
|
+
startsWith: true,
|
|
15
|
+
endsWith: true
|
|
16
|
+
};
|
|
17
|
+
/** An operator object is a plain record whose keys are ALL operator names.
|
|
18
|
+
* Anything else - a Date, an array, an empty object - is a value. */
|
|
19
|
+
const isOps = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && Object.keys(value).length > 0 && Object.keys(value).every((key) => key in WHERE_OPS);
|
|
20
|
+
/** Flatten a where to normalized conditions - the adapter author's
|
|
21
|
+
* translation seam: map each condition onto your query language, AND them. */
|
|
22
|
+
const conditionsOf = (where = {}) => Object.entries(where).flatMap(([field, spec]) => isOps(spec) ? Object.entries(spec).map(([op, value]) => ({
|
|
23
|
+
field,
|
|
24
|
+
op,
|
|
25
|
+
value
|
|
26
|
+
})) : [{
|
|
27
|
+
field,
|
|
28
|
+
op: "eq",
|
|
29
|
+
value: spec
|
|
30
|
+
}]);
|
|
31
|
+
/** Dates equate and order by their instant. */
|
|
32
|
+
const rawValue = (value) => value instanceof Date ? value.getTime() : value;
|
|
33
|
+
const equals = (a, b) => rawValue(a) === rawValue(b);
|
|
34
|
+
const compare = (a, b) => {
|
|
35
|
+
const left = rawValue(a);
|
|
36
|
+
const right = rawValue(b);
|
|
37
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
38
|
+
};
|
|
39
|
+
const holds = (row, { field, op, value }) => {
|
|
40
|
+
const current = row[field];
|
|
41
|
+
switch (op) {
|
|
42
|
+
case "eq": return equals(current, value);
|
|
43
|
+
case "ne": return !equals(current, value);
|
|
44
|
+
case "lt":
|
|
45
|
+
case "lte":
|
|
46
|
+
case "gt":
|
|
47
|
+
case "gte": {
|
|
48
|
+
if (current == null || value == null) return false;
|
|
49
|
+
const order = compare(current, value);
|
|
50
|
+
if (op === "lt") return order < 0;
|
|
51
|
+
if (op === "lte") return order <= 0;
|
|
52
|
+
if (op === "gt") return order > 0;
|
|
53
|
+
return order >= 0;
|
|
54
|
+
}
|
|
55
|
+
case "in": return value.some((member) => equals(current, member));
|
|
56
|
+
case "notIn": return !value.some((member) => equals(current, member));
|
|
57
|
+
case "contains": return typeof current === "string" && current.includes(value);
|
|
58
|
+
case "startsWith": return typeof current === "string" && current.startsWith(value);
|
|
59
|
+
case "endsWith": return typeof current === "string" && current.endsWith(value);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/** Does a row satisfy a where? The in-memory evaluator - the dummy adapter
|
|
63
|
+
* runs on it, and any adapter over an unqueryable backend can too. */
|
|
64
|
+
const matchesWhere = (row, where = {}) => conditionsOf(where).every((condition) => holds(row, condition));
|
|
65
|
+
/** The DUMMY adapter: rows in arrays, one per model. Implements the whole
|
|
66
|
+
* surface - the optional verbs by mutation (single-threaded, so "atomic"),
|
|
67
|
+
* transactions by snapshot-and-restore. */
|
|
68
|
+
const memoryAdapter = () => {
|
|
69
|
+
let tables = /* @__PURE__ */ new Map();
|
|
70
|
+
const rows = (model) => {
|
|
71
|
+
let list = tables.get(model);
|
|
72
|
+
if (!list) {
|
|
73
|
+
list = [];
|
|
74
|
+
tables.set(model, list);
|
|
75
|
+
}
|
|
76
|
+
return list;
|
|
77
|
+
};
|
|
78
|
+
const self = {
|
|
79
|
+
create: (model, data) => {
|
|
80
|
+
const row = { ...data };
|
|
81
|
+
rows(model).push(row);
|
|
82
|
+
return row;
|
|
83
|
+
},
|
|
84
|
+
findOne: (model, where) => rows(model).find((row) => matchesWhere(row, where)) ?? null,
|
|
85
|
+
findMany: (model, where, options) => {
|
|
86
|
+
let list = rows(model).filter((row) => matchesWhere(row, where));
|
|
87
|
+
const sort = options?.sortBy;
|
|
88
|
+
if (sort) {
|
|
89
|
+
const direction = sort.direction === "desc" ? -1 : 1;
|
|
90
|
+
list = [...list].sort((a, b) => direction * compare(a[sort.field], b[sort.field]));
|
|
91
|
+
}
|
|
92
|
+
const start = options?.offset ?? 0;
|
|
93
|
+
const end = options?.limit === void 0 ? void 0 : start + options.limit;
|
|
94
|
+
return start === 0 && end === void 0 ? list : list.slice(start, end);
|
|
95
|
+
},
|
|
96
|
+
update: (model, where, patch) => {
|
|
97
|
+
const row = rows(model).find((r) => matchesWhere(r, where));
|
|
98
|
+
return row ? Object.assign(row, patch) : null;
|
|
99
|
+
},
|
|
100
|
+
delete: (model, where) => {
|
|
101
|
+
const list = rows(model);
|
|
102
|
+
const keep = list.filter((row) => !matchesWhere(row, where));
|
|
103
|
+
tables.set(model, keep);
|
|
104
|
+
return list.length - keep.length;
|
|
105
|
+
},
|
|
106
|
+
count: (model, where) => rows(model).filter((row) => matchesWhere(row, where)).length,
|
|
107
|
+
consumeOne: (model, where) => {
|
|
108
|
+
const list = rows(model);
|
|
109
|
+
const index = list.findIndex((row) => matchesWhere(row, where));
|
|
110
|
+
return index === -1 ? null : list.splice(index, 1)[0];
|
|
111
|
+
},
|
|
112
|
+
incrementOne: (model, where, increments) => {
|
|
113
|
+
const row = rows(model).find((r) => matchesWhere(r, where));
|
|
114
|
+
if (!row) return null;
|
|
115
|
+
for (const [field, by] of Object.entries(increments)) row[field] = (row[field] ?? 0) + by;
|
|
116
|
+
return row;
|
|
117
|
+
},
|
|
118
|
+
transaction: async (run) => {
|
|
119
|
+
const snapshot = new Map([...tables].map(([model, list]) => [model, list.map((row) => ({ ...row }))]));
|
|
120
|
+
try {
|
|
121
|
+
return await run(self);
|
|
122
|
+
} catch (thrown) {
|
|
123
|
+
tables = snapshot;
|
|
124
|
+
throw thrown;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
return self;
|
|
129
|
+
};
|
|
130
|
+
/** Read `$attrs.db` off each field of a var's object schema. */
|
|
131
|
+
const fieldsFromSchema = (sv) => {
|
|
132
|
+
const schema = asType(sv.schema ?? {});
|
|
133
|
+
if (schema.name !== "object" || schema.shape === void 0) return {};
|
|
134
|
+
const fields = {};
|
|
135
|
+
for (const [key, child] of Object.entries(schema.shape)) {
|
|
136
|
+
const meta = attrsOf(child, "db");
|
|
137
|
+
if (meta && Object.keys(meta).length > 0) fields[key] = { ...meta };
|
|
138
|
+
}
|
|
139
|
+
return fields;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Schema attrs first; `ModelConfig.fields[k]` replaces that key entirely
|
|
143
|
+
* when present (compat override).
|
|
144
|
+
*/
|
|
145
|
+
const resolveModelFields = (input) => {
|
|
146
|
+
if (isVar(input)) {
|
|
147
|
+
const fromSchema = fieldsFromSchema(input);
|
|
148
|
+
return Object.keys(fromSchema).length > 0 ? fromSchema : void 0;
|
|
149
|
+
}
|
|
150
|
+
const config = input;
|
|
151
|
+
const fromSchema = fieldsFromSchema(config.schema);
|
|
152
|
+
const override = config.fields ?? {};
|
|
153
|
+
const merged = { ...fromSchema };
|
|
154
|
+
for (const [key, meta] of Object.entries(override)) if (meta !== void 0) merged[key] = meta;
|
|
155
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
156
|
+
};
|
|
157
|
+
/** Normalize `$models` entries so adapters always see merged `fields`.
|
|
158
|
+
* Bare vars stay bare vars (identity preserved); use
|
|
159
|
+
* {@link resolveModelFields} to read schema attrs off them. ModelConfig
|
|
160
|
+
* entries get `fields` filled from schema attrs + overrides. */
|
|
161
|
+
const resolveModels = (models) => {
|
|
162
|
+
const out = {};
|
|
163
|
+
for (const [key, input] of Object.entries(models)) {
|
|
164
|
+
if (isVar(input)) {
|
|
165
|
+
out[key] = input;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const config = input;
|
|
169
|
+
const fields = resolveModelFields(config);
|
|
170
|
+
out[key] = fields === void 0 ? { ...config } : {
|
|
171
|
+
...config,
|
|
172
|
+
fields
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
};
|
|
177
|
+
/** Duck-type a storage instance - `$models` plus the `$adapter` method. */
|
|
178
|
+
const isStorage = (value) => typeof value === "object" && value !== null && "$models" in value && typeof value.$adapter === "function";
|
|
179
|
+
const OPS = [
|
|
180
|
+
"create",
|
|
181
|
+
"findOne",
|
|
182
|
+
"findMany",
|
|
183
|
+
"update",
|
|
184
|
+
"delete",
|
|
185
|
+
"count",
|
|
186
|
+
"consumeOne",
|
|
187
|
+
"incrementOne"
|
|
188
|
+
];
|
|
189
|
+
/** The op against the backend, optional verbs falling back to the required
|
|
190
|
+
* six: find-then-write - correct alone, racy under contention. The comment
|
|
191
|
+
* every fallback deserves lives on `StorageAdapter`. */
|
|
192
|
+
const rawOp = (adapter, name, op, args) => {
|
|
193
|
+
if (op === "consumeOne" && !adapter.consumeOne) return (async () => {
|
|
194
|
+
const where = args[0];
|
|
195
|
+
const row = await adapter.findOne(name, where);
|
|
196
|
+
if (!row) return null;
|
|
197
|
+
await adapter.delete(name, row);
|
|
198
|
+
return row;
|
|
199
|
+
})();
|
|
200
|
+
if (op === "incrementOne" && !adapter.incrementOne) return (async () => {
|
|
201
|
+
const [where, increments] = args;
|
|
202
|
+
const row = await adapter.findOne(name, where);
|
|
203
|
+
if (!row) return null;
|
|
204
|
+
const patch = Object.fromEntries(Object.entries(increments).map(([field, by]) => [field, (row[field] ?? 0) + by]));
|
|
205
|
+
return adapter.update(name, where, patch);
|
|
206
|
+
})();
|
|
207
|
+
return adapter[op](name, ...args);
|
|
208
|
+
};
|
|
209
|
+
const buildStorage = (state, models) => {
|
|
210
|
+
const run = (key, name, op, args) => {
|
|
211
|
+
const base = () => Promise.resolve(rawOp(state.adapter, name, op, args));
|
|
212
|
+
return state.hooks.filter((entry) => matchesTarget(entry.target, `${key}.${op}`)).reduceRight((next, entry) => () => Promise.resolve(entry.hook({
|
|
213
|
+
model: key,
|
|
214
|
+
op,
|
|
215
|
+
args
|
|
216
|
+
}, next)), base)();
|
|
217
|
+
};
|
|
218
|
+
const storage = {};
|
|
219
|
+
for (const [key, input] of Object.entries(models)) {
|
|
220
|
+
const name = (isVar(input) ? input : input.schema).name;
|
|
221
|
+
const collection = {
|
|
222
|
+
create: (data) => run(key, name, "create", [data]),
|
|
223
|
+
findOne: (where) => run(key, name, "findOne", [where]),
|
|
224
|
+
findMany: (where, options) => run(key, name, "findMany", [where, options]),
|
|
225
|
+
update: (where, patch) => run(key, name, "update", [where, patch]),
|
|
226
|
+
delete: (where) => run(key, name, "delete", [where]),
|
|
227
|
+
count: (where) => run(key, name, "count", [where]),
|
|
228
|
+
consumeOne: (where) => run(key, name, "consumeOne", [where]),
|
|
229
|
+
incrementOne: (where, increments) => run(key, name, "incrementOne", [where, increments])
|
|
230
|
+
};
|
|
231
|
+
storage[key] = collection;
|
|
232
|
+
if (isVar(input)) continue;
|
|
233
|
+
for (const op of OPS) {
|
|
234
|
+
const declared = input[op];
|
|
235
|
+
if (!declared) continue;
|
|
236
|
+
const cacheKey = `${key}.${op}`;
|
|
237
|
+
let entries = state.subscriptions.get(cacheKey);
|
|
238
|
+
if (!entries) {
|
|
239
|
+
const produced = typeof declared === "function" ? declared(collection[op]) : declared;
|
|
240
|
+
entries = Array.isArray(produced) ? produced : [produced];
|
|
241
|
+
state.subscriptions.set(cacheKey, entries);
|
|
242
|
+
}
|
|
243
|
+
entries.forEach((entry, index) => {
|
|
244
|
+
storage[`${key}$${op}${index === 0 ? "" : index}`] = entry;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const self = Object.assign(storage, {
|
|
249
|
+
$adapter: (adapter) => {
|
|
250
|
+
state.adapter = adapter;
|
|
251
|
+
return self;
|
|
252
|
+
},
|
|
253
|
+
$on: (target, hook) => {
|
|
254
|
+
state.hooks.push({
|
|
255
|
+
target,
|
|
256
|
+
hook
|
|
257
|
+
});
|
|
258
|
+
return self;
|
|
259
|
+
},
|
|
260
|
+
$omit: (...keys) => buildStorage(state, Object.fromEntries(Object.entries(models).filter(([key]) => !keys.includes(key)))),
|
|
261
|
+
$pick: (...keys) => buildStorage(state, Object.fromEntries(keys.map((key) => [key, models[key]]))),
|
|
262
|
+
$extend: (more) => buildStorage(state, {
|
|
263
|
+
...models,
|
|
264
|
+
...more
|
|
265
|
+
}),
|
|
266
|
+
$transaction: (fn) => {
|
|
267
|
+
const inside = (adapter) => Promise.resolve(fn(buildStorage({
|
|
268
|
+
...state,
|
|
269
|
+
adapter
|
|
270
|
+
}, models)));
|
|
271
|
+
return state.adapter.transaction ? state.adapter.transaction((tx) => inside(tx)) : inside(state.adapter);
|
|
272
|
+
},
|
|
273
|
+
$models: resolveModels(models)
|
|
274
|
+
});
|
|
275
|
+
return self;
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* MANY instances of a var: each model is a collection of rows shaped like
|
|
279
|
+
* the var's VALUE, addressed by the var's NAME - the var stays what it
|
|
280
|
+
* always was (the scope's one current instance), the storage holds every
|
|
281
|
+
* other one, and the query API is how rows move between the two.
|
|
282
|
+
*
|
|
283
|
+
* The returned storage is CUSTOMIZABLE through its `$` surface: `$adapter`
|
|
284
|
+
* swaps the backend in place, `$on` mounts hooks around ops,
|
|
285
|
+
* `$omit`/`$pick`/`$extend` derive model views over the same state, and
|
|
286
|
+
* `$transaction` scopes ops to one adapter transaction.
|
|
287
|
+
*
|
|
288
|
+
* A model can also DECLARE its persistence: pass `{ schema, create: ... }`
|
|
289
|
+
* instead of the bare var, and the op subscriptions ride on the storage as
|
|
290
|
+
* mountable `v.on` entries - `use: [db]` wires them into the app. The same
|
|
291
|
+
* config carries `fields` metadata (unique, index, references) for schema
|
|
292
|
+
* generators to consume.
|
|
293
|
+
*/
|
|
294
|
+
const makeStorage = (adapter, models) => buildStorage({
|
|
295
|
+
adapter,
|
|
296
|
+
hooks: [],
|
|
297
|
+
subscriptions: /* @__PURE__ */ new Map()
|
|
298
|
+
}, models);
|
|
299
|
+
//#endregion
|
|
300
|
+
export { conditionsOf, fieldsFromSchema, isStorage, makeStorage, matchesWhere, memoryAdapter, resolveModelFields };
|
|
301
|
+
|
|
302
|
+
//# sourceMappingURL=storage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.mjs","names":[],"sources":["../src/storage.ts"],"sourcesContent":["import { matchesTarget, type OnEntry } from \"./module\";\nimport { asType, attrsOf, isVar } from \"./schema\";\nimport type { NameOfVar, ValueOfVar, VarDefination } from \"./var\";\n\n/* ---------------------------------- where ---------------------------------- */\n\n/** Per-field operators, for everything equality can't say: expiry sweeps\n * (`lt`), revocation lists (`in`), guarded counters (`lt` as the guard).\n * A bare value stays plain equality - the common case reads like data. */\nexport type WhereOps<V> = {\n\teq?: V;\n\tne?: V;\n\tlt?: V;\n\tlte?: V;\n\tgt?: V;\n\tgte?: V;\n\tin?: readonly V[];\n\tnotIn?: readonly V[];\n\tcontains?: string;\n\tstartsWith?: string;\n\tendsWith?: string;\n};\n\nexport type WhereOp = keyof WhereOps<unknown>;\n\n/** AND across fields; each field a bare value (equality) or operators. */\nexport type Where<R> = { [K in keyof R]?: R[K] | WhereOps<R[K]> };\n\n/** One normalized clause: `{ tag: \"a\" }` -> `{ field: \"tag\", op: \"eq\" }`. */\nexport type Condition = { field: string; op: WhereOp; value: unknown };\n\nconst WHERE_OPS: Record<WhereOp, true> = {\n\teq: true,\n\tne: true,\n\tlt: true,\n\tlte: true,\n\tgt: true,\n\tgte: true,\n\tin: true,\n\tnotIn: true,\n\tcontains: true,\n\tstartsWith: true,\n\tendsWith: true,\n};\n\n/** An operator object is a plain record whose keys are ALL operator names.\n * Anything else - a Date, an array, an empty object - is a value. */\nconst isOps = (value: unknown): value is WhereOps<unknown> =>\n\ttypeof value === \"object\" &&\n\tvalue !== null &&\n\t!Array.isArray(value) &&\n\t!(value instanceof Date) &&\n\tObject.keys(value).length > 0 &&\n\tObject.keys(value).every((key) => key in WHERE_OPS);\n\n/** Flatten a where to normalized conditions - the adapter author's\n * translation seam: map each condition onto your query language, AND them. */\nexport const conditionsOf = (\n\twhere: Record<string, unknown> = {},\n): Condition[] =>\n\tObject.entries(where).flatMap(([field, spec]) =>\n\t\tisOps(spec)\n\t\t\t? Object.entries(spec).map(([op, value]) => ({\n\t\t\t\t\tfield,\n\t\t\t\t\top: op as WhereOp,\n\t\t\t\t\tvalue,\n\t\t\t\t}))\n\t\t\t: [{ field, op: \"eq\" as const, value: spec }],\n\t);\n\n/** Dates equate and order by their instant. */\nconst rawValue = (value: unknown) =>\n\tvalue instanceof Date ? value.getTime() : value;\n\nconst equals = (a: unknown, b: unknown) => rawValue(a) === rawValue(b);\n\nconst compare = (a: unknown, b: unknown): number => {\n\tconst left = rawValue(a) as number;\n\tconst right = rawValue(b) as number;\n\treturn left < right ? -1 : left > right ? 1 : 0;\n};\n\nconst holds = (\n\trow: Record<string, unknown>,\n\t{ field, op, value }: Condition,\n): boolean => {\n\tconst current = row[field];\n\tswitch (op) {\n\t\tcase \"eq\":\n\t\t\treturn equals(current, value);\n\t\tcase \"ne\":\n\t\t\treturn !equals(current, value);\n\t\tcase \"lt\":\n\t\tcase \"lte\":\n\t\tcase \"gt\":\n\t\tcase \"gte\": {\n\t\t\tif (current == null || value == null) return false;\n\t\t\tconst order = compare(current, value);\n\t\t\tif (op === \"lt\") return order < 0;\n\t\t\tif (op === \"lte\") return order <= 0;\n\t\t\tif (op === \"gt\") return order > 0;\n\t\t\treturn order >= 0;\n\t\t}\n\t\tcase \"in\":\n\t\t\treturn (value as readonly unknown[]).some((member) =>\n\t\t\t\tequals(current, member),\n\t\t\t);\n\t\tcase \"notIn\":\n\t\t\treturn !(value as readonly unknown[]).some((member) =>\n\t\t\t\tequals(current, member),\n\t\t\t);\n\t\tcase \"contains\":\n\t\t\treturn typeof current === \"string\" && current.includes(value as string);\n\t\tcase \"startsWith\":\n\t\t\treturn typeof current === \"string\" && current.startsWith(value as string);\n\t\tcase \"endsWith\":\n\t\t\treturn typeof current === \"string\" && current.endsWith(value as string);\n\t}\n};\n\n/** Does a row satisfy a where? The in-memory evaluator - the dummy adapter\n * runs on it, and any adapter over an unqueryable backend can too. */\nexport const matchesWhere = (\n\trow: Record<string, unknown>,\n\twhere: Record<string, unknown> = {},\n): boolean => conditionsOf(where).every((condition) => holds(row, condition));\n\n/* ----------------------------------- ops ----------------------------------- */\n\n/** Shaping for `findMany`: sort, then window. */\nexport type FindManyOptions<R> = {\n\tlimit?: number;\n\toffset?: number;\n\tsortBy?: { field: keyof R & string; direction?: \"asc\" | \"desc\" };\n};\n\n/** One model's CRUD surface. `create` is generic so a row EXTENDED by a\n * mounted module (an account with a password field) keeps its extra\n * fields through the round-trip outside a widened scope. Inside a scope\n * that mounts `v.extend` / same-name customize on the model var, {@link\n * WidenSchemaFns} rewrites `R` (see `$modelVar`) the same way it widens\n * `v.fn.type({ input: user })`. */\nexport type Collection<R, N extends string = string> = {\n\tcreate: <T extends R>(data: T) => Promise<T>;\n\tfindOne: (where: Where<R>) => Promise<R | null>;\n\tfindMany: (where?: Where<R>, options?: FindManyOptions<R>) => Promise<R[]>;\n\t/** Merge `patch` into the FIRST match - null when nothing matched. */\n\tupdate: (where: Where<R>, patch: Partial<R>) => Promise<R | null>;\n\t/** Remove every match; how many is the answer. */\n\tdelete: (where: Where<R>) => Promise<number>;\n\tcount: (where?: Where<R>) => Promise<number>;\n\t/** Claim the FIRST match: the row comes back and is GONE, atomically -\n\t * two racing consumers never both get it. Single-use credentials\n\t * (verification tokens, one-time codes) hang off this. */\n\tconsumeOne: (where: Where<R>) => Promise<R | null>;\n\t/** Add to numeric fields of the FIRST match, atomically; the where is\n\t * also the GUARD (`count: { lt: max }`), so check-and-bump is one op -\n\t * null means no row passed it. Rate limiting hangs off this. */\n\tincrementOne: (\n\t\twhere: Where<R>,\n\t\tincrements: Partial<Record<keyof R & string, number>>,\n\t) => Promise<R | null>;\n\t/**\n\t * Phantom: the model var's name. Scope resolution reads it to WIDEN\n\t * `R` with everything the scope mounts on that var - same role as\n\t * `$fnVar` on fn schemas. Optional, so plain collection objects still\n\t * satisfy the type.\n\t */\n\treadonly $modelVar?: [N];\n};\n\n/**\n * What a real database implements: six required verbs, models addressed by\n * NAME, `where` as bare-equality fields and/or operator objects\n * (`conditionsOf` normalizes either form). The storage API above never\n * changes - adapters translate it, sync or async.\n *\n * The optional verbs are ATOMICITY upgrades: without `consumeOne` /\n * `incrementOne` the storage falls back to find-then-write - correct alone,\n * racy under contention - and without `transaction` a `$transaction` block\n * runs plainly. Implement them where the backend has the primitive.\n */\nexport type StorageAdapter = {\n\tcreate: (model: string, data: Record<string, unknown>) => unknown;\n\tfindOne: (model: string, where: Record<string, unknown>) => unknown;\n\tfindMany: (\n\t\tmodel: string,\n\t\twhere?: Record<string, unknown>,\n\t\toptions?: FindManyOptions<Record<string, unknown>>,\n\t) => unknown;\n\tupdate: (\n\t\tmodel: string,\n\t\twhere: Record<string, unknown>,\n\t\tpatch: Record<string, unknown>,\n\t) => unknown;\n\tdelete: (model: string, where: Record<string, unknown>) => unknown;\n\tcount: (model: string, where?: Record<string, unknown>) => unknown;\n\t/** Atomic find-and-delete of the first match. */\n\tconsumeOne?: (model: string, where: Record<string, unknown>) => unknown;\n\t/** Atomic guarded add to numeric fields of the first match. */\n\tincrementOne?: (\n\t\tmodel: string,\n\t\twhere: Record<string, unknown>,\n\t\tincrements: Record<string, number>,\n\t) => unknown;\n\t/** Run `run` against a transaction-bound view of this adapter:\n\t * committed when it resolves, rolled back when it throws. */\n\ttransaction?: <T>(run: (tx: StorageAdapter) => Promise<T>) => Promise<T>;\n};\n\n/** The DUMMY adapter: rows in arrays, one per model. Implements the whole\n * surface - the optional verbs by mutation (single-threaded, so \"atomic\"),\n * transactions by snapshot-and-restore. */\nexport const memoryAdapter = (): StorageAdapter => {\n\tlet tables = new Map<string, Record<string, unknown>[]>();\n\tconst rows = (model: string) => {\n\t\tlet list = tables.get(model);\n\t\tif (!list) {\n\t\t\tlist = [];\n\t\t\ttables.set(model, list);\n\t\t}\n\t\treturn list;\n\t};\n\tconst self: StorageAdapter = {\n\t\tcreate: (model, data) => {\n\t\t\tconst row = { ...data };\n\t\t\trows(model).push(row);\n\t\t\treturn row;\n\t\t},\n\t\tfindOne: (model, where) =>\n\t\t\trows(model).find((row) => matchesWhere(row, where)) ?? null,\n\t\tfindMany: (model, where, options) => {\n\t\t\tlet list = rows(model).filter((row) => matchesWhere(row, where));\n\t\t\tconst sort = options?.sortBy;\n\t\t\tif (sort) {\n\t\t\t\tconst direction = sort.direction === \"desc\" ? -1 : 1;\n\t\t\t\tlist = [...list].sort(\n\t\t\t\t\t(a, b) => direction * compare(a[sort.field], b[sort.field]),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst start = options?.offset ?? 0;\n\t\t\tconst end =\n\t\t\t\toptions?.limit === undefined ? undefined : start + options.limit;\n\t\t\treturn start === 0 && end === undefined ? list : list.slice(start, end);\n\t\t},\n\t\tupdate: (model, where, patch) => {\n\t\t\tconst row = rows(model).find((r) => matchesWhere(r, where));\n\t\t\treturn row ? Object.assign(row, patch) : null;\n\t\t},\n\t\tdelete: (model, where) => {\n\t\t\tconst list = rows(model);\n\t\t\tconst keep = list.filter((row) => !matchesWhere(row, where));\n\t\t\ttables.set(model, keep);\n\t\t\treturn list.length - keep.length;\n\t\t},\n\t\tcount: (model, where) =>\n\t\t\trows(model).filter((row) => matchesWhere(row, where)).length,\n\t\tconsumeOne: (model, where) => {\n\t\t\tconst list = rows(model);\n\t\t\tconst index = list.findIndex((row) => matchesWhere(row, where));\n\t\t\treturn index === -1 ? null : list.splice(index, 1)[0];\n\t\t},\n\t\tincrementOne: (model, where, increments) => {\n\t\t\tconst row = rows(model).find((r) => matchesWhere(r, where));\n\t\t\tif (!row) return null;\n\t\t\tfor (const [field, by] of Object.entries(increments)) {\n\t\t\t\trow[field] = ((row[field] as number) ?? 0) + by;\n\t\t\t}\n\t\t\treturn row;\n\t\t},\n\t\ttransaction: async (run) => {\n\t\t\tconst snapshot = new Map(\n\t\t\t\t[...tables].map(([model, list]) => [\n\t\t\t\t\tmodel,\n\t\t\t\t\tlist.map((row) => ({ ...row })),\n\t\t\t\t]),\n\t\t\t);\n\t\t\ttry {\n\t\t\t\treturn await run(self);\n\t\t\t} catch (thrown) {\n\t\t\t\ttables = snapshot;\n\t\t\t\tthrow thrown;\n\t\t\t}\n\t\t},\n\t};\n\treturn self;\n};\n\n/* ---------------------------------- models ---------------------------------- */\n\ntype AnyVar = VarDefination<any, any, any, any>;\n\n/** Persistence facts about one field the ROW SHAPE can't say - consumed by\n * schema generators and adapters, never acted on by the runtime. (Read\n * shaping like redaction is a `$on` hook, not metadata.) Prefer declaring\n * these via `db.unique` / `db.indexed` / `db.references` / `db.id` on the\n * field schema; `ModelConfig.fields` remains an override. */\nexport type FieldMeta = {\n\t/** Primary key for this model. */\n\tid?: boolean;\n\t/** No two rows share a value. */\n\tunique?: boolean;\n\t/** Worth an index. */\n\tindex?: boolean;\n\t/** Foreign key: this field holds `model.field` values. */\n\treferences?: {\n\t\tmodel: string;\n\t\tfield: string;\n\t\tonDelete?: \"cascade\" | \"set null\" | \"restrict\";\n\t};\n};\n\n/** Read `$attrs.db` off each field of a var's object schema. */\nexport const fieldsFromSchema = (sv: AnyVar): Record<string, FieldMeta> => {\n\tconst schema = asType((sv as { schema?: unknown }).schema ?? {});\n\tif (schema.name !== \"object\" || schema.shape === undefined) return {};\n\tconst fields: Record<string, FieldMeta> = {};\n\tfor (const [key, child] of Object.entries(\n\t\tschema.shape as Record<string, unknown>,\n\t)) {\n\t\tconst meta = attrsOf(child, \"db\") as FieldMeta | undefined;\n\t\tif (meta && Object.keys(meta).length > 0) fields[key] = { ...meta };\n\t}\n\treturn fields;\n};\n\n/**\n * Schema attrs first; `ModelConfig.fields[k]` replaces that key entirely\n * when present (compat override).\n */\nexport const resolveModelFields = (\n\tinput: ModelInput,\n): Record<string, FieldMeta> | undefined => {\n\tif (isVar(input)) {\n\t\tconst fromSchema = fieldsFromSchema(input as AnyVar);\n\t\treturn Object.keys(fromSchema).length > 0 ? fromSchema : undefined;\n\t}\n\tconst config = input as ModelConfig;\n\tconst fromSchema = fieldsFromSchema(config.schema);\n\tconst override = config.fields ?? {};\n\tconst merged: Record<string, FieldMeta> = { ...fromSchema };\n\tfor (const [key, meta] of Object.entries(override)) {\n\t\tif (meta !== undefined) merged[key] = meta;\n\t}\n\treturn Object.keys(merged).length > 0 ? merged : undefined;\n};\n\n/** Normalize `$models` entries so adapters always see merged `fields`.\n * Bare vars stay bare vars (identity preserved); use\n * {@link resolveModelFields} to read schema attrs off them. ModelConfig\n * entries get `fields` filled from schema attrs + overrides. */\nconst resolveModels = <M extends StorageModels>(models: M): M => {\n\tconst out: Record<string, unknown> = {};\n\tfor (const [key, input] of Object.entries(models)) {\n\t\tif (isVar(input)) {\n\t\t\tout[key] = input;\n\t\t\tcontinue;\n\t\t}\n\t\tconst config = input as ModelConfig;\n\t\tconst fields = resolveModelFields(config);\n\t\tout[key] = fields === undefined ? { ...config } : { ...config, fields };\n\t}\n\treturn out as M;\n};\n\n/** What an op subscription hands back: `v.on` entries to mount. */\ntype SubscriptionEntries = OnEntry<string> | readonly OnEntry<string>[];\n\n/**\n * A model declared WITH its persistence: `schema` is the var (the shape),\n * `fields` carries per-field storage metadata, and each op key SUBSCRIBES\n * that op to app events - handed the bound collection op, it returns `v.on`\n * entries that mount wherever the storage does (`use: [db]`). An\n * already-built entry works in place of the fn.\n */\nexport type ModelConfig<SV extends AnyVar = AnyVar> = {\n\tschema: SV;\n\tfields?: {\n\t\t[F in keyof NonNullable<ValueOfVar<SV>>]?: FieldMeta;\n\t};\n} & {\n\t[Op in StorageOp]?:\n\t\t| ((\n\t\t\t\taction: Collection<NonNullable<ValueOfVar<SV>>>[Op],\n\t\t ) => SubscriptionEntries)\n\t\t| SubscriptionEntries;\n};\n\n/** A model is a bare var, or a config carrying the var as `schema`. */\ntype ModelInput = AnyVar | ModelConfig;\n\n/** The var behind a model input. Checked through `$var`, never `schema` -\n * a bare var also HAS a `schema` property (its type shape). */\ntype SchemaOf<T> = T extends { $var: true }\n\t? T\n\t: T extends { schema: infer SV }\n\t\t? SV\n\t\t: never;\n\ntype RowOf<T> = NonNullable<ValueOfVar<SchemaOf<T>>>;\n\n/** Declared name of the var behind a model input - brands the collection. */\ntype ModelVarName<T> = NameOfVar<SchemaOf<T>> & string;\n\nexport type StorageModels = Record<string, ModelInput>;\n\nexport type StorageOp =\n\t| \"create\"\n\t| \"findOne\"\n\t| \"findMany\"\n\t| \"update\"\n\t| \"delete\"\n\t| \"count\"\n\t| \"consumeOne\"\n\t| \"incrementOne\";\n\n/** What a storage hook sees: which model and op, with the op's arguments\n * positionally (`create` -> [data], `update` -> [where, patch], `findMany`\n * -> [where, options], `incrementOne` -> [where, increments], ...). */\nexport type StorageHookContext = {\n\t/** The model KEY being addressed (the storage's property name). */\n\tmodel: string;\n\top: StorageOp;\n\targs: readonly unknown[];\n};\n\n/** `next()` runs the op (hooks below it included) and resolves its result;\n * the hook's own return value IS the op's result - wrap, veto, transform. */\nexport type StorageHook = (\n\tc: StorageHookContext,\n\tnext: () => Promise<unknown>,\n) => unknown;\n\n/** Every target a storage hook can name - a flat union, so editors offer\n * the whole surface: exact (\"user.create\"), per-model (\"user.*\"), per-op\n * (\"*.create\"), everything (\"*\"). */\nexport type StorageTarget<M> =\n\t| \"*\"\n\t| `${keyof M & string}.${StorageOp | \"*\"}`\n\t| `*.${StorageOp}`;\n\nexport type Storage<M extends StorageModels> = {\n\t[K in keyof M]: Collection<RowOf<M[K]>, ModelVarName<M[K]>>;\n} & StorageApi<M>;\n\n/** Duck-type a storage instance - `$models` plus the `$adapter` method. */\nexport const isStorage = (value: unknown): value is Storage<StorageModels> =>\n\ttypeof value === \"object\" &&\n\tvalue !== null &&\n\t\"$models\" in value &&\n\ttypeof (value as { $adapter?: unknown }).$adapter === \"function\";\n\n/** The customization surface, `$`-prefixed so model keys never collide. */\nexport type StorageApi<M extends StorageModels> = {\n\t/** Swap the backend IN PLACE: every view of this storage - and every\n\t * module that captured it - starts hitting the new adapter. */\n\t$adapter: (adapter: StorageAdapter) => Storage<M>;\n\t/** Intercept ops: hooks stack in mount order and apply to every view\n\t * sharing this storage's state. */\n\t$on: (target: StorageTarget<M>, hook: StorageHook) => Storage<M>;\n\t/** A view WITHOUT these models - same adapter, same hooks. */\n\t$omit: <K extends keyof M & string>(...keys: K[]) => Storage<Omit<M, K>>;\n\t/** A view of ONLY these models - same adapter, same hooks. */\n\t$pick: <K extends keyof M & string>(...keys: K[]) => Storage<Pick<M, K>>;\n\t/** A view with MORE models - same adapter, same hooks. */\n\t$extend: <M2 extends StorageModels>(models: M2) => Storage<M & M2>;\n\t/** Run `fn` against a view whose ops share ONE adapter transaction -\n\t * committed when it resolves, rolled back when it throws. Same models,\n\t * same hooks (they run inside). An adapter without `transaction` runs\n\t * `fn` plainly - no atomicity, same answer. */\n\t$transaction: <T>(fn: (tx: Storage<M>) => Promise<T> | T) => Promise<T>;\n\t/** The model definitions this view exposes. */\n\t$models: M;\n};\n\n/** Adapter, hooks and produced subscription entries live HERE, shared by\n * every view of one storage - a `$pick`ed slice still writes through the\n * same backend and hook stack, and a subscription materializes ONCE (same\n * entry object across views, so double-mounting dedups by identity). */\ntype StorageState = {\n\tadapter: StorageAdapter;\n\thooks: { target: string; hook: StorageHook }[];\n\tsubscriptions: Map<string, readonly OnEntry<string>[]>;\n};\n\nconst OPS: readonly StorageOp[] = [\n\t\"create\",\n\t\"findOne\",\n\t\"findMany\",\n\t\"update\",\n\t\"delete\",\n\t\"count\",\n\t\"consumeOne\",\n\t\"incrementOne\",\n];\n\n/** The op against the backend, optional verbs falling back to the required\n * six: find-then-write - correct alone, racy under contention. The comment\n * every fallback deserves lives on `StorageAdapter`. */\nconst rawOp = (\n\tadapter: StorageAdapter,\n\tname: string,\n\top: StorageOp,\n\targs: unknown[],\n) => {\n\tif (op === \"consumeOne\" && !adapter.consumeOne) {\n\t\treturn (async () => {\n\t\t\tconst where = args[0] as Record<string, unknown>;\n\t\t\tconst row = (await adapter.findOne(name, where)) as Record<\n\t\t\t\tstring,\n\t\t\t\tunknown\n\t\t\t> | null;\n\t\t\tif (!row) return null;\n\t\t\t// Delete THE row by its own fields, not the where - an operator\n\t\t\t// where (`expiresAt: { lt }`) must not sweep other matches.\n\t\t\tawait adapter.delete(name, row);\n\t\t\treturn row;\n\t\t})();\n\t}\n\tif (op === \"incrementOne\" && !adapter.incrementOne) {\n\t\treturn (async () => {\n\t\t\tconst [where, increments] = args as [\n\t\t\t\tRecord<string, unknown>,\n\t\t\t\tRecord<string, number>,\n\t\t\t];\n\t\t\tconst row = (await adapter.findOne(name, where)) as Record<\n\t\t\t\tstring,\n\t\t\t\tunknown\n\t\t\t> | null;\n\t\t\tif (!row) return null;\n\t\t\tconst patch = Object.fromEntries(\n\t\t\t\tObject.entries(increments).map(([field, by]) => [\n\t\t\t\t\tfield,\n\t\t\t\t\t((row[field] as number) ?? 0) + by,\n\t\t\t\t]),\n\t\t\t);\n\t\t\treturn adapter.update(name, where, patch);\n\t\t})();\n\t}\n\treturn (adapter[op] as (...a: unknown[]) => unknown)(name, ...args);\n};\n\nconst buildStorage = <M extends StorageModels>(\n\tstate: StorageState,\n\tmodels: M,\n): Storage<M> => {\n\t// Every op funnels here: matching hooks compose around the adapter\n\t// call, first mounted outermost - the `v.on` rules, one layer down.\n\tconst run = (key: string, name: string, op: StorageOp, args: unknown[]) => {\n\t\tconst base = () => Promise.resolve(rawOp(state.adapter, name, op, args));\n\t\treturn state.hooks\n\t\t\t.filter((entry) => matchesTarget(entry.target, `${key}.${op}`))\n\t\t\t.reduceRight<() => Promise<unknown>>(\n\t\t\t\t(next, entry) => () =>\n\t\t\t\t\tPromise.resolve(entry.hook({ model: key, op, args }, next)),\n\t\t\t\tbase,\n\t\t\t)();\n\t};\n\n\tconst storage: Record<string, unknown> = {};\n\tfor (const [key, input] of Object.entries(models)) {\n\t\tconst def = isVar(input) ? input : (input as ModelConfig).schema;\n\t\tconst name = (def as { name: string }).name;\n\t\tconst collection = {\n\t\t\tcreate: (data: unknown) => run(key, name, \"create\", [data]),\n\t\t\tfindOne: (where: unknown) => run(key, name, \"findOne\", [where]),\n\t\t\tfindMany: (where?: unknown, options?: unknown) =>\n\t\t\t\trun(key, name, \"findMany\", [where, options]),\n\t\t\tupdate: (where: unknown, patch: unknown) =>\n\t\t\t\trun(key, name, \"update\", [where, patch]),\n\t\t\tdelete: (where: unknown) => run(key, name, \"delete\", [where]),\n\t\t\tcount: (where?: unknown) => run(key, name, \"count\", [where]),\n\t\t\tconsumeOne: (where: unknown) => run(key, name, \"consumeOne\", [where]),\n\t\t\tincrementOne: (where: unknown, increments: unknown) =>\n\t\t\t\trun(key, name, \"incrementOne\", [where, increments]),\n\t\t} satisfies Record<StorageOp, unknown>;\n\t\tstorage[key] = collection;\n\n\t\t// Op subscriptions become PROPERTIES of the storage, so mounting it\n\t\t// as a module (`use: [db]`) mounts the persistence it declared.\n\t\t// Materialized once per state - views share entry identity.\n\t\tif (isVar(input)) continue;\n\t\tfor (const op of OPS) {\n\t\t\tconst declared = (input as Record<string, unknown>)[op];\n\t\t\tif (!declared) continue;\n\t\t\tconst cacheKey = `${key}.${op}`;\n\t\t\tlet entries = state.subscriptions.get(cacheKey);\n\t\t\tif (!entries) {\n\t\t\t\tconst produced =\n\t\t\t\t\ttypeof declared === \"function\"\n\t\t\t\t\t\t? declared(collection[op])\n\t\t\t\t\t\t: (declared as SubscriptionEntries);\n\t\t\t\tentries = Array.isArray(produced) ? produced : [produced];\n\t\t\t\tstate.subscriptions.set(cacheKey, entries as OnEntry<string>[]);\n\t\t\t}\n\t\t\tentries.forEach((entry, index) => {\n\t\t\t\tstorage[`${key}$${op}${index === 0 ? \"\" : index}`] = entry;\n\t\t\t});\n\t\t}\n\t}\n\n\tconst self: Storage<M> = Object.assign(storage, {\n\t\t$adapter: (adapter: StorageAdapter) => {\n\t\t\tstate.adapter = adapter;\n\t\t\treturn self;\n\t\t},\n\t\t$on: (target: StorageTarget<M>, hook: StorageHook) => {\n\t\t\tstate.hooks.push({ target, hook });\n\t\t\treturn self;\n\t\t},\n\t\t$omit: (...keys: string[]) =>\n\t\t\tbuildStorage(\n\t\t\t\tstate,\n\t\t\t\tObject.fromEntries(\n\t\t\t\t\tObject.entries(models).filter(([key]) => !keys.includes(key)),\n\t\t\t\t) as StorageModels,\n\t\t\t),\n\t\t$pick: (...keys: string[]) =>\n\t\t\tbuildStorage(\n\t\t\t\tstate,\n\t\t\t\tObject.fromEntries(\n\t\t\t\t\tkeys.map((key) => [key, models[key]]),\n\t\t\t\t) as StorageModels,\n\t\t\t),\n\t\t$extend: (more: StorageModels) =>\n\t\t\tbuildStorage(state, { ...models, ...more }),\n\t\t$transaction: <T>(fn: (tx: Storage<M>) => Promise<T> | T) => {\n\t\t\t// The tx view is this storage with only the adapter swapped:\n\t\t\t// hooks and subscriptions stay the SHARED arrays, so they apply\n\t\t\t// (and mount) inside exactly as outside.\n\t\t\tconst inside = (adapter: StorageAdapter) =>\n\t\t\t\tPromise.resolve(fn(buildStorage({ ...state, adapter }, models)));\n\t\t\treturn state.adapter.transaction\n\t\t\t\t? state.adapter.transaction((tx) => inside(tx))\n\t\t\t\t: inside(state.adapter);\n\t\t},\n\t\t$models: resolveModels(models),\n\t} as StorageApi<M>) as Storage<M>;\n\treturn self;\n};\n\n/**\n * MANY instances of a var: each model is a collection of rows shaped like\n * the var's VALUE, addressed by the var's NAME - the var stays what it\n * always was (the scope's one current instance), the storage holds every\n * other one, and the query API is how rows move between the two.\n *\n * The returned storage is CUSTOMIZABLE through its `$` surface: `$adapter`\n * swaps the backend in place, `$on` mounts hooks around ops,\n * `$omit`/`$pick`/`$extend` derive model views over the same state, and\n * `$transaction` scopes ops to one adapter transaction.\n *\n * A model can also DECLARE its persistence: pass `{ schema, create: ... }`\n * instead of the bare var, and the op subscriptions ride on the storage as\n * mountable `v.on` entries - `use: [db]` wires them into the app. The same\n * config carries `fields` metadata (unique, index, references) for schema\n * generators to consume.\n */\nexport const makeStorage = <const M extends StorageModels>(\n\tadapter: StorageAdapter,\n\tmodels: M,\n): Storage<M> =>\n\tbuildStorage({ adapter, hooks: [], subscriptions: new Map() }, models);\n"],"mappings":"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"}
|
package/dist/types.d.cts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type LiteralString = string & Record<never, never>;
|
|
3
|
+
type Prettify<T> = { [K in keyof T]: T[K]; } & {};
|
|
4
|
+
type UnionToIntersection<U> = (U extends unknown ? (u: U) => void : never) extends ((i: infer I) => void) ? I : never;
|
|
5
|
+
type Members<P> = P extends readonly unknown[] ? P[number] : never;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { LiteralString, Members, Prettify, UnionToIntersection };
|
|
8
|
+
//# sourceMappingURL=types.d.cts.map
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type LiteralString = string & Record<never, never>;
|
|
3
|
+
type Prettify<T> = { [K in keyof T]: T[K]; } & {};
|
|
4
|
+
type UnionToIntersection<U> = (U extends unknown ? (u: U) => void : never) extends ((i: infer I) => void) ? I : never;
|
|
5
|
+
type Members<P> = P extends readonly unknown[] ? P[number] : never;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { LiteralString, Members, Prettify, UnionToIntersection };
|
|
8
|
+
//# sourceMappingURL=types.d.mts.map
|
package/dist/var.cjs
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const require_error = require("./error.cjs");
|
|
2
|
+
const require_schema = require("./schema.cjs");
|
|
3
|
+
const require_module = require("./module.cjs");
|
|
4
|
+
const require_fn = require("./fn.cjs");
|
|
5
|
+
//#region src/var.ts
|
|
6
|
+
const varRegistry = /* @__PURE__ */ new Map();
|
|
7
|
+
const makeVar = (name, options = {}) => {
|
|
8
|
+
const schema = options.schema === void 0 ? void 0 : require_schema.asType(options.schema);
|
|
9
|
+
const def = {
|
|
10
|
+
$var: true,
|
|
11
|
+
name,
|
|
12
|
+
default: options.default,
|
|
13
|
+
schema,
|
|
14
|
+
...options.attrs !== void 0 ? { $attrs: options.attrs } : {},
|
|
15
|
+
$accessor: options.accessor === true,
|
|
16
|
+
$merge: options.merge === true,
|
|
17
|
+
$derive: options.derive,
|
|
18
|
+
customize: (opts) => makeVar(name, {
|
|
19
|
+
...options,
|
|
20
|
+
default: def.default,
|
|
21
|
+
...def.$attrs !== void 0 ? { attrs: def.$attrs } : {},
|
|
22
|
+
schema: opts.schema({
|
|
23
|
+
...require_schema.vTypes,
|
|
24
|
+
fn: require_fn.fnImpl,
|
|
25
|
+
add: (shape) => ({
|
|
26
|
+
name: "object",
|
|
27
|
+
shape: {
|
|
28
|
+
...def.schema?.shape ?? {},
|
|
29
|
+
...shape
|
|
30
|
+
}
|
|
31
|
+
}),
|
|
32
|
+
replace: (schema) => schema
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
};
|
|
36
|
+
varRegistry.set(name, def);
|
|
37
|
+
return def;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Derive a var from another: computed lazily off the source on every read,
|
|
41
|
+
* so setting the source "sets" every var derived from it. Writing a
|
|
42
|
+
* derived var directly shadows the computation for that scope.
|
|
43
|
+
*/
|
|
44
|
+
const deriveVar = (name, source, get) => makeVar(name, { derive: {
|
|
45
|
+
source: source.name,
|
|
46
|
+
get
|
|
47
|
+
} });
|
|
48
|
+
/** Cells materialize lazily from the registry on first touch. */
|
|
49
|
+
const getCell = (cells, name) => {
|
|
50
|
+
const existing = cells[name];
|
|
51
|
+
if (existing) return existing;
|
|
52
|
+
const def = varRegistry.get(name);
|
|
53
|
+
const cell = {
|
|
54
|
+
value: def?.$derive ? void 0 : def?.$accessor ? {} : def?.default,
|
|
55
|
+
derive: def?.$derive,
|
|
56
|
+
shadowed: false,
|
|
57
|
+
accumulate: def?.$accessor === true || def?.$merge === true
|
|
58
|
+
};
|
|
59
|
+
cells[name] = cell;
|
|
60
|
+
return cell;
|
|
61
|
+
};
|
|
62
|
+
/** The current value, deriveds computed off their source. A name never
|
|
63
|
+
* declared and never written reads `undefined` WITHOUT materializing a
|
|
64
|
+
* cell - property probes on the context must not pollute the scope. */
|
|
65
|
+
const readVar = (cells, name) => {
|
|
66
|
+
if (!cells[name] && !varRegistry.has(name)) return void 0;
|
|
67
|
+
const cell = getCell(cells, name);
|
|
68
|
+
if (cell.derive && !cell.shadowed) {
|
|
69
|
+
const src = readVar(cells, cell.derive.source);
|
|
70
|
+
return src == null ? null : cell.derive.get(src);
|
|
71
|
+
}
|
|
72
|
+
return cell.value;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* A handler's `c`: its own fields (`name`, `value`, `fn`) in front, every
|
|
76
|
+
* other property a RAW read of the scope's vars - so a handler reading
|
|
77
|
+
* `c.<var>` cannot recurse into its own get hook.
|
|
78
|
+
*/
|
|
79
|
+
const handlerContext = (cells, base) => new Proxy(base, {
|
|
80
|
+
get: (t, prop) => prop in t ? t[prop] : typeof prop === "string" ? readVar(cells, prop) : void 0,
|
|
81
|
+
has: (t, prop) => prop in t || typeof prop === "string" && prop in cells
|
|
82
|
+
});
|
|
83
|
+
const isThenable = (value) => typeof value?.then === "function";
|
|
84
|
+
/**
|
|
85
|
+
* Every write funnels here: the readonly lock beats everything, then the
|
|
86
|
+
* matching `var.set.<name>` entries run SYNCHRONOUSLY around the actual
|
|
87
|
+
* write - call `next()` to land it, skip to cancel, throw to abort. The
|
|
88
|
+
* bare "*" target means "every fn", never "every var write".
|
|
89
|
+
*/
|
|
90
|
+
const writeVar = (frame, name, value) => {
|
|
91
|
+
if (frame.lockedBy) throw new require_error.ValidationError(`${frame.key}.readonly`, `"${frame.lockedBy}" is readonly: attempted to write var "${name}"`);
|
|
92
|
+
const cell = getCell(frame.cells, name);
|
|
93
|
+
const merged = () => {
|
|
94
|
+
const current = cell.derive && !cell.shadowed ? readVar(frame.cells, name) : cell.value;
|
|
95
|
+
return {
|
|
96
|
+
...typeof current === "object" && current !== null ? current : {},
|
|
97
|
+
...value
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const next = cell.accumulate ? merged() : value;
|
|
101
|
+
const apply = () => {
|
|
102
|
+
cell.value = next;
|
|
103
|
+
if (cell.derive) cell.shadowed = true;
|
|
104
|
+
};
|
|
105
|
+
const hooks = frame.entries.filter((e) => e.target !== "*" && require_module.matchesTarget(e.target, `var.set.${name}`));
|
|
106
|
+
if (hooks.length === 0) return apply();
|
|
107
|
+
hooks.reduceRight((proceed, entry) => () => {
|
|
108
|
+
const result = entry.handler(handlerContext(frame.cells, {
|
|
109
|
+
name,
|
|
110
|
+
value: next,
|
|
111
|
+
fn: frame.key
|
|
112
|
+
}), proceed);
|
|
113
|
+
if (isThenable(result)) throw new require_error.ValidationError(`${frame.key}.set`, `var-set handlers must be synchronous - "${String(entry.target)}" returned a promise`);
|
|
114
|
+
}, apply)();
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Context reads funnel here: matching `var.get.<name>` entries run
|
|
118
|
+
* SYNCHRONOUSLY around the raw read - `next()` yields the stored value and
|
|
119
|
+
* whatever the handler returns becomes the read result. Internal reads
|
|
120
|
+
* stay raw: derive sources, handler contexts (so a handler reading a var
|
|
121
|
+
* off `c` cannot recurse into itself) and contract checks all see the
|
|
122
|
+
* cell as-is.
|
|
123
|
+
*/
|
|
124
|
+
const readVarThrough = (frame, name) => {
|
|
125
|
+
const hooks = frame.entries.filter((e) => e.target !== "*" && require_module.matchesTarget(e.target, `var.get.${name}`));
|
|
126
|
+
if (hooks.length === 0) return readVar(frame.cells, name);
|
|
127
|
+
return hooks.reduceRight((proceed, entry) => () => {
|
|
128
|
+
const result = entry.handler(handlerContext(frame.cells, {
|
|
129
|
+
name,
|
|
130
|
+
fn: frame.key
|
|
131
|
+
}), proceed);
|
|
132
|
+
if (isThenable(result)) throw new require_error.ValidationError(`${frame.key}.get`, `var-get handlers must be synchronous - "${String(entry.target)}" returned a promise`);
|
|
133
|
+
return result;
|
|
134
|
+
}, () => readVar(frame.cells, name))();
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Merge-var reads expose a shallow view that binds any `$fn` member into
|
|
138
|
+
* this context - helpers contributed via `use` call like usable fns.
|
|
139
|
+
*/
|
|
140
|
+
const viewMergeVar = (name, value, ctx) => {
|
|
141
|
+
if (!varRegistry.get(name)?.$merge || value == null || typeof value !== "object") return value;
|
|
142
|
+
return new Proxy(value, { get: (t, prop, receiver) => {
|
|
143
|
+
const member = Reflect.get(t, prop, receiver);
|
|
144
|
+
if (typeof member !== "function" || member.$fn !== true) return member;
|
|
145
|
+
const usedArity = member.$arity;
|
|
146
|
+
return usedArity === void 0 ? (i) => member(i, ctx) : (...args) => {
|
|
147
|
+
const padded = args.slice(0, usedArity);
|
|
148
|
+
while (padded.length < usedArity) padded.push(void 0);
|
|
149
|
+
return member(...padded, ctx);
|
|
150
|
+
};
|
|
151
|
+
} });
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* The fn context for one frame: `base` carries the fixed surface (input,
|
|
155
|
+
* error, fn, types, the bound `use` fns, internal symbols) and EVERY
|
|
156
|
+
* other property is a var - read it directly (`c.session.userId`) and
|
|
157
|
+
* write it by plain assignment (`c.session = {...}`). Reads route through
|
|
158
|
+
* the `var.get` entries, writes through `writeVar` (readonly lock,
|
|
159
|
+
* `var.set` entries), and both are always synchronous. Base keys shadow
|
|
160
|
+
* vars of the same name, both ways.
|
|
161
|
+
*/
|
|
162
|
+
const contextScope = (frame, base) => new Proxy(base, {
|
|
163
|
+
get: (t, prop, receiver) => {
|
|
164
|
+
if (prop in t) return Reflect.get(t, prop, receiver);
|
|
165
|
+
if (typeof prop === "string") return viewMergeVar(prop, readVarThrough(frame, prop), receiver);
|
|
166
|
+
},
|
|
167
|
+
set: (t, prop, value, receiver) => {
|
|
168
|
+
if (typeof prop !== "string" || prop in t) return Reflect.set(t, prop, value, receiver);
|
|
169
|
+
writeVar(frame, prop, value);
|
|
170
|
+
return true;
|
|
171
|
+
},
|
|
172
|
+
has: (t, prop) => prop in t || typeof prop === "string"
|
|
173
|
+
});
|
|
174
|
+
//#endregion
|
|
175
|
+
exports.contextScope = contextScope;
|
|
176
|
+
exports.deriveVar = deriveVar;
|
|
177
|
+
exports.getCell = getCell;
|
|
178
|
+
exports.handlerContext = handlerContext;
|
|
179
|
+
exports.makeVar = makeVar;
|
|
180
|
+
exports.readVar = readVar;
|
|
181
|
+
exports.readVarThrough = readVarThrough;
|
|
182
|
+
exports.varRegistry = varRegistry;
|
|
183
|
+
exports.viewMergeVar = viewMergeVar;
|
|
184
|
+
exports.writeVar = writeVar;
|
|
185
|
+
|
|
186
|
+
//# sourceMappingURL=var.cjs.map
|
package/dist/var.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"var.cjs","names":["asType","vTypes","fnImpl","ValidationError","matchesTarget"],"sources":["../src/var.ts"],"sourcesContent":["import { ValidationError } from \"./error\";\n// Cycle with fn.ts (it imports our cells): fine, `fnImpl` is only touched\n// inside the lazy `customize` closure, never during module evaluation.\nimport { type Fn, fnImpl } from \"./fn\";\nimport { matchesTarget, type OnEntry } from \"./module\";\nimport {\n\ttype AttrBag,\n\tasType,\n\ttype DefineOutput,\n\ttype InferArgs,\n\ttype InferInput,\n\ttype TypeDefination,\n\tvTypes,\n} from \"./schema\";\nimport type { LiteralString, Prettify } from \"./types\";\n\nexport interface VarDefination<\n\tN extends LiteralString,\n\tT,\n\tSchema = unknown,\n\tSource extends string = never,\n> {\n\t$var: true;\n\tname: N;\n\tdefault?: T;\n\t/** Kept in the type so the var can also be used as an input field. */\n\tschema?: Schema;\n\ttype?: T;\n\t/** Phantom: the var this one derives from - `requires` on the source\n\t * makes this one non-null too. */\n\t$source?: Source;\n\t/** Whole-var plugin attributes (field attrs live on the schema). */\n\t$attrs?: AttrBag;\n\t/**\n\t * Object contributions from `use` modules merge instead of replacing:\n\t * same-name var defaults and same-export-key namespaces shallow-merge\n\t * onto this value (storage default + helpers). Writes accumulate too.\n\t * `v.var(..., { merge: true })` brands this as `true` so context types\n\t * fold same-key helpers onto the value.\n\t */\n\t$merge?: true | boolean;\n\tcustomize: <S>(options: {\n\t\tschema: (v: VarCustomizer<T>) => S;\n\t}) => VarDefination<N, InferInput<S>, S>;\n}\n\nexport type ValueOfVar<SV> =\n\tSV extends VarDefination<any, infer T, any, any> ? T : never;\n\nexport type NameOfVar<SV> =\n\tSV extends VarDefination<infer N, any, any, any> ? N : never;\n\n/**\n * The toolkit a `customize` callback receives. It is a SUPERSET of `v`'s\n * type constructors on purpose - the callback parameter shadows `v`, so\n * `(v) => v.add({ role: v.string() })` has to keep working.\n */\nexport type VarCustomizer<T> = typeof vTypes & {\n\t/** Declare a fn-typed field: bare `fn` is \"any function\",\n\t * `fn({ input, output })` a specific signature. */\n\tfn: Fn;\n\t// The args side goes through InferArgs, not a plain key-for-key map, so\n\t// an `optional`/defaulted added field is optional to SEND as well - the\n\t// same rule `v.object` applies to its shape.\n\tadd: <S>(\n\t\tshape: S,\n\t) => TypeDefination<InferArgs<S>, Prettify<NonNullable<T> & DefineOutput<S>>>;\n\treplace: <S>(schema: S) => S;\n};\n\nexport type VarMap = Record<string, VarDefination<any, any, any, any>>;\n\nexport const varRegistry = new Map<string, any>();\n\nexport const makeVar = (name: string, options: any = {}): any => {\n\tconst schema =\n\t\toptions.schema === undefined ? undefined : asType(options.schema);\n\tconst def: any = {\n\t\t$var: true,\n\t\tname,\n\t\tdefault: options.default,\n\t\tschema,\n\t\t...(options.attrs !== undefined ? { $attrs: options.attrs } : {}),\n\t\t$accessor: options.accessor === true,\n\t\t$merge: options.merge === true,\n\t\t$derive: options.derive,\n\t\tcustomize: (opts: any) =>\n\t\t\tmakeVar(name, {\n\t\t\t\t...options,\n\t\t\t\tdefault: def.default,\n\t\t\t\t// Carry whole-var attrs through customize (including any\n\t\t\t\t// attached after mint via withAttrs-rebinding, which goes\n\t\t\t\t// through its own path; this covers options.attrs).\n\t\t\t\t...(def.$attrs !== undefined ? { attrs: def.$attrs } : {}),\n\t\t\t\tschema: opts.schema({\n\t\t\t\t\t...vTypes,\n\t\t\t\t\tfn: fnImpl,\n\t\t\t\t\tadd: (shape: any) => ({\n\t\t\t\t\t\tname: \"object\",\n\t\t\t\t\t\tshape: { ...((def.schema?.shape as any) ?? {}), ...shape },\n\t\t\t\t\t}),\n\t\t\t\t\treplace: (schema: any) => schema,\n\t\t\t\t}),\n\t\t\t}),\n\t};\n\tvarRegistry.set(name, def);\n\treturn def;\n};\n\n/**\n * Derive a var from another: computed lazily off the source on every read,\n * so setting the source \"sets\" every var derived from it. Writing a\n * derived var directly shadows the computation for that scope.\n */\nexport const deriveVar = (name: string, source: any, get: any): any =>\n\tmakeVar(name, { derive: { source: source.name, get } });\n\n/* ---------------------------------- cells ---------------------------------- */\n\n/**\n * One var's state within a scope: the current value, plus how reads and\n * writes behave for it (derived computation, record accumulation).\n */\nexport type Cell = {\n\tvalue: unknown;\n\tderive?: { source: string; get: (value: any) => any };\n\t/** A direct write to a derived var shadows its computation. */\n\tshadowed: boolean;\n\t/** Record / merge var: `set()` merges instead of replacing. */\n\taccumulate: boolean;\n};\n\nexport type Cells = Record<string, Cell>;\n\n/** Cells materialize lazily from the registry on first touch. */\nexport const getCell = (cells: Cells, name: string): Cell => {\n\tconst existing = cells[name];\n\tif (existing) return existing;\n\tconst def = varRegistry.get(name);\n\tconst cell: Cell = {\n\t\tvalue: def?.$derive ? undefined : def?.$accessor ? {} : def?.default,\n\t\tderive: def?.$derive,\n\t\tshadowed: false,\n\t\taccumulate: def?.$accessor === true || def?.$merge === true,\n\t};\n\tcells[name] = cell;\n\treturn cell;\n};\n\n/** The current value, deriveds computed off their source. A name never\n * declared and never written reads `undefined` WITHOUT materializing a\n * cell - property probes on the context must not pollute the scope. */\nexport const readVar = (cells: Cells, name: string): unknown => {\n\tif (!cells[name] && !varRegistry.has(name)) return undefined;\n\tconst cell = getCell(cells, name);\n\tif (cell.derive && !cell.shadowed) {\n\t\tconst src = readVar(cells, cell.derive.source);\n\t\treturn src == null ? null : cell.derive.get(src);\n\t}\n\treturn cell.value;\n};\n\n/**\n * A handler's `c`: its own fields (`name`, `value`, `fn`) in front, every\n * other property a RAW read of the scope's vars - so a handler reading\n * `c.<var>` cannot recurse into its own get hook.\n */\nexport const handlerContext = (\n\tcells: Cells,\n\tbase: Record<string, unknown>,\n): Record<string, unknown> =>\n\tnew Proxy(base, {\n\t\tget: (t, prop) =>\n\t\t\tprop in t\n\t\t\t\t? (t as any)[prop]\n\t\t\t\t: typeof prop === \"string\"\n\t\t\t\t\t? readVar(cells, prop)\n\t\t\t\t\t: undefined,\n\t\thas: (t, prop) => prop in t || (typeof prop === \"string\" && prop in cells),\n\t});\n\n/* ---------------------------------- frame ---------------------------------- */\n\n/**\n * What a handle needs to know about the fn frame it was created in: the\n * scope's cells, plus everything write behavior depends on there.\n */\nexport type Frame = {\n\tcells: Cells;\n\tkey: string;\n\tlockedBy: string | undefined;\n\tentries: readonly OnEntry<string>[];\n};\n\nconst isThenable = (value: any): value is Promise<unknown> =>\n\ttypeof value?.then === \"function\";\n\n/**\n * Every write funnels here: the readonly lock beats everything, then the\n * matching `var.set.<name>` entries run SYNCHRONOUSLY around the actual\n * write - call `next()` to land it, skip to cancel, throw to abort. The\n * bare \"*\" target means \"every fn\", never \"every var write\".\n */\nexport const writeVar = (frame: Frame, name: string, value: unknown) => {\n\tif (frame.lockedBy) {\n\t\tthrow new ValidationError(\n\t\t\t`${frame.key}.readonly`,\n\t\t\t`\"${frame.lockedBy}\" is readonly: attempted to write var \"${name}\"`,\n\t\t);\n\t}\n\tconst cell = getCell(frame.cells, name);\n\tconst merged = () => {\n\t\tconst current =\n\t\t\tcell.derive && !cell.shadowed ? readVar(frame.cells, name) : cell.value;\n\t\tconst base = typeof current === \"object\" && current !== null ? current : {};\n\t\treturn { ...base, ...(value as Record<string, unknown>) };\n\t};\n\tconst next = cell.accumulate ? merged() : value;\n\tconst apply = () => {\n\t\tcell.value = next;\n\t\tif (cell.derive) cell.shadowed = true;\n\t};\n\tconst hooks = frame.entries.filter(\n\t\t(e) => e.target !== \"*\" && matchesTarget(e.target, `var.set.${name}`),\n\t);\n\tif (hooks.length === 0) return apply();\n\tconst chain = hooks.reduceRight<() => void>(\n\t\t(proceed, entry) => () => {\n\t\t\tconst result = entry.handler(\n\t\t\t\thandlerContext(frame.cells, {\n\t\t\t\t\tname,\n\t\t\t\t\tvalue: next,\n\t\t\t\t\tfn: frame.key,\n\t\t\t\t}) as any,\n\t\t\t\tproceed as any,\n\t\t\t);\n\t\t\tif (isThenable(result)) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t`${frame.key}.set`,\n\t\t\t\t\t`var-set handlers must be synchronous - \"${String(entry.target)}\" returned a promise`,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\tapply,\n\t);\n\tchain();\n};\n\n/**\n * Context reads funnel here: matching `var.get.<name>` entries run\n * SYNCHRONOUSLY around the raw read - `next()` yields the stored value and\n * whatever the handler returns becomes the read result. Internal reads\n * stay raw: derive sources, handler contexts (so a handler reading a var\n * off `c` cannot recurse into itself) and contract checks all see the\n * cell as-is.\n */\nexport const readVarThrough = (frame: Frame, name: string): unknown => {\n\tconst hooks = frame.entries.filter(\n\t\t(e) => e.target !== \"*\" && matchesTarget(e.target, `var.get.${name}`),\n\t);\n\tif (hooks.length === 0) return readVar(frame.cells, name);\n\tconst chain = hooks.reduceRight<() => unknown>(\n\t\t(proceed, entry) => () => {\n\t\t\tconst result = entry.handler(\n\t\t\t\thandlerContext(frame.cells, {\n\t\t\t\t\tname,\n\t\t\t\t\tfn: frame.key,\n\t\t\t\t}) as any,\n\t\t\t\tproceed as any,\n\t\t\t);\n\t\t\tif (isThenable(result)) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t`${frame.key}.get`,\n\t\t\t\t\t`var-get handlers must be synchronous - \"${String(entry.target)}\" returned a promise`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\t\t() => readVar(frame.cells, name),\n\t);\n\treturn chain();\n};\n\n/* ---------------------------------- scope ---------------------------------- */\n\n/**\n * Merge-var reads expose a shallow view that binds any `$fn` member into\n * this context - helpers contributed via `use` call like usable fns.\n */\nexport const viewMergeVar = (\n\tname: string,\n\tvalue: unknown,\n\tctx: unknown,\n): unknown => {\n\tconst def = varRegistry.get(name);\n\tif (!def?.$merge || value == null || typeof value !== \"object\") return value;\n\treturn new Proxy(value as object, {\n\t\tget: (t, prop, receiver) => {\n\t\t\tconst member = Reflect.get(t, prop, receiver);\n\t\t\tif (\n\t\t\t\ttypeof member !== \"function\" ||\n\t\t\t\t(member as { $fn?: boolean }).$fn !== true\n\t\t\t) {\n\t\t\t\treturn member;\n\t\t\t}\n\t\t\tconst usedArity = (member as { $arity?: number }).$arity;\n\t\t\treturn usedArity === undefined\n\t\t\t\t? (i?: unknown) => (member as any)(i, ctx)\n\t\t\t\t: (...args: unknown[]) => {\n\t\t\t\t\t\tconst padded = args.slice(0, usedArity);\n\t\t\t\t\t\twhile (padded.length < usedArity) padded.push(undefined);\n\t\t\t\t\t\treturn (member as any)(...padded, ctx);\n\t\t\t\t\t};\n\t\t},\n\t});\n};\n\n/**\n * The fn context for one frame: `base` carries the fixed surface (input,\n * error, fn, types, the bound `use` fns, internal symbols) and EVERY\n * other property is a var - read it directly (`c.session.userId`) and\n * write it by plain assignment (`c.session = {...}`). Reads route through\n * the `var.get` entries, writes through `writeVar` (readonly lock,\n * `var.set` entries), and both are always synchronous. Base keys shadow\n * vars of the same name, both ways.\n */\nexport const contextScope = (frame: Frame, base: object): any =>\n\tnew Proxy(base, {\n\t\tget: (t, prop, receiver) => {\n\t\t\tif (prop in t) return Reflect.get(t, prop, receiver);\n\t\t\tif (typeof prop === \"string\") {\n\t\t\t\treturn viewMergeVar(prop, readVarThrough(frame, prop), receiver);\n\t\t\t}\n\t\t\treturn undefined;\n\t\t},\n\t\tset: (t, prop, value, receiver) => {\n\t\t\tif (typeof prop !== \"string\" || prop in t) {\n\t\t\t\treturn Reflect.set(t, prop, value, receiver);\n\t\t\t}\n\t\t\twriteVar(frame, prop, value);\n\t\t\treturn true;\n\t\t},\n\t\thas: (t, prop) => prop in t || typeof prop === \"string\",\n\t});\n"],"mappings":"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"}
|