@isaijs/core 0.1.4 → 0.1.6
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/auth/index.d.ts +2 -2
- package/dist/auth/index.js +2 -2
- package/dist/case-DpXlJCL9.js +63 -0
- package/dist/cli/index.js +10 -6
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +119 -5
- package/dist/{create-auth-9y_43vJf.js → create-auth-BFKvQVOG.js} +18 -10
- package/dist/{data-layer-D1BFo3wO.d.ts → data-layer-43pme0Gy.d.ts} +13 -1
- package/dist/{data-layer-_TZtuy-F.js → data-layer-C5oJZ8V1.js} +16 -3
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +4 -4
- package/dist/{index-DlgVg3D8.d.ts → index-a7ElyL3B.d.ts} +67 -34
- package/dist/index.d.ts +1 -1
- package/dist/index.js +136 -17
- package/dist/{make-DKBn79cH.js → make-ByA-84h8.js} +1 -1
- package/dist/{makers-DeSNM0Nd.js → makers-P9kg68C8.js} +2 -2
- package/dist/{migrator-BU3_PHk8.js → migrator-B25prc9n.js} +152 -108
- package/dist/{base64url-C5GTYIL5.js → model-Dc3HunKb.js} +424 -418
- package/dist/{policies-D_G5Rmxv.js → policies-KsqmlX67.js} +1 -1
- package/dist/{resource-CkzoR3Rq.js → resource-BQK7bkPQ.js} +5 -3
- package/dist/{tables-IrLjsomf.js → tables-DgSwge7s.js} +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +4 -4
- package/dist/{types-S6zF5IXV.d.ts → types-BgOkJy5d.d.ts} +17 -3
- package/package.json +1 -1
- package/dist/case-BnpcvhdW.js +0 -11
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { A as fakeUlid, C as text, D as choice, E as url, F as quantity, I as sequence, M as id, N as int, O as date, P as money, R as list, S as subdomain, T as token, W as kind, _ as newToken, a as file, b as slug, c as image, d as json, f as urls, g as markdown, h as line, j as flag, k as datetime, l as images, n as phoneKind, p as email, s as files, u as define, v as personName, w as textKind, x as slugify, y as secret } from "./validation-Cppmd0RV.js";
|
|
2
|
-
import { n as snakeCase } from "./case-
|
|
2
|
+
import { n as snakeCase, r as ulid } from "./case-DpXlJCL9.js";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
//#region src/internal/scope.ts
|
|
5
5
|
/**
|
|
@@ -19,58 +19,6 @@ var Scope = class {
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
//#endregion
|
|
22
|
-
//#region src/internal/ulid.ts
|
|
23
|
-
/**
|
|
24
|
-
* ULIDs: 48 bits of milliseconds + 80 random bits, Crockford base32, 26 characters.
|
|
25
|
-
* Generated in JS (works inside a D1 batch, the same on every driver) and sortable by time.
|
|
26
|
-
*
|
|
27
|
-
* Monotonic: ids made in the same millisecond (or after the clock stepped back) reuse the
|
|
28
|
-
* last time and add one to the last random part, so ids always sort in the order they were
|
|
29
|
-
* made within an isolate or process.
|
|
30
|
-
*/
|
|
31
|
-
const ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
32
|
-
const RANDOM_LENGTH = 16;
|
|
33
|
-
function encodeTime(time) {
|
|
34
|
-
let rest = time;
|
|
35
|
-
let text = "";
|
|
36
|
-
for (let i = 0; i < 10; i++) {
|
|
37
|
-
text = ALPHABET.charAt(rest % 32) + text;
|
|
38
|
-
rest = Math.floor(rest / 32);
|
|
39
|
-
}
|
|
40
|
-
return text;
|
|
41
|
-
}
|
|
42
|
-
function randomDigits() {
|
|
43
|
-
return Array.from(crypto.getRandomValues(new Uint8Array(RANDOM_LENGTH)), (byte) => byte % 32);
|
|
44
|
-
}
|
|
45
|
-
/** Adds one to base-32 digits in place; false when they were all at the maximum. */
|
|
46
|
-
function increment(digits) {
|
|
47
|
-
for (let i = digits.length - 1; i >= 0; i--) {
|
|
48
|
-
if ((digits[i] ?? 0) < 31) {
|
|
49
|
-
digits[i] = (digits[i] ?? 0) + 1;
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
digits[i] = 0;
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
function monotonicUlid() {
|
|
57
|
-
let lastTime = -1;
|
|
58
|
-
let lastRandom = [];
|
|
59
|
-
return (now) => {
|
|
60
|
-
const time = now.getTime();
|
|
61
|
-
if (time > lastTime) {
|
|
62
|
-
lastTime = time;
|
|
63
|
-
lastRandom = randomDigits();
|
|
64
|
-
} else if (!increment(lastRandom)) {
|
|
65
|
-
lastTime += 1;
|
|
66
|
-
lastRandom = randomDigits();
|
|
67
|
-
}
|
|
68
|
-
return encodeTime(lastTime) + lastRandom.map((digit) => ALPHABET.charAt(digit)).join("");
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
/** The process-wide generator every write uses. */
|
|
72
|
-
const ulid = monotonicUlid();
|
|
73
|
-
//#endregion
|
|
74
22
|
//#region src/db/orm/audit.ts
|
|
75
23
|
/**
|
|
76
24
|
* The audit log in the ORM's write path: which writes are recorded, who makes them, and the
|
|
@@ -196,287 +144,21 @@ function actionEntry(engine, model, rowId, action, context, user) {
|
|
|
196
144
|
return engine.dialect.insert(log, [entry], context);
|
|
197
145
|
}
|
|
198
146
|
//#endregion
|
|
199
|
-
//#region src/
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
this.spec = spec;
|
|
204
|
-
}
|
|
205
|
-
/** The kind behind the field, so a field works anywhere a shape does (route inputs, `validate`). */
|
|
206
|
-
get "~kind"() {
|
|
207
|
-
return this.spec.kind;
|
|
208
|
-
}
|
|
209
|
-
get "~standard"() {
|
|
210
|
-
return this.spec.kind["~standard"];
|
|
211
|
-
}
|
|
212
|
-
/** The field may be empty (`null`). Fields are required by default. */
|
|
213
|
-
optional() {
|
|
214
|
-
return new Field({
|
|
215
|
-
...this.spec,
|
|
216
|
-
kind: this.spec.kind.optional()
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
/** The value written when a create leaves the field out. */
|
|
220
|
-
default(value) {
|
|
221
|
-
return new Field({
|
|
222
|
-
...this.spec,
|
|
223
|
-
kind: this.spec.kind.default(value)
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
/** Narrows the kind's lower limit, for a business rule. */
|
|
227
|
-
min(limit) {
|
|
228
|
-
return new Field({
|
|
229
|
-
...this.spec,
|
|
230
|
-
kind: this.spec.kind.min(limit)
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
/** Narrows the kind's upper limit, for a business rule ("at most 8 images"). */
|
|
234
|
-
max(limit) {
|
|
235
|
-
return new Field({
|
|
236
|
-
...this.spec,
|
|
237
|
-
kind: this.spec.kind.max(limit)
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
unique() {
|
|
241
|
-
return new Field({
|
|
242
|
-
...this.spec,
|
|
243
|
-
unique: true
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
/** Stores the field in a column with another name (a field renamed in code keeps its column). */
|
|
247
|
-
column(name) {
|
|
248
|
-
return new Field({
|
|
249
|
-
...this.spec,
|
|
250
|
-
column: name
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
/** Set on create and never changed: updates can't name it (a type error, and a `fixed` issue). */
|
|
254
|
-
fixed() {
|
|
255
|
-
return new Field({
|
|
256
|
-
...this.spec,
|
|
257
|
-
fixed: true
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* The changes a choice allows, from each value to the ones it may become; any other change of a
|
|
262
|
-
* stored value fails (`notAllowed`), whoever writes it. A value missing from the keys is final.
|
|
263
|
-
*
|
|
264
|
-
* f.choice(["pending", "confirmed", "cancelled"]).flow({ pending: ["confirmed", "cancelled"], confirmed: ["cancelled"] })
|
|
265
|
-
*/
|
|
266
|
-
flow(transitions) {
|
|
267
|
-
const choices = this.spec.kind.spec.choices;
|
|
268
|
-
if (!choices) throw new TypeError("isai: .flow() is for a choice");
|
|
269
|
-
for (const [from, to] of Object.entries(transitions)) for (const value of [from, ...to]) if (!choices.includes(value)) throw new TypeError(`isai: .flow() names "${value}", which isn't a choice`);
|
|
270
|
-
return new Field({
|
|
271
|
-
...this.spec,
|
|
272
|
-
flow: transitions
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* A copy of a related row's value (`.from((s) => s.criterion.maxScore)`), so the row's checks and
|
|
277
|
-
* uniques can use it. The database writes it on create and keeps it equal to its source (triggers
|
|
278
|
-
* on each table along the path, so raw SQL writes are seen too); it's never written. The path's
|
|
279
|
-
* first ref must be `.fixed()`; when every step and the final field are fixed, it's written once
|
|
280
|
-
* and no trigger is made. A plain path to one column only: a derived value is `.computed()`, and a
|
|
281
|
-
* value frozen as it was is a snapshot, not a copy.
|
|
282
|
-
*/
|
|
283
|
-
from(build) {
|
|
284
|
-
if (this.spec.maintained || this.spec.computed || this.spec.generate) throw new TypeError("isai: .from() is for a plain field (not one the database or the framework makes)");
|
|
285
|
-
return new Field({
|
|
286
|
-
...this.spec,
|
|
287
|
-
maintained: {
|
|
288
|
-
by: "copy",
|
|
289
|
-
build,
|
|
290
|
-
node: void 0
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
/** Fills a module's slot. The field's values must be the slot's (a `string` slot takes no `f.int()`). */
|
|
295
|
-
slot(slot) {
|
|
296
|
-
if (this.spec.slots.some((filled) => filled.name === slot.spec.name)) throw new TypeError(`isai: the field already fills the slot ${slot.spec.name}`);
|
|
297
|
-
return new Field({
|
|
298
|
-
...this.spec,
|
|
299
|
-
slots: [...this.spec.slots, slot.spec]
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
/** On a ref: the row this one belongs to. At most one per model. */
|
|
303
|
-
parent() {
|
|
304
|
-
this.refSpec("parent");
|
|
305
|
-
return new Field({
|
|
306
|
-
...this.spec,
|
|
307
|
-
parent: true
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
/** On a ref: deleting the referenced row deletes this one. */
|
|
311
|
-
cascade() {
|
|
312
|
-
return new Field({
|
|
313
|
-
...this.spec,
|
|
314
|
-
ref: {
|
|
315
|
-
...this.refSpec("cascade"),
|
|
316
|
-
onDelete: "cascade"
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
/** On an optional ref: deleting the referenced row empties this field. */
|
|
321
|
-
setNull() {
|
|
322
|
-
return new Field({
|
|
323
|
-
...this.spec,
|
|
324
|
-
ref: {
|
|
325
|
-
...this.refSpec("setNull"),
|
|
326
|
-
onDelete: "setNull"
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
refSpec(modifier) {
|
|
331
|
-
if (!this.spec.ref) throw new TypeError(`isai: ${modifier}() is only for ref fields`);
|
|
332
|
-
return this.spec.ref;
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
/**
|
|
336
|
-
* How sensitive a field's values are, from its kind and the slots it fills; `hidden` wins over
|
|
337
|
-
* `personal`. Rows in services stay complete: the output layer applies it.
|
|
338
|
-
*/
|
|
339
|
-
function sensitivity(spec) {
|
|
340
|
-
const marks = [spec.kind.spec.sensitive, ...spec.slots.map((slot) => slot.sensitive)];
|
|
341
|
-
if (marks.includes("hidden")) return "hidden";
|
|
342
|
-
if (marks.includes("personal")) return "personal";
|
|
343
|
-
}
|
|
344
|
-
//#endregion
|
|
345
|
-
//#region src/db/fields/kinds.ts
|
|
346
|
-
/**
|
|
347
|
-
* The built-in field kinds: each is a kind from `isai/validation` with its storage. Kinds own
|
|
348
|
-
* their limits and rules; columns never repeat them.
|
|
349
|
-
*/
|
|
350
|
-
function fieldSpec(kind, storage, options) {
|
|
351
|
-
return {
|
|
352
|
-
kind,
|
|
353
|
-
storage,
|
|
354
|
-
unique: false,
|
|
355
|
-
column: void 0,
|
|
356
|
-
ref: void 0,
|
|
357
|
-
fixed: false,
|
|
358
|
-
parent: false,
|
|
359
|
-
generate: void 0,
|
|
360
|
-
computed: void 0,
|
|
361
|
-
flow: void 0,
|
|
362
|
-
maintained: void 0,
|
|
363
|
-
encrypted: false,
|
|
364
|
-
slots: [],
|
|
365
|
-
...options
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
/** A field of a kind, stored as `storage`. */
|
|
369
|
-
function field(kind, storage, options = {}) {
|
|
370
|
-
return new Field(fieldSpec(kind, storage, options));
|
|
371
|
-
}
|
|
372
|
-
/** A field the framework fills when a create leaves it out, so writes may omit it. */
|
|
373
|
-
function filled(kind, storage, options = {}) {
|
|
374
|
-
return new Field(fieldSpec(kind, storage, options));
|
|
147
|
+
//#region src/internal/base64url.ts
|
|
148
|
+
/** base64url without padding: how isai writes random bytes as text (keys, tokens, signatures). */
|
|
149
|
+
function toBase64url(bytes) {
|
|
150
|
+
return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
375
151
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
function slug$1(options = {}) {
|
|
385
|
-
const { from } = options;
|
|
386
|
-
if (from === void 0) return field(slug(), "text", { unique: true });
|
|
387
|
-
return filled(slug(), "text", {
|
|
388
|
-
unique: true,
|
|
389
|
-
generate: {
|
|
390
|
-
from,
|
|
391
|
-
make: (values) => slugify(String(values[from] ?? "")) || void 0
|
|
392
|
-
}
|
|
393
|
-
});
|
|
152
|
+
/** The bytes, or undefined when `text` isn't base64url. */
|
|
153
|
+
function fromBase64url(text) {
|
|
154
|
+
if (!/^[A-Za-z0-9_-]*$/.test(text)) return void 0;
|
|
155
|
+
try {
|
|
156
|
+
const binary = atob(text.replace(/-/g, "+").replace(/_/g, "/"));
|
|
157
|
+
return Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
158
|
+
} catch {
|
|
159
|
+
return;
|
|
394
160
|
}
|
|
395
|
-
return {
|
|
396
|
-
line: () => field(line(), "text"),
|
|
397
|
-
text: () => field(text(), "text"),
|
|
398
|
-
markdown: () => field(markdown(), "text"),
|
|
399
|
-
personName: () => field(personName(), "text"),
|
|
400
|
-
email: () => field(email(), "text"),
|
|
401
|
-
phone: () => field(phoneKind(() => defaults().phoneRegion), "text"),
|
|
402
|
-
url: () => field(url(), "text"),
|
|
403
|
-
urls: () => field(urls(), "json"),
|
|
404
|
-
slug: slug$1,
|
|
405
|
-
/** A tenant's subdomain on the platform: one DNS label, 3–63, not a reserved name. */
|
|
406
|
-
subdomain: () => field(subdomain(), "text"),
|
|
407
|
-
/** Encrypted at rest with the database's `secretKey`, and hidden. Never compared, ordered or unique. */
|
|
408
|
-
secret: () => field(secret(), "text", { encrypted: true }),
|
|
409
|
-
/**
|
|
410
|
-
* A human-friendly number made by the database on create (`f.sequence({ prefix: "ORD-" })`):
|
|
411
|
-
* `ORD-0042` in code and JSON, stored as its number, so it orders as one.
|
|
412
|
-
*/
|
|
413
|
-
sequence: (options = {}) => field(sequence(options), "integer", {
|
|
414
|
-
unique: true,
|
|
415
|
-
maintained: { by: "sequence" }
|
|
416
|
-
}),
|
|
417
|
-
/**
|
|
418
|
-
* How many rows of a reverse relation there are (`f.count({ of: "members" })`), or how many match
|
|
419
|
-
* `where` (over their own fields). Kept by the database as they're created, changed and deleted,
|
|
420
|
-
* so checks can use it (`e.confirmedCount.lte(e.capacity)`).
|
|
421
|
-
*/
|
|
422
|
-
count: (options) => field(quantity(), "integer", { maintained: {
|
|
423
|
-
by: "count",
|
|
424
|
-
of: options.of,
|
|
425
|
-
where: options.where
|
|
426
|
-
} }),
|
|
427
|
-
/** Random and unique, made on create. */
|
|
428
|
-
token: () => filled(token(), "text", {
|
|
429
|
-
unique: true,
|
|
430
|
-
generate: {
|
|
431
|
-
from: void 0,
|
|
432
|
-
make: newToken
|
|
433
|
-
}
|
|
434
|
-
}),
|
|
435
|
-
int: () => field(int(), "integer"),
|
|
436
|
-
quantity: () => field(quantity(), "integer"),
|
|
437
|
-
money: () => field(money(), "integer"),
|
|
438
|
-
flag: () => field(flag(), "boolean"),
|
|
439
|
-
datetime: () => field(datetime(), "datetime"),
|
|
440
|
-
date: () => field(date(), "date"),
|
|
441
|
-
choice: (values) => field(choice(values), "text"),
|
|
442
|
-
/** A JSON value of a shape (`{ shape: { name: f.line() } }`, or `f.list(...)`). */
|
|
443
|
-
json: (options) => field(json(options.shape), "json"),
|
|
444
|
-
/** A list, for shapes (`f.json({ shape: f.list(...) })`, route inputs). Not a column itself. */
|
|
445
|
-
list,
|
|
446
|
-
/** A file; `{ storage }` names a storage other than `default`. */
|
|
447
|
-
file: (options = {}) => field(file(options), "json"),
|
|
448
|
-
image: (options = {}) => field(image(options), "json"),
|
|
449
|
-
files: (options = {}) => field(files(options), "json"),
|
|
450
|
-
images: (options = {}) => field(images(options), "json"),
|
|
451
|
-
/** A row of another model, by its id. Restricts deletes of that row unless `.cascade()` / `.setNull()`. */
|
|
452
|
-
ref: (model, options = {}) => new Field(fieldSpec(id(`a ${model} id`), "text", { ref: {
|
|
453
|
-
model,
|
|
454
|
-
onDelete: "restrict",
|
|
455
|
-
related: options.related
|
|
456
|
-
} })),
|
|
457
|
-
/** A project kind: a base type, a normalisation and a rule. */
|
|
458
|
-
define: (definition) => field(define(definition), STORAGE_OF[definition.type])
|
|
459
|
-
};
|
|
460
161
|
}
|
|
461
|
-
/**
|
|
462
|
-
* The fields every model has without declaring them. The framework fills them on every write
|
|
463
|
-
* (a ULID, the clock's time), so their type says "defaulted" and writes may omit them.
|
|
464
|
-
*/
|
|
465
|
-
function implicitFields() {
|
|
466
|
-
return {
|
|
467
|
-
id: filled(textKind("id", {
|
|
468
|
-
max: 255,
|
|
469
|
-
fake: fakeUlid
|
|
470
|
-
}), "text"),
|
|
471
|
-
createdAt: filled(datetime(), "datetime"),
|
|
472
|
-
updatedAt: filled(datetime(), "datetime")
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
const IMPLICIT_FIELDS = [
|
|
476
|
-
"id",
|
|
477
|
-
"createdAt",
|
|
478
|
-
"updatedAt"
|
|
479
|
-
];
|
|
480
162
|
//#endregion
|
|
481
163
|
//#region src/db/conditions/ast.ts
|
|
482
164
|
/** Every node in a tree, depth first. */
|
|
@@ -529,6 +211,64 @@ function* nodesOf(node) {
|
|
|
529
211
|
case "placeholder": return;
|
|
530
212
|
}
|
|
531
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* The condition with each operand `swap` replaces (it returns the operand itself to keep it),
|
|
216
|
+
* looking inside arithmetic and conditions used as values. A relation's own rows (an aggregate's
|
|
217
|
+
* `where`, a `some`) are left as they are.
|
|
218
|
+
*/
|
|
219
|
+
function mapOperands(node, swap) {
|
|
220
|
+
const operand = (o) => {
|
|
221
|
+
const swapped = swap(o);
|
|
222
|
+
if (swapped !== o) return swapped;
|
|
223
|
+
switch (o.t) {
|
|
224
|
+
case "math": return {
|
|
225
|
+
...o,
|
|
226
|
+
left: operand(o.left),
|
|
227
|
+
right: operand(o.right)
|
|
228
|
+
};
|
|
229
|
+
case "coalesce": return {
|
|
230
|
+
...o,
|
|
231
|
+
items: o.items.map(operand)
|
|
232
|
+
};
|
|
233
|
+
case "local": return {
|
|
234
|
+
...o,
|
|
235
|
+
operand: operand(o.operand)
|
|
236
|
+
};
|
|
237
|
+
case "truth": return {
|
|
238
|
+
...o,
|
|
239
|
+
condition: mapOperands(o.condition, swap)
|
|
240
|
+
};
|
|
241
|
+
default: return o;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
switch (node.t) {
|
|
245
|
+
case "compare": return {
|
|
246
|
+
...node,
|
|
247
|
+
left: operand(node.left),
|
|
248
|
+
right: operand(node.right)
|
|
249
|
+
};
|
|
250
|
+
case "isNull":
|
|
251
|
+
case "in":
|
|
252
|
+
case "text": return {
|
|
253
|
+
...node,
|
|
254
|
+
operand: operand(node.operand)
|
|
255
|
+
};
|
|
256
|
+
case "and":
|
|
257
|
+
case "or": return {
|
|
258
|
+
...node,
|
|
259
|
+
items: node.items.map((item) => mapOperands(item, swap))
|
|
260
|
+
};
|
|
261
|
+
case "not": return {
|
|
262
|
+
...node,
|
|
263
|
+
item: mapOperands(node.item, swap)
|
|
264
|
+
};
|
|
265
|
+
default: return node;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/** A condition's totals over a whole table (`rows(Product).count()`): aggregates with no owner. */
|
|
269
|
+
function tableTotals(node) {
|
|
270
|
+
return [...nodesOf(node)].filter((n) => n.t === "aggregate" && !n.relation.owner);
|
|
271
|
+
}
|
|
532
272
|
/** The row's own fields a condition reads, in first-seen order (refs and relations excluded). */
|
|
533
273
|
function fieldsOf(node) {
|
|
534
274
|
const fields = /* @__PURE__ */ new Set();
|
|
@@ -845,88 +585,370 @@ var Relation = class {
|
|
|
845
585
|
sum(build, where) {
|
|
846
586
|
return this.aggregate("sum", build, where, void 0);
|
|
847
587
|
}
|
|
848
|
-
/** The smallest value over the related rows, or those that match (null when there are none). */
|
|
849
|
-
min(build, where) {
|
|
850
|
-
return this.aggregate("min", build, where, void 0);
|
|
588
|
+
/** The smallest value over the related rows, or those that match (null when there are none). */
|
|
589
|
+
min(build, where) {
|
|
590
|
+
return this.aggregate("min", build, where, void 0);
|
|
591
|
+
}
|
|
592
|
+
/** The largest value over the related rows, or those that match (null when there are none). */
|
|
593
|
+
max(build, where) {
|
|
594
|
+
return this.aggregate("max", build, where, void 0);
|
|
595
|
+
}
|
|
596
|
+
aggregate(fn, value, where, storage) {
|
|
597
|
+
const scope = newScope();
|
|
598
|
+
const rows = this.rows(scope);
|
|
599
|
+
const valueNode = value?.(rows).node;
|
|
600
|
+
return new Expr({
|
|
601
|
+
t: "aggregate",
|
|
602
|
+
fn,
|
|
603
|
+
relation: {
|
|
604
|
+
...this.relation,
|
|
605
|
+
scope
|
|
606
|
+
},
|
|
607
|
+
value: valueNode,
|
|
608
|
+
where: where?.(rows).node,
|
|
609
|
+
storage: storage ?? (valueNode ? storageOf(valueNode) : void 0) ?? "real"
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
/** The storage of an operand's value, when it's known (for encoding the literals compared with it). */
|
|
614
|
+
function storageOf(node) {
|
|
615
|
+
switch (node.t) {
|
|
616
|
+
case "column":
|
|
617
|
+
case "aggregate": return node.storage;
|
|
618
|
+
case "now": return "datetime";
|
|
619
|
+
case "math": return node.op === "/" ? "real" : storageOf(node.left) ?? storageOf(node.right);
|
|
620
|
+
case "coalesce": return node.items.map(storageOf).find((storage) => storage !== void 0);
|
|
621
|
+
case "local": return node.unit === "date" ? "date" : "text";
|
|
622
|
+
case "truth": return "boolean";
|
|
623
|
+
case "total": return node.storage;
|
|
624
|
+
case "value":
|
|
625
|
+
case "bound": return;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/** A computed value's node: an expression, or a condition used as a value. */
|
|
629
|
+
function valueNode(value) {
|
|
630
|
+
if (value instanceof Condition) return {
|
|
631
|
+
t: "truth",
|
|
632
|
+
condition: value.node
|
|
633
|
+
};
|
|
634
|
+
if (value instanceof Expr) return value.node;
|
|
635
|
+
throw new TypeError("isai: a computed field must be an expression or a condition");
|
|
636
|
+
}
|
|
637
|
+
/** A condition's node, from a condition or a boolean computed field. */
|
|
638
|
+
function conditionNode(value) {
|
|
639
|
+
if (value instanceof Condition) return value.node;
|
|
640
|
+
if (value instanceof Expr && value.node.t === "truth") return value.node.condition;
|
|
641
|
+
if (value instanceof Expr) return {
|
|
642
|
+
t: "compare",
|
|
643
|
+
op: "=",
|
|
644
|
+
left: value.node,
|
|
645
|
+
right: {
|
|
646
|
+
t: "value",
|
|
647
|
+
value: true
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
throw new TypeError("isai: a condition is expected here (row(Model).field.is(…), any(…), byRole)");
|
|
651
|
+
}
|
|
652
|
+
/** As a condition: a condition, or a boolean computed field. */
|
|
653
|
+
const asCondition = (value) => value instanceof Condition ? value : new Condition(conditionNode(value));
|
|
654
|
+
function all(...conditions) {
|
|
655
|
+
return new Condition({
|
|
656
|
+
t: "and",
|
|
657
|
+
items: conditions.map(conditionNode)
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
/** Any of them (OR). */
|
|
661
|
+
function any(...conditions) {
|
|
662
|
+
return new Condition({
|
|
663
|
+
t: "or",
|
|
664
|
+
items: conditions.map(conditionNode)
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
function not(condition) {
|
|
668
|
+
return asCondition(condition).not();
|
|
669
|
+
}
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region src/db/fields/field.ts
|
|
672
|
+
var Field = class Field {
|
|
673
|
+
spec;
|
|
674
|
+
constructor(spec) {
|
|
675
|
+
this.spec = spec;
|
|
676
|
+
}
|
|
677
|
+
/** The kind behind the field, so a field works anywhere a shape does (route inputs, `validate`). */
|
|
678
|
+
get "~kind"() {
|
|
679
|
+
return this.spec.kind;
|
|
680
|
+
}
|
|
681
|
+
get "~standard"() {
|
|
682
|
+
return this.spec.kind["~standard"];
|
|
683
|
+
}
|
|
684
|
+
/** The field may be empty (`null`). Fields are required by default. */
|
|
685
|
+
optional() {
|
|
686
|
+
return new Field({
|
|
687
|
+
...this.spec,
|
|
688
|
+
kind: this.spec.kind.optional()
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
/** The value written when a create leaves the field out. */
|
|
692
|
+
default(value) {
|
|
693
|
+
return new Field({
|
|
694
|
+
...this.spec,
|
|
695
|
+
kind: this.spec.kind.default(value)
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
/** Narrows the kind's lower limit, for a business rule. */
|
|
699
|
+
min(limit) {
|
|
700
|
+
return new Field({
|
|
701
|
+
...this.spec,
|
|
702
|
+
kind: this.spec.kind.min(limit)
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
/** Narrows the kind's upper limit, for a business rule ("at most 8 images"). */
|
|
706
|
+
max(limit) {
|
|
707
|
+
return new Field({
|
|
708
|
+
...this.spec,
|
|
709
|
+
kind: this.spec.kind.max(limit)
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
unique() {
|
|
713
|
+
return new Field({
|
|
714
|
+
...this.spec,
|
|
715
|
+
unique: true
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
/** Stores the field in a column with another name (a field renamed in code keeps its column). */
|
|
719
|
+
column(name) {
|
|
720
|
+
return new Field({
|
|
721
|
+
...this.spec,
|
|
722
|
+
column: name
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
/** Set on create and never changed: updates can't name it (a type error, and a `fixed` issue). */
|
|
726
|
+
fixed() {
|
|
727
|
+
return new Field({
|
|
728
|
+
...this.spec,
|
|
729
|
+
fixed: true
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* The changes a choice allows, from each value to the ones it may become; any other change of a
|
|
734
|
+
* stored value fails (`notAllowed`), whoever writes it. A value missing from the keys is final.
|
|
735
|
+
*
|
|
736
|
+
* f.choice(["pending", "confirmed", "cancelled"]).flow({ pending: ["confirmed", "cancelled"], confirmed: ["cancelled"] })
|
|
737
|
+
*/
|
|
738
|
+
flow(transitions) {
|
|
739
|
+
const choices = this.spec.kind.spec.choices;
|
|
740
|
+
if (!choices) throw new TypeError("isai: .flow() is for a choice");
|
|
741
|
+
for (const [from, to] of Object.entries(transitions)) for (const value of [from, ...to]) if (!choices.includes(value)) throw new TypeError(`isai: .flow() names "${value}", which isn't a choice`);
|
|
742
|
+
return new Field({
|
|
743
|
+
...this.spec,
|
|
744
|
+
flow: transitions
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* A copy of a related row's value (`.from((s) => s.criterion.maxScore)`), so the row's checks and
|
|
749
|
+
* uniques can use it. The database writes it on create and keeps it equal to its source (triggers
|
|
750
|
+
* on each table along the path, so raw SQL writes are seen too); it's never written. The path's
|
|
751
|
+
* first ref must be `.fixed()`; when every step and the final field are fixed, it's written once
|
|
752
|
+
* and no trigger is made. A plain path to one column only: a derived value is `.computed()`, and a
|
|
753
|
+
* value frozen as it was is a snapshot, not a copy.
|
|
754
|
+
*/
|
|
755
|
+
from(build) {
|
|
756
|
+
if (this.spec.maintained || this.spec.computed || this.spec.generate) throw new TypeError("isai: .from() is for a plain field (not one the database or the framework makes)");
|
|
757
|
+
return new Field({
|
|
758
|
+
...this.spec,
|
|
759
|
+
maintained: {
|
|
760
|
+
by: "copy",
|
|
761
|
+
build,
|
|
762
|
+
node: void 0
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
/** Fills a module's slot. The field's values must be the slot's (a `string` slot takes no `f.int()`). */
|
|
767
|
+
slot(slot) {
|
|
768
|
+
if (this.spec.slots.some((filled) => filled.name === slot.spec.name)) throw new TypeError(`isai: the field already fills the slot ${slot.spec.name}`);
|
|
769
|
+
return new Field({
|
|
770
|
+
...this.spec,
|
|
771
|
+
slots: [...this.spec.slots, slot.spec]
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
/** On a ref: the row this one belongs to. At most one per model. */
|
|
775
|
+
parent() {
|
|
776
|
+
this.refSpec("parent");
|
|
777
|
+
return new Field({
|
|
778
|
+
...this.spec,
|
|
779
|
+
parent: true
|
|
780
|
+
});
|
|
851
781
|
}
|
|
852
|
-
/**
|
|
853
|
-
|
|
854
|
-
return
|
|
782
|
+
/** On a ref: deleting the referenced row deletes this one. */
|
|
783
|
+
cascade() {
|
|
784
|
+
return new Field({
|
|
785
|
+
...this.spec,
|
|
786
|
+
ref: {
|
|
787
|
+
...this.refSpec("cascade"),
|
|
788
|
+
onDelete: "cascade"
|
|
789
|
+
}
|
|
790
|
+
});
|
|
855
791
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
...this.relation,
|
|
865
|
-
scope
|
|
866
|
-
},
|
|
867
|
-
value: valueNode,
|
|
868
|
-
where: where?.(rows).node,
|
|
869
|
-
storage: storage ?? (valueNode ? storageOf(valueNode) : void 0) ?? "real"
|
|
792
|
+
/** On an optional ref: deleting the referenced row empties this field. */
|
|
793
|
+
setNull() {
|
|
794
|
+
return new Field({
|
|
795
|
+
...this.spec,
|
|
796
|
+
ref: {
|
|
797
|
+
...this.refSpec("setNull"),
|
|
798
|
+
onDelete: "setNull"
|
|
799
|
+
}
|
|
870
800
|
});
|
|
871
801
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
switch (node.t) {
|
|
876
|
-
case "column":
|
|
877
|
-
case "aggregate": return node.storage;
|
|
878
|
-
case "now": return "datetime";
|
|
879
|
-
case "math": return node.op === "/" ? "real" : storageOf(node.left) ?? storageOf(node.right);
|
|
880
|
-
case "coalesce": return node.items.map(storageOf).find((storage) => storage !== void 0);
|
|
881
|
-
case "local": return node.unit === "date" ? "date" : "text";
|
|
882
|
-
case "truth": return "boolean";
|
|
883
|
-
case "total": return node.storage;
|
|
884
|
-
case "value":
|
|
885
|
-
case "bound": return;
|
|
802
|
+
refSpec(modifier) {
|
|
803
|
+
if (!this.spec.ref) throw new TypeError(`isai: ${modifier}() is only for ref fields`);
|
|
804
|
+
return this.spec.ref;
|
|
886
805
|
}
|
|
806
|
+
};
|
|
807
|
+
/**
|
|
808
|
+
* How sensitive a field's values are, from its kind and the slots it fills; `hidden` wins over
|
|
809
|
+
* `personal`. Rows in services stay complete: the output layer applies it.
|
|
810
|
+
*/
|
|
811
|
+
function sensitivity(spec) {
|
|
812
|
+
const marks = [spec.kind.spec.sensitive, ...spec.slots.map((slot) => slot.sensitive)];
|
|
813
|
+
if (marks.includes("hidden")) return "hidden";
|
|
814
|
+
if (marks.includes("personal")) return "personal";
|
|
887
815
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
816
|
+
//#endregion
|
|
817
|
+
//#region src/db/fields/kinds.ts
|
|
818
|
+
/**
|
|
819
|
+
* The built-in field kinds: each is a kind from `isai/validation` with its storage. Kinds own
|
|
820
|
+
* their limits and rules; columns never repeat them.
|
|
821
|
+
*/
|
|
822
|
+
function fieldSpec(kind, storage, options) {
|
|
823
|
+
return {
|
|
824
|
+
kind,
|
|
825
|
+
storage,
|
|
826
|
+
unique: false,
|
|
827
|
+
column: void 0,
|
|
828
|
+
ref: void 0,
|
|
829
|
+
fixed: false,
|
|
830
|
+
parent: false,
|
|
831
|
+
generate: void 0,
|
|
832
|
+
computed: void 0,
|
|
833
|
+
flow: void 0,
|
|
834
|
+
maintained: void 0,
|
|
835
|
+
encrypted: false,
|
|
836
|
+
slots: [],
|
|
837
|
+
...options
|
|
893
838
|
};
|
|
894
|
-
if (value instanceof Expr) return value.node;
|
|
895
|
-
throw new TypeError("isai: a computed field must be an expression or a condition");
|
|
896
839
|
}
|
|
897
|
-
/** A
|
|
898
|
-
function
|
|
899
|
-
|
|
900
|
-
if (value instanceof Expr && value.node.t === "truth") return value.node.condition;
|
|
901
|
-
if (value instanceof Expr) return {
|
|
902
|
-
t: "compare",
|
|
903
|
-
op: "=",
|
|
904
|
-
left: value.node,
|
|
905
|
-
right: {
|
|
906
|
-
t: "value",
|
|
907
|
-
value: true
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
throw new TypeError("isai: a condition is expected here (row(Model).field.is(…), any(…), byRole)");
|
|
840
|
+
/** A field of a kind, stored as `storage`. */
|
|
841
|
+
function field(kind, storage, options = {}) {
|
|
842
|
+
return new Field(fieldSpec(kind, storage, options));
|
|
911
843
|
}
|
|
912
|
-
/**
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
return new Condition({
|
|
916
|
-
t: "and",
|
|
917
|
-
items: conditions.map(conditionNode)
|
|
918
|
-
});
|
|
844
|
+
/** A field the framework fills when a create leaves it out, so writes may omit it. */
|
|
845
|
+
function filled(kind, storage, options = {}) {
|
|
846
|
+
return new Field(fieldSpec(kind, storage, options));
|
|
919
847
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
848
|
+
const STORAGE_OF = {
|
|
849
|
+
text: "text",
|
|
850
|
+
integer: "integer",
|
|
851
|
+
number: "real",
|
|
852
|
+
boolean: "boolean"
|
|
853
|
+
};
|
|
854
|
+
/** The kinds every project gets as `f`. `defaults` is read when a value is checked. */
|
|
855
|
+
function fieldKinds(defaults) {
|
|
856
|
+
function slug$1(options = {}) {
|
|
857
|
+
const { from } = options;
|
|
858
|
+
if (from === void 0) return field(slug(), "text", { unique: true });
|
|
859
|
+
return filled(slug(), "text", {
|
|
860
|
+
unique: true,
|
|
861
|
+
generate: {
|
|
862
|
+
from,
|
|
863
|
+
make: (values) => slugify(String(values[from] ?? "")) || void 0
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
return {
|
|
868
|
+
line: () => field(line(), "text"),
|
|
869
|
+
text: () => field(text(), "text"),
|
|
870
|
+
markdown: () => field(markdown(), "text"),
|
|
871
|
+
personName: () => field(personName(), "text"),
|
|
872
|
+
email: () => field(email(), "text"),
|
|
873
|
+
phone: () => field(phoneKind(() => defaults().phoneRegion), "text"),
|
|
874
|
+
url: () => field(url(), "text"),
|
|
875
|
+
urls: () => field(urls(), "json"),
|
|
876
|
+
slug: slug$1,
|
|
877
|
+
/** A tenant's subdomain on the platform: one DNS label, 3–63, not a reserved name. */
|
|
878
|
+
subdomain: () => field(subdomain(), "text"),
|
|
879
|
+
/** Encrypted at rest with the database's `secretKey`, and hidden. Never compared, ordered or unique. */
|
|
880
|
+
secret: () => field(secret(), "text", { encrypted: true }),
|
|
881
|
+
/**
|
|
882
|
+
* A human-friendly number made by the database on create (`f.sequence({ prefix: "ORD-" })`):
|
|
883
|
+
* `ORD-0042` in code and JSON, stored as its number, so it orders as one.
|
|
884
|
+
*/
|
|
885
|
+
sequence: (options = {}) => field(sequence(options), "integer", {
|
|
886
|
+
unique: true,
|
|
887
|
+
maintained: { by: "sequence" }
|
|
888
|
+
}),
|
|
889
|
+
/**
|
|
890
|
+
* How many rows of a reverse relation there are (`f.count({ of: "members" })`), or how many match
|
|
891
|
+
* `where` (over their own fields). Kept by the database as they're created, changed and deleted,
|
|
892
|
+
* so checks can use it (`e.confirmedCount.lte(e.capacity)`).
|
|
893
|
+
*/
|
|
894
|
+
count: (options) => field(quantity(), "integer", { maintained: {
|
|
895
|
+
by: "count",
|
|
896
|
+
of: options.of,
|
|
897
|
+
where: options.where
|
|
898
|
+
} }),
|
|
899
|
+
/** Random and unique, made on create. */
|
|
900
|
+
token: () => filled(token(), "text", {
|
|
901
|
+
unique: true,
|
|
902
|
+
generate: {
|
|
903
|
+
from: void 0,
|
|
904
|
+
make: newToken
|
|
905
|
+
}
|
|
906
|
+
}),
|
|
907
|
+
int: () => field(int(), "integer"),
|
|
908
|
+
quantity: () => field(quantity(), "integer"),
|
|
909
|
+
money: () => field(money(), "integer"),
|
|
910
|
+
flag: () => field(flag(), "boolean"),
|
|
911
|
+
datetime: () => field(datetime(), "datetime"),
|
|
912
|
+
date: () => field(date(), "date"),
|
|
913
|
+
choice: (values) => field(choice(values), "text"),
|
|
914
|
+
/** A JSON value of a shape (`{ shape: { name: f.line() } }`, or `f.list(...)`). */
|
|
915
|
+
json: (options) => field(json(options.shape), "json"),
|
|
916
|
+
/** A list, for shapes (`f.json({ shape: f.list(...) })`, route inputs). Not a column itself. */
|
|
917
|
+
list,
|
|
918
|
+
/** A file; `{ storage }` names a storage other than `default`. */
|
|
919
|
+
file: (options = {}) => field(file(options), "json"),
|
|
920
|
+
image: (options = {}) => field(image(options), "json"),
|
|
921
|
+
files: (options = {}) => field(files(options), "json"),
|
|
922
|
+
images: (options = {}) => field(images(options), "json"),
|
|
923
|
+
/** A row of another model, by its id. Restricts deletes of that row unless `.cascade()` / `.setNull()`. */
|
|
924
|
+
ref: (model, options = {}) => new Field(fieldSpec(id(`a ${model} id`), "text", { ref: {
|
|
925
|
+
model,
|
|
926
|
+
onDelete: "restrict",
|
|
927
|
+
related: options.related
|
|
928
|
+
} })),
|
|
929
|
+
/** A project kind: a base type, a normalisation and a rule. */
|
|
930
|
+
define: (definition) => field(define(definition), STORAGE_OF[definition.type])
|
|
931
|
+
};
|
|
926
932
|
}
|
|
927
|
-
|
|
928
|
-
|
|
933
|
+
/**
|
|
934
|
+
* The fields every model has without declaring them. The framework fills them on every write
|
|
935
|
+
* (a ULID, the clock's time), so their type says "defaulted" and writes may omit them.
|
|
936
|
+
*/
|
|
937
|
+
function implicitFields() {
|
|
938
|
+
return {
|
|
939
|
+
id: filled(textKind("id", {
|
|
940
|
+
max: 255,
|
|
941
|
+
fake: fakeUlid
|
|
942
|
+
}), "text"),
|
|
943
|
+
createdAt: filled(datetime(), "datetime"),
|
|
944
|
+
updatedAt: filled(datetime(), "datetime")
|
|
945
|
+
};
|
|
929
946
|
}
|
|
947
|
+
const IMPLICIT_FIELDS = [
|
|
948
|
+
"id",
|
|
949
|
+
"createdAt",
|
|
950
|
+
"updatedAt"
|
|
951
|
+
];
|
|
930
952
|
//#endregion
|
|
931
953
|
//#region src/db/schema/rows.ts
|
|
932
954
|
/**
|
|
@@ -1292,20 +1314,4 @@ function defineModel(registry, name, declared, traits = [], origin = {}) {
|
|
|
1292
1314
|
return Object.keys(computed).length > 0 ? model.computed(computed) : model;
|
|
1293
1315
|
}
|
|
1294
1316
|
//#endregion
|
|
1295
|
-
|
|
1296
|
-
/** base64url without padding: how isai writes random bytes as text (keys, tokens, signatures). */
|
|
1297
|
-
function toBase64url(bytes) {
|
|
1298
|
-
return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1299
|
-
}
|
|
1300
|
-
/** The bytes, or undefined when `text` isn't base64url. */
|
|
1301
|
-
function fromBase64url(text) {
|
|
1302
|
-
if (!/^[A-Za-z0-9_-]*$/.test(text)) return void 0;
|
|
1303
|
-
try {
|
|
1304
|
-
const binary = atob(text.replace(/-/g, "+").replace(/_/g, "/"));
|
|
1305
|
-
return Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
1306
|
-
} catch {
|
|
1307
|
-
return;
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
//#endregion
|
|
1311
|
-
export { unbound as A, aroundUpsert as B, not as C, nodesOf as D, fieldsOf as E, sensitivity as F, ulid as G, assertNotLog as H, actionEntry as I, Scope as K, aroundCreate as L, field as M, fieldKinds as N, readsNow as O, Field as P, aroundDelete as R, count as S, storageOf as T, isRecorded as U, asActor as V, wrap as W, Total as _, columnName as a, asCondition as b, fieldOf as c, Column as d, Condition as f, Relation as g, Now as h, assertNotEncrypted as i, IMPLICIT_FIELDS as j, readsOtherRows as k, computedFieldOf as l, Expr as m, toBase64url as n, columnNode as o, Embed as p, Model as r, defineModel as s, fromBase64url as t, rowOf as u, all as v, now as w, conditionNode as x, any as y, aroundUpdate as z };
|
|
1317
|
+
export { mapOperands as A, aroundDelete as B, asCondition as C, now as D, not as E, unbound as F, isRecorded as G, aroundUpsert as H, fromBase64url as I, wrap as K, toBase64url as L, readsNow as M, readsOtherRows as N, storageOf as O, tableTotals as P, actionEntry as R, any as S, count as T, asActor as U, aroundUpdate as V, assertNotLog as W, Expr as _, defineModel as a, Total as b, rowOf as c, fieldKinds as d, Field as f, Embed as g, Condition as h, columnNode as i, nodesOf as j, fieldsOf as k, IMPLICIT_FIELDS as l, Column as m, assertNotEncrypted as n, fieldOf as o, sensitivity as p, Scope as q, columnName as r, computedFieldOf as s, Model as t, field as u, Now as v, conditionNode as w, all as x, Relation as y, aroundCreate as z };
|