@isaijs/core 0.1.0 → 0.1.3

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-C5GTYIL5.js} +23 -9
  18. package/dist/cli/index.js +18 -15
  19. package/dist/client/index.d.ts +46 -5
  20. package/dist/client/index.js +156 -8
  21. package/dist/{create-auth-Byf1-VJw.js → create-auth-Ci0PaBoX.js} +5 -5
  22. package/dist/{data-layer-BMviRJaW.d.ts → data-layer-D1BFo3wO.d.ts} +29 -12
  23. package/dist/{data-layer-BNtz40PV.js → data-layer-_TZtuy-F.js} +19 -11
  24. package/dist/db/index.d.ts +3 -3
  25. package/dist/db/index.js +5 -5
  26. package/dist/{index-BVCrbMav.d.ts → index-CYcklQ1l.d.ts} +21 -7
  27. package/dist/{index-BKwTOLop.d.ts → index-Ce5p2-EB.d.ts} +2 -2
  28. package/dist/{index-CBWFoL3s.d.ts → index-DlgVg3D8.d.ts} +38 -26
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +152 -56
  31. package/dist/{issues-Bxq4JExX.d.ts → issues-CKto95IW.d.ts} +3 -1
  32. package/dist/{make-DKzPd2b_.js → make-DKBn79cH.js} +17 -2
  33. package/dist/{config-D6PexpSC.js → makers-DeSNM0Nd.js} +13 -39
  34. package/dist/{migrator-C65RM_K-.js → migrator-BU3_PHk8.js} +102 -10
  35. package/dist/{policies-yVzFCjxU.js → policies-CKIdBFKt.js} +3 -3
  36. package/dist/ports/index.d.ts +1 -1
  37. package/dist/{resource-9fjxJgmz.js → resource-CsYksjKc.js} +42 -15
  38. package/dist/{tables-C3Uza4qK.js → tables-IrLjsomf.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-S6zF5IXV.d.ts} +15 -12
  42. package/dist/validation/index.d.ts +3 -3
  43. package/dist/validation/index.js +1 -1
  44. package/dist/{validation-l5JG-PFn.js → validation-Cppmd0RV.js} +23 -7
  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 { G as ValidationError, M as id } from "./validation-Cppmd0RV.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-C5GTYIL5.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. */
@@ -261,6 +278,39 @@ function evaluate(node, row, now, zone = utc) {
261
278
  }
262
279
  }
263
280
  //#endregion
281
+ //#region src/tenancy/config.ts
282
+ /**
283
+ * The two database names that mean something, and only with tenancy: each tenant's own database,
284
+ * and the platform's. Every other name is just a name.
285
+ */
286
+ const TENANT = "tenant";
287
+ const PLATFORM = "platform";
288
+ /** A host without a scheme, path or trailing dot; a port is allowed (local development). */
289
+ const HOST = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*(?::\d{1,5})?$/;
290
+ /**
291
+ * Checks the tenancy key against the databases, when the app starts. With tenancy the databases
292
+ * are `tenant` (each tenant's) and `platform`; without it, the app has one database, named freely.
293
+ */
294
+ function checkTenancy(tenancy, databases) {
295
+ const names = Object.keys(databases);
296
+ if (!tenancy) {
297
+ const reserved = names.find((name) => name === "tenant" || name === "platform");
298
+ if (reserved) throw new TypeError(`isai: databases.${reserved} is ${reserved === "tenant" ? "each tenant's database" : "the platform's database"}, which only an app with a \`tenancy\` key has; name the app's database something else (main)`);
299
+ if (names.length !== 1) throw new TypeError(`isai: an app without tenancy has one database (named as you like); it has ${names.length}${names.length > 1 ? `: ${names.join(", ")}. Several aren't supported yet` : ""}`);
300
+ if (Object.values(databases)[0]?.kind !== "database") throw new TypeError("isai: every tenant's own database (durableObject(env.TENANT)) needs a `tenancy` key");
301
+ return;
302
+ }
303
+ if (tenancy.mode !== "isolated") throw new TypeError(`isai: tenancy.mode "${String(tenancy.mode)}" isn't built yet; use "isolated"`);
304
+ if (tenancy.by !== "host") throw new TypeError(`isai: tenancy.by "${String(tenancy.by)}" isn't built yet; use "host"`);
305
+ if (typeof tenancy.platform !== "string" || !HOST.test(tenancy.platform.toLowerCase())) throw new TypeError(`isai: tenancy.platform must be a host, like app.example.com (no https:// or path); got "${String(tenancy.platform)}"`);
306
+ const model = tenancy.tenant;
307
+ if (model?.spec?.name !== "Tenant" || model.spec.builtin !== "tenancy") throw new TypeError("isai: tenancy.tenant is the Tenant model: builtins.Tenant, or its extension (import { Tenant } from \"#db/platform/tenants\")");
308
+ if (databases["tenant"]?.kind !== "tenantDatabases") throw new TypeError("isai: with isolated tenancy, databases.tenant is each tenant's database: durableObject(env.TENANT)");
309
+ if (databases["platform"]?.kind !== "database") throw new TypeError("isai: an app with tenancy needs databases.platform, where the Tenant and Domain tables live");
310
+ const other = names.find((name) => name !== "tenant" && name !== "platform");
311
+ if (other) throw new TypeError(`isai: with tenancy, the databases are tenant and platform; databases.${other} isn't supported yet`);
312
+ }
313
+ //#endregion
264
314
  //#region src/internal/secret-box.ts
265
315
  /**
266
316
  * Encrypted values for `secret` fields: AES-256-GCM through Web Crypto, on every runtime.
@@ -947,6 +997,20 @@ const audit = {
947
997
  * Migration operations and rules as SQLite DDL. Tables hold only what can't live elsewhere:
948
998
  * columns (nullable; "required" is a rule in code), the primary key and foreign keys. Uniques
949
999
  * are separate indexes and guards are triggers, so changing either never rebuilds a table.
1000
+ *
1001
+ * Where each rule runs (one-field rules in code, everything else in the database):
1002
+ *
1003
+ * required, a kind's limits and format, choices JS only, in the ORM's write path
1004
+ * unique a unique index (concurrent writes both see "free")
1005
+ * ref a foreign key, and an index
1006
+ * a number's limits (≥ 0, a narrowed min / max) a guard, on updates only: `stock - 2` is SQL's
1007
+ * rules over counts and `.from()` copies a guard: the database changes them
1008
+ * a `.check()` over several columns a guard: a partial update needs the stored values
1009
+ *
1010
+ * A guard is a trigger that raises (`RAISE(ABORT, 'isai:check:<model>.<rule>')`, parsed into the
1011
+ * same `CheckFailed` JS gives), because a D1 batch rolls back only on an error. One insert trigger
1012
+ * per table; update triggers are scoped to the columns their rules read (`BEFORE UPDATE OF …`).
1013
+ * Writers outside the ORM (raw SQL, the D1 console) skip the one-field rules; guards still apply.
950
1014
  */
951
1015
  const TYPES = {
952
1016
  text: "TEXT",
@@ -1293,7 +1357,15 @@ function translate(error, dialect, models) {
1293
1357
  case "unique": {
1294
1358
  const model = models.find((m) => m.table === violation.table);
1295
1359
  const fields = model ? violation.columns.map((c) => fieldAt(model, c)) : violation.columns;
1296
- return new UniqueFailed(model?.name ?? violation.table, fields, { cause: error });
1360
+ const from = {};
1361
+ for (const field of fields) {
1362
+ const source = model && fieldOf(model, field)?.generate?.from;
1363
+ if (source) from[field] = source;
1364
+ }
1365
+ return new UniqueFailed(model?.name ?? violation.table, fields, {
1366
+ cause: error,
1367
+ from
1368
+ });
1297
1369
  }
1298
1370
  case "reference": return new ReferenceFailed("A referenced row doesn't exist, or other rows still refer to this one", { cause: error });
1299
1371
  default: return error;
@@ -1925,7 +1997,7 @@ var FindOne = class FindOne extends Maybe {
1925
1997
  * The write pipeline's first half, in JS, before any SQL: fill implicit and generated fields,
1926
1998
  * read every value through its kind (defaults, normalisation, one-field rules), then run the
1927
1999
  * checks the write can decide. Rules over values the database holds are guards, enforced in
1928
- * SQL (planning/migrations.md).
2000
+ * SQL.
1929
2001
  */
1930
2002
  const MANAGED$1 = new Set(IMPLICIT_FIELDS);
1931
2003
  /** The one row of a `traits.singleton()` model has this id, so a second create fails as a duplicate. */
@@ -2198,9 +2270,29 @@ var DeleteMany = class DeleteMany extends Conditions {
2198
2270
  //#endregion
2199
2271
  //#region src/db/orm/database.ts
2200
2272
  /**
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.
2273
+ * The database handle (`app.db` in services). Async everywhere, one API on every driver:
2274
+ * `…One` by a key (one of the model's declared uniques: `{ id }`, `{ email }`, `{ event, user }`),
2275
+ * `…Many` by a condition (chained `.where()`, so policies, filters and client queries combine) or a
2276
+ * list. Rows are plain data: no identity map, no change tracking.
2277
+ *
2278
+ * await db.findOne(Event, { id }).mustExist() // Row, or NotFound (a 404)
2279
+ * await db.findMany(Event).where((e) => e.startsAt.after(now)).order("startsAt").limit(20)
2280
+ * await db.updateOne(Variant, { id }, { stock: (v) => v.stock.minus(2) }) // an SQL expression
2281
+ * await db.upsertOne(Registration, { event, user }, { create: { … }, update: { … } })
2282
+ * await app.db.atomic((tx) => { const order = tx.createOne(Order, { … }); tx.createOne(Line, { order, … }) })
2283
+ *
2284
+ * - Absence isn't an error: `findOne`, `updateOne`, `deleteOne` give `undefined` unless
2285
+ * `.mustExist()`. A refused write always throws its typed error.
2286
+ * - Conflicts belong to `upsert…`: `createOne` / `createMany` always fail on a duplicate.
2287
+ * - `updateMany` / `deleteMany` refuse to run without a `.where()` (every row is
2288
+ * `.where(() => all())`), and take no order or limit (SQLite has no `UPDATE … LIMIT`).
2289
+ * - No `.select()`: a row is always the model's full row. Shaping output is a resource's job,
2290
+ * hiding sensitive values the output layer's.
2291
+ * - `now` (and `now.plus("8d")`) is a value in writes, resolved once per write or `atomic`, from the
2292
+ * framework's clock (which tests set), so every row in it gets the same instant.
2293
+ * - `atomic` is write-only on every driver: D1 can't run an interactive transaction, and a rule
2294
+ * that depended on the driver would make app code depend on the target. Read before it; enforce
2295
+ * rules with constraints (`quantity` ≥ 0), counters (`f.count`) or conditional writes.
2204
2296
  */
2205
2297
  function isRawValue(value) {
2206
2298
  return value === null || typeof value === "string" || typeof value === "number" || value instanceof Uint8Array;
@@ -2484,7 +2576,7 @@ function forUpkeep(db) {
2484
2576
  return db[UPKEEP]();
2485
2577
  }
2486
2578
  /**
2487
- * An action on a row, in the audit log (`markPaid`, `signIn`; planning/audit.md): who took it, from
2579
+ * An action on a row, in the audit log (`markPaid`, `signIn`): who took it, from
2488
2580
  * the request, unless `user` says (a sign-in, before the request has a user). Nothing when the
2489
2581
  * model isn't recorded. Internal, like `forUpkeep`.
2490
2582
  */
@@ -3469,4 +3561,4 @@ async function migrateFresh(driver, models, options = {}) {
3469
3561
  await sync(driver, manifest, options);
3470
3562
  }
3471
3563
  //#endregion
3472
- export { Query as A, modelOfRow as B, database as C, planInsert as D, recordAction as E, systemClock as F, UniqueFailed as G, DatabaseError as H, newSecretKey as I, NeedsDatabase as L, dialectFor as M, sqlite as N, planUpdate as O, fixedClock as P, evaluate as R, clockOf as S, forUpkeep as T, NotFound as U, CheckFailed as V, ReferenceFailed as W, TODO as _, currentRelease as a, Database as b, rollback as c, applyRules as d, dayOf as f, UnsafeMigration as g, snapshotOf as h, acquireLock as i, conditionOf as j, keyCondition as k, setReleaseStatus as l, emptySnapshot as m, migrateFresh as n, holdOf as o, generateMigration as p, sync as r, releaseLock as s, migrate as t, startRelease as u, dataStep as v, decodeStored as w, Transaction as x, MigrationError as y, utc as z };
3564
+ export { Query as A, NeedsDatabase as B, database as C, planInsert as D, recordAction as E, systemClock as F, DatabaseError as G, utc as H, newSecretKey as I, UniqueFailed as J, NotFound as K, PLATFORM as L, dialectFor as M, sqlite as N, planUpdate as O, fixedClock as P, TENANT as R, clockOf as S, forUpkeep as T, modelOfRow as U, evaluate as V, CheckFailed as W, TODO as _, currentRelease as a, Database as b, rollback as c, applyRules as d, dayOf as f, UnsafeMigration as g, snapshotOf as h, acquireLock as i, conditionOf as j, keyCondition as k, setReleaseStatus as l, emptySnapshot as m, migrateFresh as n, holdOf as o, generateMigration as p, ReferenceFailed as q, sync as r, releaseLock as s, migrate as t, startRelease as u, dataStep as v, decodeStored as w, Transaction as x, MigrationError as y, checkTenancy as z };
@@ -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-C5GTYIL5.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 { G as ValidationError, H as validate, V as toKind, z as object } from "./validation-Cppmd0RV.js";
2
+ import { J as UniqueFailed, K as NotFound, W as CheckFailed, q as ReferenceFailed } from "./migrator-BU3_PHk8.js";
3
+ import { a as columnName, b as asCondition, j as IMPLICIT_FIELDS, l as computedFieldOf, m as Expr, p as Embed } from "./base64url-C5GTYIL5.js";
4
+ import { r as actionsOf } from "./policies-CKIdBFKt.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-C5GTYIL5.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-S6zF5IXV.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 { H as utc, L as PLATFORM, M as dialectFor, P as fixedClock, R as TENANT, 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 } from "../migrator-BU3_PHk8.js";
3
+ import { n as toBase64url, r as Model } from "../base64url-C5GTYIL5.js";
4
+ import { t as createDataLayer } from "../data-layer-_TZtuy-F.js";
5
+ import { d as forgetGoogleKeys, r as authOf, s as GOOGLE, t as makers, y as seedAs } from "../makers-DeSNM0Nd.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, M as IdInput, T as Shape, Y as StoredFile, ct as Sensitivity, ft as StandardProps, g as KindDefinition, h as Definable, nt as AnyKind, ot as KindTypes, q as ImageType, st as Measure, w as Output, x as Input$2 } from "./index-CYcklQ1l.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";
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
+ import { a as ValidationError, i as Result, n as IssueCode, r as Path, t as Issue } from "../issues-CKto95IW.js";
2
+ import { $ as images, 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 SIZES_MAX, K as ImageSize, L as flag, M as IdInput, N as choice, O as serialize, P as date, Q as image, R as id, S as LIST_MAX, T as Shape, U as phone, V as quantity, W as phoneKind, X as file, Y as StoredFile, Z as files, _ as define, a as newToken, at as KindSpec, b as CarriesKind, c as slug, ct as Sensitivity, d as text, dt as StandardIssue, et as Fake, f as textKind, ft as StandardProps, g as KindDefinition, h as Definable, i as markdown, it as Kind, j as CalendarDate, k as toKind, l as slugify, lt as TypeText, m as url, mt as StandardSchemaV1, n as host, nt as AnyKind, o as personName, ot as KindTypes, p as token, pt as StandardResult, q as ImageType, r as line, rt as FileSpec, s as secret, st as Measure, t as email, tt as FakeContext, u as subdomain, ut as kind, v as json, w as Output, x as Input, y as urls, z as int } from "../index-CYcklQ1l.js";
3
+ export { type AnyKind, type CalendarDate, type CarriesKind, type Definable, Fake, type FakeContext, type FileSpec, type FileType, type IdInput, type ImageSize, 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, SIZES_MAX, 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 { K as Fake, U as Kind, L as LIST_MAX, i as SIZES_MAX, G as ValidationError, D as choice, O as date, k as datetime, u as define, p as email, A as fakeUlid, a as file, s as files, j as flag, m as host, M as id, c as image, l as images, N as int, d as json, W as kind, h as line, R as list, g as markdown, P as money, _ as newToken, z as object, v as personName, t as phone, n as phoneKind, F as quantity, y as secret, I as sequence, B as serialize, b as slug, x as slugify, S as subdomain, C as text, w as textKind, V as toKind, T as token, E as url, f as urls, H as validate } from "../validation-Cppmd0RV.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,
@@ -1079,6 +1078,8 @@ function define(definition) {
1079
1078
  }
1080
1079
  //#endregion
1081
1080
  //#region src/validation/kinds/files.ts
1081
+ /** At most this many smaller copies of one image. */
1082
+ const SIZES_MAX = 4;
1082
1083
  /** File types by their usual name, with the media types each covers. */
1083
1084
  const FILE_TYPES = {
1084
1085
  pdf: ["application/pdf"],
@@ -1140,7 +1141,21 @@ const reference = object({
1140
1141
  fake: () => "text/plain"
1141
1142
  }),
1142
1143
  width: int().min(1).optional(),
1143
- height: int().min(1).optional()
1144
+ height: int().min(1).optional(),
1145
+ sizes: list(object({
1146
+ key: textKind("fileKey", {
1147
+ max: 1024,
1148
+ fake: (fake) => `fake/${fakeUlid(fake)}`
1149
+ }),
1150
+ size: quantity(),
1151
+ type: textKind("mediaType", {
1152
+ max: 255,
1153
+ normalize: (value) => value.toLowerCase(),
1154
+ fake: () => "image/webp"
1155
+ }),
1156
+ width: int().min(1),
1157
+ height: int().min(1)
1158
+ })).max(4).optional()
1144
1159
  });
1145
1160
  function fileKind(name, spec) {
1146
1161
  const { types, maxBytes } = spec;
@@ -1151,11 +1166,12 @@ function fileKind(name, spec) {
1151
1166
  read: (value, path) => {
1152
1167
  const read = reference.parse(value, path);
1153
1168
  if (!read.ok) return read;
1154
- const { width, height, ...rest } = read.value;
1169
+ const { width, height, sizes, ...rest } = read.value;
1155
1170
  const file = {
1156
1171
  ...rest,
1157
1172
  ...width ? { width } : {},
1158
- ...height ? { height } : {}
1173
+ ...height ? { height } : {},
1174
+ ...sizes?.length ? { sizes } : {}
1159
1175
  };
1160
1176
  const issues = [];
1161
1177
  if (!types.has(file.type)) issues.push({
@@ -1297,4 +1313,4 @@ function phone(options = {}) {
1297
1313
  return phoneKind(() => region);
1298
1314
  }
1299
1315
  //#endregion
1300
- export { flag as A, toKind as B, textKind as C, date as D, choice as E, sequence as F, Fake as G, Kind as H, LIST_MAX as I, fakeValue as K, list as L, int as M, money as N, datetime as O, quantity as P, object as R, text as S, url as T, kind as U, validate as V, ValidationError as W, personName as _, fileTypeNames as a, slugify as b, images as c, urls as d, email as f, newToken as g, markdown as h, file as i, id as j, fakeUlid as k, define as l, line as m, phoneKind as n, files as o, host as p, isPhoneRegion as r, image as s, phone as t, json as u, secret as v, token as w, subdomain as x, slug as y, serialize as z };
1316
+ export { fakeUlid as A, serialize as B, text as C, choice as D, url as E, quantity as F, ValidationError as G, validate as H, sequence as I, Fake as K, LIST_MAX as L, id as M, int as N, date as O, money as P, list as R, subdomain as S, token as T, Kind as U, toKind as V, kind as W, newToken as _, file as a, slug as b, image as c, json as d, urls as f, markdown as g, line as h, SIZES_MAX as i, flag as j, datetime as k, images as l, host as m, phoneKind as n, fileTypeNames as o, email as p, fakeValue as q, isPhoneRegion as r, files as s, phone as t, define as u, personName as v, textKind as w, slugify as x, secret as y, object as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isaijs/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "A batteries-included, type-safe TypeScript backend framework. Cloudflare first, no lock-in.",
5
5
  "type": "module",
6
6
  "license": "MIT",