@isaijs/core 0.1.0 → 0.1.1

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.
Files changed (45) hide show
  1. package/dist/adapters/cache/cloudflare-kv/index.d.ts +1 -1
  2. package/dist/adapters/database/d1/index.d.ts +1 -1
  3. package/dist/adapters/database/durable-object/index.d.ts +1 -1
  4. package/dist/adapters/database/sqlite-node/index.d.ts +1 -1
  5. package/dist/adapters/mail/gmail/index.d.ts +1 -1
  6. package/dist/adapters/pdf/cloudflare-browser/index.d.ts +1 -1
  7. package/dist/adapters/queue/cloudflare/index.d.ts +1 -1
  8. package/dist/adapters/ratelimit/cloudflare/index.d.ts +1 -1
  9. package/dist/adapters/ratelimit/memory/index.d.ts +1 -1
  10. package/dist/adapters/runtime/cloudflare/index.d.ts +1 -1
  11. package/dist/adapters/runtime/cloudflare/index.js +1 -1
  12. package/dist/adapters/storage/disk/index.d.ts +1 -1
  13. package/dist/adapters/storage/memory/index.d.ts +1 -1
  14. package/dist/adapters/storage/r2/index.d.ts +1 -1
  15. package/dist/auth/index.d.ts +2 -2
  16. package/dist/auth/index.js +2 -2
  17. package/dist/{base64url-DwWPqoOT.js → base64url-K_wCmvxR.js} +23 -9
  18. package/dist/cli/index.js +12 -12
  19. package/dist/client/index.d.ts +5 -5
  20. package/dist/client/index.js +3 -3
  21. package/dist/{config-D6PexpSC.js → config-0PGFbJh6.js} +13 -6
  22. package/dist/{create-auth-Byf1-VJw.js → create-auth-y8E8cxkn.js} +5 -5
  23. package/dist/{data-layer-BNtz40PV.js → data-layer-D9UPOK6L.js} +9 -7
  24. package/dist/{data-layer-BMviRJaW.d.ts → data-layer-ubPfZvFV.d.ts} +20 -11
  25. package/dist/db/index.d.ts +3 -3
  26. package/dist/db/index.js +5 -5
  27. package/dist/{index-CBWFoL3s.d.ts → index-B6ipokoP.d.ts} +35 -25
  28. package/dist/{index-BVCrbMav.d.ts → index-C-WmIKxT.d.ts} +5 -6
  29. package/dist/{index-BKwTOLop.d.ts → index-Ce5p2-EB.d.ts} +2 -2
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +47 -30
  32. package/dist/{issues-Bxq4JExX.d.ts → issues-CKto95IW.d.ts} +3 -1
  33. package/dist/{make-DKzPd2b_.js → make-WQpkYW8N.js} +17 -2
  34. package/dist/{migrator-C65RM_K-.js → migrator-6oWUefLA.js} +68 -9
  35. package/dist/{policies-yVzFCjxU.js → policies-pFzslmSB.js} +3 -3
  36. package/dist/ports/index.d.ts +1 -1
  37. package/dist/{resource-9fjxJgmz.js → resource-C17NvNa3.js} +42 -15
  38. package/dist/{tables-C3Uza4qK.js → tables-C4ipMFjg.js} +4 -4
  39. package/dist/testing/index.d.ts +2 -2
  40. package/dist/testing/index.js +6 -6
  41. package/dist/{types-BFc_NEx8.d.ts → types-DpOcIuDl.d.ts} +15 -12
  42. package/dist/validation/index.d.ts +2 -2
  43. package/dist/validation/index.js +1 -1
  44. package/dist/{validation-l5JG-PFn.js → validation-FyOpSZqE.js} +2 -3
  45. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import { W as ValidationError, j as id } from "./validation-l5JG-PFn.js";
2
- import { A as unbound, B as aroundUpsert, E as fieldsOf, G as ulid, H as assertNotLog, I as actionEntry, L as aroundCreate, O as readsNow, R as aroundDelete, T as storageOf, W as wrap, a as columnName, c as fieldOf, f as Condition, h as Now, i as assertNotEncrypted, j as IMPLICIT_FIELDS, k as readsOtherRows, m as Expr, n as toBase64url, o as columnNode, t as fromBase64url$1, u as rowOf$1, x as conditionNode, z as aroundUpdate } from "./base64url-DwWPqoOT.js";
1
+ import { W as ValidationError, j as id } from "./validation-FyOpSZqE.js";
2
+ import { A as unbound, B as aroundUpsert, E as fieldsOf, G as ulid, H as assertNotLog, I as actionEntry, L as aroundCreate, O as readsNow, R as aroundDelete, T as storageOf, W as wrap, a as columnName, c as fieldOf, f as Condition, h as Now, i as assertNotEncrypted, j as IMPLICIT_FIELDS, k as readsOtherRows, m as Expr, n as toBase64url, o as columnNode, t as fromBase64url$1, u as rowOf$1, x as conditionNode, z as aroundUpdate } from "./base64url-K_wCmvxR.js";
3
3
  import { t as NotImplementedError } from "./errors-mCNLlXg9.js";
4
4
  //#region src/db/errors.ts
5
5
  var DatabaseError = class extends Error {
@@ -16,15 +16,32 @@ var CheckFailed = class extends DatabaseError {
16
16
  this.check = check;
17
17
  }
18
18
  };
19
- /** A unique field (or combination) already has this value. */
19
+ /**
20
+ * A unique field (or combination) already has this value. `fields` are the unique's own; `issues`
21
+ * put one on each for a form, except that a field made from another (a slug from its title) puts
22
+ * it on that one, the field a person typed.
23
+ */
20
24
  var UniqueFailed = class extends DatabaseError {
21
25
  model;
22
26
  fields;
23
27
  name = "UniqueFailed";
28
+ issues;
24
29
  constructor(model, fields, options) {
25
30
  super(`${model}: ${fields.join(", ")} must be unique`, options);
26
31
  this.model = model;
27
32
  this.fields = fields;
33
+ this.issues = fields.map((field) => {
34
+ const source = options?.from?.[field];
35
+ return source === void 0 ? {
36
+ path: [field],
37
+ code: "taken",
38
+ message: "Already taken"
39
+ } : {
40
+ path: [source],
41
+ code: "taken",
42
+ message: "Already taken, or too like one that is"
43
+ };
44
+ });
28
45
  }
29
46
  };
30
47
  /** A ref points at a row that doesn't exist, or a delete is restricted by rows that reference it. */
@@ -947,6 +964,20 @@ const audit = {
947
964
  * Migration operations and rules as SQLite DDL. Tables hold only what can't live elsewhere:
948
965
  * columns (nullable; "required" is a rule in code), the primary key and foreign keys. Uniques
949
966
  * are separate indexes and guards are triggers, so changing either never rebuilds a table.
967
+ *
968
+ * Where each rule runs (one-field rules in code, everything else in the database):
969
+ *
970
+ * required, a kind's limits and format, choices JS only, in the ORM's write path
971
+ * unique a unique index (concurrent writes both see "free")
972
+ * ref a foreign key, and an index
973
+ * a number's limits (≥ 0, a narrowed min / max) a guard, on updates only: `stock - 2` is SQL's
974
+ * rules over counts and `.from()` copies a guard: the database changes them
975
+ * a `.check()` over several columns a guard: a partial update needs the stored values
976
+ *
977
+ * A guard is a trigger that raises (`RAISE(ABORT, 'isai:check:<model>.<rule>')`, parsed into the
978
+ * same `CheckFailed` JS gives), because a D1 batch rolls back only on an error. One insert trigger
979
+ * per table; update triggers are scoped to the columns their rules read (`BEFORE UPDATE OF …`).
980
+ * Writers outside the ORM (raw SQL, the D1 console) skip the one-field rules; guards still apply.
950
981
  */
951
982
  const TYPES = {
952
983
  text: "TEXT",
@@ -1293,7 +1324,15 @@ function translate(error, dialect, models) {
1293
1324
  case "unique": {
1294
1325
  const model = models.find((m) => m.table === violation.table);
1295
1326
  const fields = model ? violation.columns.map((c) => fieldAt(model, c)) : violation.columns;
1296
- return new UniqueFailed(model?.name ?? violation.table, fields, { cause: error });
1327
+ const from = {};
1328
+ for (const field of fields) {
1329
+ const source = model && fieldOf(model, field)?.generate?.from;
1330
+ if (source) from[field] = source;
1331
+ }
1332
+ return new UniqueFailed(model?.name ?? violation.table, fields, {
1333
+ cause: error,
1334
+ from
1335
+ });
1297
1336
  }
1298
1337
  case "reference": return new ReferenceFailed("A referenced row doesn't exist, or other rows still refer to this one", { cause: error });
1299
1338
  default: return error;
@@ -1925,7 +1964,7 @@ var FindOne = class FindOne extends Maybe {
1925
1964
  * The write pipeline's first half, in JS, before any SQL: fill implicit and generated fields,
1926
1965
  * read every value through its kind (defaults, normalisation, one-field rules), then run the
1927
1966
  * checks the write can decide. Rules over values the database holds are guards, enforced in
1928
- * SQL (planning/migrations.md).
1967
+ * SQL.
1929
1968
  */
1930
1969
  const MANAGED$1 = new Set(IMPLICIT_FIELDS);
1931
1970
  /** The one row of a `traits.singleton()` model has this id, so a second create fails as a duplicate. */
@@ -2198,9 +2237,29 @@ var DeleteMany = class DeleteMany extends Conditions {
2198
2237
  //#endregion
2199
2238
  //#region src/db/orm/database.ts
2200
2239
  /**
2201
- * The database handle (`app.db` in services). Async everywhere, one API on every driver
2202
- * (planning/orm.md → The API): `…One` by a key, `…Many` by a condition or a list.
2203
- * Rows are plain data: no identity map, no change tracking.
2240
+ * The database handle (`app.db` in services). Async everywhere, one API on every driver:
2241
+ * `…One` by a key (one of the model's declared uniques: `{ id }`, `{ email }`, `{ event, user }`),
2242
+ * `…Many` by a condition (chained `.where()`, so policies, filters and client queries combine) or a
2243
+ * list. Rows are plain data: no identity map, no change tracking.
2244
+ *
2245
+ * await db.findOne(Event, { id }).mustExist() // Row, or NotFound (a 404)
2246
+ * await db.findMany(Event).where((e) => e.startsAt.after(now)).order("startsAt").limit(20)
2247
+ * await db.updateOne(Variant, { id }, { stock: (v) => v.stock.minus(2) }) // an SQL expression
2248
+ * await db.upsertOne(Registration, { event, user }, { create: { … }, update: { … } })
2249
+ * await app.db.atomic((tx) => { const order = tx.createOne(Order, { … }); tx.createOne(Line, { order, … }) })
2250
+ *
2251
+ * - Absence isn't an error: `findOne`, `updateOne`, `deleteOne` give `undefined` unless
2252
+ * `.mustExist()`. A refused write always throws its typed error.
2253
+ * - Conflicts belong to `upsert…`: `createOne` / `createMany` always fail on a duplicate.
2254
+ * - `updateMany` / `deleteMany` refuse to run without a `.where()` (every row is
2255
+ * `.where(() => all())`), and take no order or limit (SQLite has no `UPDATE … LIMIT`).
2256
+ * - No `.select()`: a row is always the model's full row. Shaping output is a resource's job,
2257
+ * hiding sensitive values the output layer's.
2258
+ * - `now` (and `now.plus("8d")`) is a value in writes, resolved once per write or `atomic`, from the
2259
+ * framework's clock (which tests set), so every row in it gets the same instant.
2260
+ * - `atomic` is write-only on every driver: D1 can't run an interactive transaction, and a rule
2261
+ * that depended on the driver would make app code depend on the target. Read before it; enforce
2262
+ * rules with constraints (`quantity` ≥ 0), counters (`f.count`) or conditional writes.
2204
2263
  */
2205
2264
  function isRawValue(value) {
2206
2265
  return value === null || typeof value === "string" || typeof value === "number" || value instanceof Uint8Array;
@@ -2484,7 +2543,7 @@ function forUpkeep(db) {
2484
2543
  return db[UPKEEP]();
2485
2544
  }
2486
2545
  /**
2487
- * An action on a row, in the audit log (`markPaid`, `signIn`; planning/audit.md): who took it, from
2546
+ * An action on a row, in the audit log (`markPaid`, `signIn`): who took it, from
2488
2547
  * the request, unless `user` says (a sign-in, before the request has a user). Nothing when the
2489
2548
  * model isn't recorded. Internal, like `forUpkeep`.
2490
2549
  */
@@ -1,7 +1,7 @@
1
- import { D as nodesOf, U as isRecorded, a as columnName, f as Condition, m as Expr, u as rowOf, x as conditionNode } from "./base64url-DwWPqoOT.js";
1
+ import { D as nodesOf, U as isRecorded, a as columnName, f as Condition, m as Expr, u as rowOf, x as conditionNode } from "./base64url-K_wCmvxR.js";
2
2
  //#region src/auth/enforce.ts
3
3
  /**
4
- * Checking a policy (planning/policies.md → How a check runs): its action's condition, with the
4
+ * Checking a policy: its action's condition, with the
5
5
  * placeholders bound for this request, then simplified so constant parts drop out:
6
6
  *
7
7
  * anyone → true
@@ -236,7 +236,7 @@ async function bindFor(node, policies, model, action, asker) {
236
236
  //#endregion
237
237
  //#region src/auth/policies.ts
238
238
  /**
239
- * Policies (planning/policies.md): who may do what to a model's rows, one policy per model, each
239
+ * Policies: who may do what to a model's rows, one policy per model, each
240
240
  * action a condition. Every action is a permission, keyed `<model in camelCase>.<action>`
241
241
  * (`product.publish`), which roles in `app.ts` list. A model's policy always has `read`,
242
242
  * `create`, `update` and `delete`, `byRole` unless it says otherwise.
@@ -1,2 +1,2 @@
1
- import { _ as StoredObject, a as DatabaseDriver, b as TenantDatabases, c as MailMessage, d as RateLimiter, f as Row, g as Storage, h as Sql, i as DatabaseCapabilities, l as PdfRenderer, m as ServiceRef, n as Body, o as DialectName, p as Runtime, r as Cache, s as MailDriver, t as AppCore, u as Queue, v as TaskMessage, y as TenantCore } from "../index-BKwTOLop.js";
1
+ import { _ as StoredObject, a as DatabaseDriver, b as TenantDatabases, c as MailMessage, d as RateLimiter, f as Row, g as Storage, h as Sql, i as DatabaseCapabilities, l as PdfRenderer, m as ServiceRef, n as Body, o as DialectName, p as Runtime, r as Cache, s as MailDriver, t as AppCore, u as Queue, v as TaskMessage, y as TenantCore } from "../index-Ce5p2-EB.js";
2
2
  export { AppCore, Body, Cache, DatabaseCapabilities, DatabaseDriver, DialectName, MailDriver, MailMessage, PdfRenderer, Queue, RateLimiter, Row, Runtime, ServiceRef, Sql, Storage, StoredObject, TaskMessage, TenantCore, TenantDatabases };
@@ -1,12 +1,12 @@
1
- import { B as toKind, R as object, V as validate, W as ValidationError } from "./validation-l5JG-PFn.js";
2
- import { G as UniqueFailed, U as NotFound, V as CheckFailed, W as ReferenceFailed } from "./migrator-C65RM_K-.js";
3
- import { a as columnName, b as asCondition, j as IMPLICIT_FIELDS, l as computedFieldOf, m as Expr, p as Embed } from "./base64url-DwWPqoOT.js";
4
- import { r as actionsOf } from "./policies-yVzFCjxU.js";
1
+ import { B as toKind, R as object, V as validate, W as ValidationError } from "./validation-FyOpSZqE.js";
2
+ import { G as UniqueFailed, U as NotFound, V as CheckFailed, W as ReferenceFailed } from "./migrator-6oWUefLA.js";
3
+ import { a as columnName, b as asCondition, j as IMPLICIT_FIELDS, l as computedFieldOf, m as Expr, p as Embed } from "./base64url-K_wCmvxR.js";
4
+ import { r as actionsOf } from "./policies-pFzslmSB.js";
5
5
  import { n as RESERVED, t as LOOKUPS } from "./syntax-CrUZFrS7.js";
6
6
  //#region src/api/fail.ts
7
7
  /**
8
8
  * `fail(code, message)`: the app's own business rules, for what can't be a `.check()`, a flow or a
9
- * unique. Always a 409; app code never picks a status (planning/requests.md → Errors).
9
+ * unique. Always a 409; app code never picks a status.
10
10
  */
11
11
  var Rejected = class extends Error {
12
12
  code;
@@ -24,8 +24,25 @@ function fail(code, message) {
24
24
  //#endregion
25
25
  //#region src/api/errors.ts
26
26
  /**
27
- * The one error format (planning/requests.md → Errors): `{ error: { code, message, … }, requestId }`.
28
- * The status comes from the error; app code never picks one.
27
+ * The one error format: `{ error: { code, message, … }, requestId }`. `code` is stable and
28
+ * machine-readable, `message` is for people, and typed errors add their own fields (`issues`,
29
+ * `model`, `check`, `fields`, `action`). `requestId` is also the `X-Request-Id` header.
30
+ *
31
+ * The status comes from the error; app code never picks one:
32
+ *
33
+ * 400 ValidationError input, or a write, fails its kinds (`issues`, each with its path)
34
+ * 401 SignInRequired no session, or an anonymous request a policy denies
35
+ * 403 NotAllowed a row the user can read, without the action
36
+ * 403 CrossSiteRequest a cookie session's write from a site the app doesn't trust
37
+ * 404 NotFound `.mustExist()`, a row the user can't read, an unknown path or host
38
+ * 405 MethodNotAllowed a known path with another method (`Allow`)
39
+ * 409 UniqueFailed (`fields`, and `issues` for a form), CheckFailed, ReferenceFailed, or the
40
+ * app's own code from `fail`
41
+ * 411/413/415 uploads without a size, too large, of a refused type; a body over
42
+ * the JSON limit (413) or not JSON (415)
43
+ * 429 TooManyRequests anonymous writes past the per-IP limit (`Retry-After`)
44
+ * 503 TenantSuspended a suspended tenant
45
+ * 500 InternalError anything else: logged with its stack as one JSON line, nothing sent
29
46
  */
30
47
  /** An error the pipeline raises itself, already carrying its status and code. */
31
48
  var HttpError = class extends Error {
@@ -47,7 +64,7 @@ const methodNotAllowed = (allowed) => new HttpError(405, "MethodNotAllowed", "Th
47
64
  const tooLarge = (limit) => new HttpError(413, "TooLarge", `The body must be at most ${limit / 1024 / 1024} MB`);
48
65
  /** Over the per-IP limit for anonymous writes. */
49
66
  const tooManyRequests = () => new HttpError(429, "TooManyRequests", "Too many requests; try again in a minute", { "Retry-After": "60" });
50
- /** A tenant a platform admin suspended: its data is kept, and it answers nothing (planning/tenancy.md). */
67
+ /** A tenant a platform admin suspended: its data is kept, and it answers nothing. */
51
68
  const tenantSuspended = () => new HttpError(503, "TenantSuspended", "This site is suspended");
52
69
  const lengthRequired = () => new HttpError(411, "LengthRequired", "Send the file's size (Content-Length)");
53
70
  const fileTooLarge = (maxBytes) => new HttpError(413, "TooLarge", `The file must be at most ${maxBytes / 1024 / 1024} MB`);
@@ -85,7 +102,8 @@ function describe(error) {
85
102
  code: "UniqueFailed",
86
103
  message: error.message,
87
104
  model: error.model,
88
- fields: error.fields
105
+ fields: error.fields,
106
+ issues: error.issues
89
107
  }
90
108
  };
91
109
  if (error instanceof CheckFailed) return {
@@ -170,7 +188,7 @@ function failure(error, requestId, place) {
170
188
  const QUERY_METHODS = /* @__PURE__ */ new Set(["GET", "DELETE"]);
171
189
  /**
172
190
  * One declared route. The pipeline finds these among an app module's exports. `O` is what its
173
- * handler returns, awaited: the typed client reads it (planning/client.md).
191
+ * handler returns, awaited: the typed client reads it.
174
192
  */
175
193
  var Route = class {
176
194
  method;
@@ -209,8 +227,12 @@ const route = {
209
227
  //#endregion
210
228
  //#region src/api/input.ts
211
229
  /**
212
- * `input` (planning/requests.md → Input): the JSON body on writes, the query string on GET / DELETE,
213
- * validated by the endpoint's kinds. Unknown keys are issues, never dropped.
230
+ * `input`: the JSON body on writes, the query string on GET / DELETE, validated by the endpoint's
231
+ * kinds in their JSON forms (money in minor units, dates in ISO 8601, refs by id). Unknown keys are
232
+ * issues, never dropped, so a client's typo doesn't pass silently.
233
+ *
234
+ * One source per method: on writes the query string belongs to the framework, never `input`.
235
+ * `params` are strings typed from the path and aren't validated: a malformed id finds no row (404).
214
236
  */
215
237
  /** The largest JSON body a request may send. Uploads have their own path. */
216
238
  const BODY_LIMIT = 1048576;
@@ -295,7 +317,7 @@ async function readText(request) {
295
317
  //#endregion
296
318
  //#region src/resources/query.ts
297
319
  /**
298
- * A resource list's query syntax (planning/resources.md → Client query syntax): Django's lookups,
320
+ * A resource list's query syntax: Django's lookups,
299
321
  * as Django REST framework and django-filter take them.
300
322
  *
301
323
  * GET /api/products?category__slug=rice&price__gte=1000&price__lt=5000&onSale&search=basmati
@@ -513,7 +535,7 @@ function filterFor(field, op, raw) {
513
535
  //#endregion
514
536
  //#region src/resources/resource.ts
515
537
  /**
516
- * `resource(Model, options)` (planning/resources.md): what the API exposes over a model, a named
538
+ * `resource(Model, options)`: what the API exposes over a model, a named
517
539
  * shape the model's policy always applies to and that can only narrow it. Each resource exported
518
540
  * from an app module is mounted at `/api/<export name in kebab-case>` (mount.ts).
519
541
  *
@@ -525,6 +547,11 @@ function filterFor(field, op, raw) {
525
547
  * custom: { archive },
526
548
  * })
527
549
  *
550
+ * Models are never exposed directly: adding a model never publishes an API. A resource over a model
551
+ * without a policy denies every request, so declaring one isn't enough to open it. A role-dependent
552
+ * view of the same rows is two resources (`products`, `manageProducts`), never one with hidden
553
+ * fields; the resource *is* the shape, so there's no `?fields=` or `?include=`.
554
+ *
528
555
  * A resource is checked when it's declared, against its model: its columns are the model's,
529
556
  * its key is unique, `create` covers the required fields, the owner column is never writable, a
530
557
  * singleton has no key. Its actions are checked against the model's policy when the app loads.
@@ -552,7 +579,7 @@ function fieldAt(model, node) {
552
579
  const NAME = /^[a-z][A-Za-z0-9]*$/;
553
580
  /**
554
581
  * A declared resource, with what the engine reads from it. `C` is its custom actions, as declared:
555
- * the typed client reads what each returns (planning/client.md).
582
+ * the typed client reads what each returns.
556
583
  */
557
584
  var Resource = class {
558
585
  model;
@@ -1,7 +1,7 @@
1
- import { r as Model, s as defineModel } from "./base64url-DwWPqoOT.js";
1
+ import { r as Model, s as defineModel } from "./base64url-K_wCmvxR.js";
2
2
  //#region src/db/schema/builtins.ts
3
3
  /**
4
- * Built-in tables (planning/auth.md → Extending): sets of models a part of isai owns, switched on
4
+ * Built-in tables: sets of models a part of isai owns, switched on
5
5
  * in `db/index.ts` with `use: [base.tenancy({ database: "platform" })]`. They're defined in the
6
6
  * schema's registry by `createDataLayer`, so they exist without being declared, and refs reach them
7
7
  * by name (`f.ref("Tenant")`). An app adds columns to one with `builtins.Tenant.extend({ … })`,
@@ -77,7 +77,7 @@ function authTables(f, options) {
77
77
  return new TableSet("auth", options.database, authDefinitions(f, "", false));
78
78
  }
79
79
  /**
80
- * The platform's own copy (planning/tenancy.md → End of v1), in the platform database with
80
+ * The platform's own copy, in the platform database with
81
81
  * `base.tenancy()`: the same tables, prefixed (`PlatformUser`, `PlatformSession`…), since model
82
82
  * names are one namespace across the app's databases. Sealed: the platform's are never extended.
83
83
  */
@@ -164,7 +164,7 @@ function authDefinitions(f, prefix, sealAll) {
164
164
  user: f.ref(named("User"), { related: "roles" }).cascade(),
165
165
  role: f.ref(named("Role"), { related: "holders" }).cascade(),
166
166
  /**
167
- * Held by the platform (planning/tenancy.md → End of v1): the tenant's people, the owner
167
+ * Held by the platform: the tenant's people, the owner
168
168
  * included, can't take it away or suspend its holder.
169
169
  */
170
170
  held: f.choice(HELD).optional()
@@ -1,5 +1,5 @@
1
- import { a as DatabaseDriver, g as Storage } from "../index-BKwTOLop.js";
2
- import { I as Clock, L as FixedClock, R as fixedClock, V as AnyModel, i as Row, l as Overrides, p as Database, s as Makers, u as Seed } from "../types-BFc_NEx8.js";
1
+ import { a as DatabaseDriver, g as Storage } from "../index-Ce5p2-EB.js";
2
+ import { I as Clock, L as FixedClock, R as fixedClock, V as AnyModel, i as Row, l as Overrides, p as Database, s as Makers, u as Seed } from "../types-DpOcIuDl.js";
3
3
  //#region src/testing/conformance/database.d.ts
4
4
  /**
5
5
  * Makes the driver under test. Every call must return a driver on an **empty** database:
@@ -1,8 +1,8 @@
1
1
  import { t as __exportAll } from "../rolldown-runtime-D7D4PA-g.js";
2
- import { M as dialectFor, P as fixedClock, a as currentRelease, b as Database, c as rollback, i as acquireLock, k as keyCondition, l as setReleaseStatus, m as emptySnapshot, n as migrateFresh, o as holdOf, p as generateMigration, r as sync, s as releaseLock, t as migrate, u as startRelease, v as dataStep, y as MigrationError, z as utc } from "../migrator-C65RM_K-.js";
3
- import { n as toBase64url, r as Model } from "../base64url-DwWPqoOT.js";
4
- import { t as createDataLayer } from "../data-layer-BNtz40PV.js";
5
- import { S as seedAs, i as makers, m as forgetGoogleKeys, n as TENANT, o as authOf, t as PLATFORM, u as GOOGLE } from "../config-D6PexpSC.js";
2
+ import { M as dialectFor, P as fixedClock, a as currentRelease, b as Database, c as rollback, i as acquireLock, k as keyCondition, l as setReleaseStatus, m as emptySnapshot, n as migrateFresh, o as holdOf, p as generateMigration, r as sync, s as releaseLock, t as migrate, u as startRelease, v as dataStep, y as MigrationError, z as utc } from "../migrator-6oWUefLA.js";
3
+ import { n as toBase64url, r as Model } from "../base64url-K_wCmvxR.js";
4
+ import { t as createDataLayer } from "../data-layer-D9UPOK6L.js";
5
+ import { S as seedAs, i as makers, m as forgetGoogleKeys, n as TENANT, o as authOf, t as PLATFORM, u as GOOGLE } from "../config-0PGFbJh6.js";
6
6
  import { describe, expect, it } from "vitest";
7
7
  //#region src/testing/conformance/database.ts
8
8
  /**
@@ -720,7 +720,7 @@ var conformance_exports = /* @__PURE__ */ __exportAll({
720
720
  //#endregion
721
721
  //#region src/testing/database.ts
722
722
  /**
723
- * A database per test (planning/data-layer.md → Seeds and fakes): migrated fresh from the
723
+ * A database per test: migrated fresh from the
724
724
  * models, filled from a seed, with `make` and `many` for the test's own rows.
725
725
  *
726
726
  * let t: TestDatabase
@@ -842,7 +842,7 @@ async function fakeGoogle(client) {
842
842
  //#endregion
843
843
  //#region src/testing/tenant.ts
844
844
  /**
845
- * A tenant per test (planning/tenancy.md → Development and tests): a fresh platform database with
845
+ * A tenant per test: a fresh platform database with
846
846
  * the tenant's row, and the tenant's own database migrated fresh from the app's models and filled
847
847
  * from a seed that runs in it (`tenant` in its context), as `inTenant` runs one.
848
848
  *
@@ -1,5 +1,5 @@
1
- import { a as DatabaseDriver, f as Row$1, h as Sql, o as DialectName } from "./index-BKwTOLop.js";
2
- import { E as list, G as FileType, K as ImageType, M as IdInput, T as Shape, at as Measure, et as AnyKind, g as KindDefinition, h as Definable, it as KindTypes, ot as Sensitivity, q as StoredFile, ut as StandardProps, w as Output, x as Input$2 } from "./index-BVCrbMav.js";
1
+ import { a as DatabaseDriver, f as Row$1, h as Sql, o as DialectName } from "./index-Ce5p2-EB.js";
2
+ import { E as list, G as FileType, K as ImageType, M as IdInput, T as Shape, at as Measure, et as AnyKind, g as KindDefinition, h as Definable, it as KindTypes, ot as Sensitivity, q as StoredFile, ut as StandardProps, w as Output, x as Input$2 } from "./index-C-WmIKxT.js";
3
3
  //#region src/db/fields/slot.d.ts
4
4
  interface SlotSpec {
5
5
  /** Namespaced by module: `auth.owner`. */
@@ -168,8 +168,11 @@ declare class Field<Output, Input = Output, Optional extends boolean = false, Ha
168
168
  flow(this: Field<Output, Input, Optional, HasDefault, Meta>, transitions: { readonly [From in Output & string]?: readonly (Output & string)[]; }): Field<Output, Input, Optional, HasDefault, Meta>;
169
169
  /**
170
170
  * A copy of a related row's value (`.from((s) => s.criterion.maxScore)`), so the row's checks and
171
- * uniques can use it. The database writes it on create and keeps it equal to its source; it's
172
- * never written. The path's first ref must be `.fixed()`.
171
+ * uniques can use it. The database writes it on create and keeps it equal to its source (triggers
172
+ * on each table along the path, so raw SQL writes are seen too); it's never written. The path's
173
+ * first ref must be `.fixed()`; when every step and the final field are fixed, it's written once
174
+ * and no trigger is made. A plain path to one column only: a derived value is `.computed()`, and a
175
+ * value frozen as it was is a snapshot, not a copy.
173
176
  */
174
177
  from(build: (row: any) => Expr<unknown, unknown>): Field<Output, Input, Optional, HasDefault, Meta & {
175
178
  readonly maintained: true;
@@ -276,7 +279,7 @@ declare function fieldKinds(defaults: () => SchemaDefaults): {
276
279
  }) => Field<Output<S>, Input$2<S>, false, false, {}>;
277
280
  /** A list, for shapes (`f.json({ shape: f.list(...) })`, route inputs). Not a column itself. */
278
281
  list: typeof list;
279
- /** A file; `{ storage }` names a storage other than `default` (planning/uploads.md). */
282
+ /** A file; `{ storage }` names a storage other than `default`. */
280
283
  file: (options?: {
281
284
  readonly types?: readonly FileType[];
282
285
  readonly storage?: string;
@@ -636,7 +639,7 @@ interface RelationTarget {
636
639
  readonly field: string;
637
640
  readonly spec: FieldSpec;
638
641
  }
639
- /** The audit log's settings, when `base.audit` is in the `use` list (planning/audit.md). */
642
+ /** The audit log's settings, when `base.audit` is in the `use` list. */
640
643
  interface AuditConfig {
641
644
  /** The database the log lives in; only writes to it are recorded. */
642
645
  readonly database: string;
@@ -690,7 +693,7 @@ interface ModelSpec {
690
693
  /** The built-in table set it comes from (`tenancy`); undefined for the app's own models. */
691
694
  readonly builtin?: string;
692
695
  /**
693
- * Whether the audit log records its writes (planning/audit.md): its own `.audit(…)`, else the
696
+ * Whether the audit log records its writes: its own `.audit(…)`, else the
694
697
  * module's `byDefault`; `"never"` for built-ins that are never recorded (Session, the log itself).
695
698
  */
696
699
  readonly audit?: boolean | "never";
@@ -774,7 +777,7 @@ declare class Model<Name extends string = string, F extends Fields = Fields, Uni
774
777
  table(name: string): Model<Name, F, Uniques>;
775
778
  /**
776
779
  * A rule over the row, written as a condition. It runs in JS on every write; when it reads
777
- * more than one field it is also a database guard (planning/migrations.md).
780
+ * more than one field it is also a database guard.
778
781
  */
779
782
  check(name: string, build: (row: OwnColumns<ImplicitFields & F>) => Condition): Model<Name, F, Uniques>;
780
783
  /** The spec with these computed fields added (`computed`, `reading`). */
@@ -880,7 +883,7 @@ interface IndexSnapshot {
880
883
  readonly unique: boolean;
881
884
  }
882
885
  /**
883
- * A rule the database enforces itself (planning/migrations.md): a `.check()` over more than one
886
+ * A rule the database enforces itself: a `.check()` over more than one
884
887
  * column, or a number's limit (`min` / `max`), which an SQL expression could otherwise break.
885
888
  * For a limit, `name` is the field.
886
889
  */
@@ -1060,7 +1063,7 @@ interface TotalsQuery {
1060
1063
  /** What a write returns: the stored rows, or only their ids (when the caller just counts them). */
1061
1064
  type Returning = "rows" | "ids";
1062
1065
  /**
1063
- * One audited statement's entries (planning/audit.md): each entry's id is `stamp.<row id>`, so a
1066
+ * One audited statement's entries: each entry's id is `stamp.<row id>`, so a
1064
1067
  * later statement in the batch finds this statement's entries by the stamp. The rest is who and
1065
1068
  * where, the same on every entry.
1066
1069
  */
@@ -1119,7 +1122,7 @@ interface Dialect {
1119
1122
  */
1120
1123
  reorder(model: ModelSpec, ids: readonly string[], context: SqlContext): Sql;
1121
1124
  /**
1122
- * The audit log's statements, placed around a write in its batch (planning/audit.md). `log` is
1125
+ * The audit log's statements, placed around a write in its batch. `log` is
1123
1126
  * the `AuditEntry` model; `model` the one written.
1124
1127
  */
1125
1128
  readonly audit: {
@@ -1550,7 +1553,7 @@ interface Makers {
1550
1553
  }
1551
1554
  /**
1552
1555
  * What a seed's function is given: `make` and `many` on its database and, with tenancy, the tenant
1553
- * it runs in and `inTenant` to fill one (planning/tenancy.md → Creating tenants).
1556
+ * it runs in and `inTenant` to fill one.
1554
1557
  */
1555
1558
  interface SeedContext extends Makers {
1556
1559
  /** The tenant a seed runs in (its `Tenant` row), when `inTenant` ran it; undefined otherwise. */
@@ -1,3 +1,3 @@
1
- import { a as ValidationError, i as Result, n as IssueCode, r as Path, t as Issue } from "../issues-Bxq4JExX.js";
2
- import { $ as FakeContext, A as validate, B as money, C as ObjectShape, D as object, E as list, F as datetime, G as FileType, H as sequence, I as fakeUlid, J as file, K as ImageType, L as flag, M as IdInput, N as choice, O as serialize, P as date, Q as Fake, R as id, S as LIST_MAX, T as Shape, U as phone, V as quantity, W as phoneKind, X as image, Y as files, Z as images, _ as define, a as newToken, at as Measure, b as CarriesKind, c as slug, ct as kind, d as text, dt as StandardResult, et as AnyKind, f as textKind, ft as StandardSchemaV1, g as KindDefinition, h as Definable, i as markdown, it as KindTypes, j as CalendarDate, k as toKind, l as slugify, lt as StandardIssue, m as url, n as host, nt as Kind, o as personName, ot as Sensitivity, p as token, q as StoredFile, r as line, rt as KindSpec, s as secret, st as TypeText, t as email, tt as FileSpec, u as subdomain, ut as StandardProps, v as json, w as Output, x as Input, y as urls, z as int } from "../index-BVCrbMav.js";
1
+ import { a as ValidationError, i as Result, n as IssueCode, r as Path, t as Issue } from "../issues-CKto95IW.js";
2
+ import { $ as FakeContext, A as validate, B as money, C as ObjectShape, D as object, E as list, F as datetime, G as FileType, H as sequence, I as fakeUlid, J as file, K as ImageType, L as flag, M as IdInput, N as choice, O as serialize, P as date, Q as Fake, R as id, S as LIST_MAX, T as Shape, U as phone, V as quantity, W as phoneKind, X as image, Y as files, Z as images, _ as define, a as newToken, at as Measure, b as CarriesKind, c as slug, ct as kind, d as text, dt as StandardResult, et as AnyKind, f as textKind, ft as StandardSchemaV1, g as KindDefinition, h as Definable, i as markdown, it as KindTypes, j as CalendarDate, k as toKind, l as slugify, lt as StandardIssue, m as url, n as host, nt as Kind, o as personName, ot as Sensitivity, p as token, q as StoredFile, r as line, rt as KindSpec, s as secret, st as TypeText, t as email, tt as FileSpec, u as subdomain, ut as StandardProps, v as json, w as Output, x as Input, y as urls, z as int } from "../index-C-WmIKxT.js";
3
3
  export { type AnyKind, type CalendarDate, type CarriesKind, type Definable, Fake, type FakeContext, type FileSpec, type FileType, type IdInput, type ImageType, type Input, type Issue, type IssueCode, Kind, type KindDefinition, type KindSpec, type KindTypes, LIST_MAX, type Measure, type ObjectShape, type Output, type Path, type Result, type Sensitivity, type Shape, type StandardIssue, type StandardProps, type StandardResult, type StandardSchemaV1, type StoredFile, type TypeText, ValidationError, choice, date, datetime, define, email, fakeUlid, file, files, flag, host, id, image, images, int, json, kind, line, list, markdown, money, newToken, object, personName, phone, phoneKind, quantity, secret, sequence, serialize, slug, slugify, subdomain, text, textKind, toKind, token, url, urls, validate };
@@ -1 +1 @@
1
- export { G as Fake, H as Kind, I as LIST_MAX, W as ValidationError, E as choice, D as date, O as datetime, l as define, f as email, k as fakeUlid, i as file, o as files, A as flag, p as host, j as id, s as image, c as images, M as int, u as json, U as kind, m as line, L as list, h as markdown, N as money, g as newToken, R as object, _ as personName, t as phone, n as phoneKind, P as quantity, v as secret, F as sequence, z as serialize, y as slug, b as slugify, x as subdomain, S as text, C as textKind, B as toKind, w as token, T as url, d as urls, V as validate } from "../validation-l5JG-PFn.js";
1
+ export { G as Fake, H as Kind, I as LIST_MAX, W as ValidationError, E as choice, D as date, O as datetime, l as define, f as email, k as fakeUlid, i as file, o as files, A as flag, p as host, j as id, s as image, c as images, M as int, u as json, U as kind, m as line, L as list, h as markdown, N as money, g as newToken, R as object, _ as personName, t as phone, n as phoneKind, P as quantity, v as secret, F as sequence, z as serialize, y as slug, b as slugify, x as subdomain, S as text, C as textKind, B as toKind, w as token, T as url, d as urls, V as validate } from "../validation-FyOpSZqE.js";
@@ -904,9 +904,8 @@ const RESERVED_SUBDOMAINS = /* @__PURE__ */ new Set([
904
904
  "system"
905
905
  ]);
906
906
  /**
907
- * One DNS label for a tenant's host on the platform (`demo` in `demo.thiraiangadi.shop`,
908
- * planning/tenancy.md → End of v1): lowercase letters, digits and inner dashes, 3–63 characters,
909
- * none of the reserved names.
907
+ * One DNS label for a tenant's host on the platform (`demo` in `demo.thiraiangadi.shop`):
908
+ * lowercase letters, digits and inner dashes, 3–63 characters, none of the reserved names.
910
909
  */
911
910
  const subdomain = () => textKind("subdomain", {
912
911
  min: 3,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isaijs/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A batteries-included, type-safe TypeScript backend framework. Cloudflare first, no lock-in.",
5
5
  "type": "module",
6
6
  "license": "MIT",