@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,7 +1,7 @@
1
- import { a as DatabaseDriver, b as TenantDatabases, d as RateLimiter, g as Storage, l as PdfRenderer, n as Body, p as Runtime, r as Cache, s as MailDriver, u as Queue } from "./index-BKwTOLop.js";
2
- import { C as ObjectShape, q as StoredFile, w as Output } from "./index-BVCrbMav.js";
3
- import { $ as Expr, C as WithIncludes, D as PageOptions, E as Page, J as Models, Q as Embed, S as Key, T as NumberedPageOptions, V as AnyModel, X as Condition, Z as Conditional, b as RowOf, ct as ColumnNode, gt as FieldSpec, i as Row, lt as ConditionNode, n as Patch, p as Database, t as Insert, ut as Operand, v as OrderKey, w as NumberedPage, x as IncludePath } from "./types-BFc_NEx8.js";
4
- import { a as PROVIDERS, i as HELD, k as Manifest } from "./data-layer-BMviRJaW.js";
1
+ import { a as DatabaseDriver, b as TenantDatabases, d as RateLimiter, g as Storage, l as PdfRenderer, n as Body, p as Runtime, r as Cache, s as MailDriver, u as Queue } from "./index-Ce5p2-EB.js";
2
+ import { C as ObjectShape, q as StoredFile, w as Output } from "./index-C-WmIKxT.js";
3
+ import { $ as Expr, C as WithIncludes, D as PageOptions, E as Page, J as Models, Q as Embed, S as Key, T as NumberedPageOptions, V as AnyModel, X as Condition, Z as Conditional, b as RowOf, ct as ColumnNode, gt as FieldSpec, i as Row, lt as ConditionNode, n as Patch, p as Database, t as Insert, ut as Operand, v as OrderKey, w as NumberedPage, x as IncludePath } from "./types-DpOcIuDl.js";
4
+ import { a as PROVIDERS, i as HELD, k as Manifest } from "./data-layer-ubPfZvFV.js";
5
5
  //#region src/auth/policies.d.ts
6
6
  /** The actions every model's policy has. */
7
7
  declare const STANDARD_ACTIONS: readonly ["read", "create", "update", "delete"];
@@ -58,7 +58,7 @@ interface DefinePolicy {
58
58
  //#region src/api/fail.d.ts
59
59
  /**
60
60
  * `fail(code, message)`: the app's own business rules, for what can't be a `.check()`, a flow or a
61
- * unique. Always a 409; app code never picks a status (planning/requests.md → Errors).
61
+ * unique. Always a 409; app code never picks a status.
62
62
  */
63
63
  declare class Rejected extends Error {
64
64
  /** Stable and machine-readable (`alreadyCheckedIn`). */
@@ -148,7 +148,7 @@ type User = Models extends {
148
148
  readonly id: string;
149
149
  };
150
150
  /**
151
- * Who signs in on the platform's host (planning/tenancy.md → End of v1): the `PlatformUser` row,
151
+ * Who signs in on the platform's host: the `PlatformUser` row,
152
152
  * once it's in the `Models` registry.
153
153
  */
154
154
  type PlatformUser = Models extends {
@@ -165,8 +165,8 @@ interface RouteOptions<S extends ObjectShape = ObjectShape, A extends boolean =
165
165
  /** Callable without a session; `user` is then `User | undefined`. Signed in by default. */
166
166
  readonly anonymous?: A;
167
167
  /**
168
- * Served on the platform's host, in the front door, with no tenant (isolated tenancy,
169
- * planning/tenancy.md → End of v1): `db` is the platform's, `user` a platform user.
168
+ * Served on the platform's host, in the front door, with no tenant (isolated tenancy): `db` is
169
+ * the platform's, `user` a platform user.
170
170
  */
171
171
  readonly platform?: Pl;
172
172
  }
@@ -190,13 +190,13 @@ interface RouteContext<P extends string, S extends ObjectShape, A extends boolea
190
190
  readonly input: Output<S>;
191
191
  /** The raw request, for headers. */
192
192
  readonly request: Request;
193
- /** The tenant this request is for (planning/tenancy.md); undefined without tenancy, and on the platform's host. */
193
+ /** The tenant this request is for; undefined without tenancy, and on the platform's host. */
194
194
  readonly tenant: Pl extends true ? undefined : TenantRow;
195
195
  }
196
196
  type RouteHandler<P extends string, S extends ObjectShape, A extends boolean, R = unknown, Pl extends boolean = false> = (context: RouteContext<P, S, A, Pl>) => R;
197
197
  /**
198
198
  * One declared route. The pipeline finds these among an app module's exports. `O` is what its
199
- * handler returns, awaited: the typed client reads it (planning/client.md).
199
+ * handler returns, awaited: the typed client reads it.
200
200
  */
201
201
  declare class Route<O = unknown> {
202
202
  readonly method: Method;
@@ -284,7 +284,7 @@ interface AppConfig {
284
284
  * `durableObject(env.TENANT)`) and `platform`. Without: one database, named freely (`main`).
285
285
  */
286
286
  readonly databases: Readonly<Record<string, DatabaseDriver | TenantDatabases>>;
287
- /** Many tenants (planning/tenancy.md); left out, the app has one. Needs `databases.platform`. */
287
+ /** Many tenants; left out, the app has one. Needs `databases.platform`. */
288
288
  readonly tenancy?: TenancyConfig;
289
289
  /** Who signs in and how: `auth` from `auth.ts` (createAuth). */
290
290
  readonly auth?: Auth<any>;
@@ -330,11 +330,19 @@ type AnyColumn = {
330
330
  interface ResourceOptions {
331
331
  /** ANDed with the policy for every action (a view's `WHERE`); `byRole` is the action's permission. */
332
332
  readonly filter?: Conditional;
333
- /** The output: exactly these columns, or `"all"` (hidden kinds never leave either way). */
333
+ /**
334
+ * The output: exactly these columns, or `"all"` (hidden kinds never leave either way). Refs and
335
+ * relations are embedded with `.pick(...)`, whose paths may follow relations
336
+ * (`product.variants.pick("price", "tiers.price")`); each level keeps only the rows its own
337
+ * model's `read` rule allows.
338
+ */
334
339
  readonly fields: readonly (AnyColumn | Embed)[] | "all";
335
340
  /** Named values computed in SQL (counts, sums, conditions), beside the fields. */
336
341
  readonly computed?: Readonly<Record<string, AnyColumn | Condition>>;
337
- /** The only fields create and update accept, or `"all"`. */
342
+ /**
343
+ * The only fields create and update accept (a mass-assignment guard), or `"all"`. With `create`,
344
+ * it covers every required field without a default.
345
+ */
338
346
  readonly writable?: readonly AnyColumn[] | "all";
339
347
  /** Fields (or ref paths: `product.category.slug`) a client may filter by. */
340
348
  readonly filterable?: readonly AnyColumn[];
@@ -342,7 +350,7 @@ interface ResourceOptions {
342
350
  readonly sortable?: readonly (AnyColumn | string)[];
343
351
  /** Named conditions a client switches on: `?onSale`. */
344
352
  readonly filters?: Readonly<Record<string, Conditional>>;
345
- /** Text fields `?q=` searches. */
353
+ /** Fields (or ref paths: `product.category.name`) `?search=` matches, any of them, ignoring case. */
346
354
  readonly search?: readonly AnyColumn[];
347
355
  /** The column `/:key` looks up (unique). The id by default; a singleton has none. */
348
356
  readonly key?: AnyColumn;
@@ -358,7 +366,7 @@ interface SearchField {
358
366
  }
359
367
  /**
360
368
  * A declared resource, with what the engine reads from it. `C` is its custom actions, as declared:
361
- * the typed client reads what each returns (planning/client.md).
369
+ * the typed client reads what each returns.
362
370
  */
363
371
  declare class Resource<C = Readonly<Record<string, CustomAction>>> {
364
372
  readonly model: AnyModel;
@@ -431,13 +439,13 @@ interface App {
431
439
  readonly db: Database;
432
440
  /**
433
441
  * The platform's database (isolated tenancy): for the platform's routes, services and seeds,
434
- * refused inside a tenant, where `app.db` is the tenant's (planning/tenancy.md → End of v1).
442
+ * refused inside a tenant, where `app.db` is the tenant's.
435
443
  */
436
444
  readonly platform: {
437
445
  readonly db: Database;
438
446
  };
439
447
  /**
440
- * Files from code (planning/uploads.md): `put` stores one for a write (in the current tenant's
448
+ * Files from code: `put` stores one for a write (in the current tenant's
441
449
  * space), `get` reads it, `url` links to it, `delete` removes it.
442
450
  */
443
451
  readonly files: AppFiles;
@@ -456,7 +464,7 @@ interface AppOptions {
456
464
  /**
457
465
  * The tenant database's history: `() => import("#migrations/tenant")`, through a
458
466
  * `#migrations/<name>` alias to `./migrations/<name>/index.ts` in `package.json` imports. Needed with
459
- * isolated tenancy, where each tenant's database applies it on wake (planning/tenancy.md).
467
+ * isolated tenancy, where each tenant's database applies it on wake.
460
468
  */
461
469
  readonly migrations?: AppMigrations;
462
470
  /**
@@ -480,7 +488,7 @@ interface CreatedApp<Served, TenantHost = undefined> {
480
488
  */
481
489
  readonly Tenant: TenantHost;
482
490
  readonly route: RouteBuilder;
483
- /** `resource(Model, options)`: what the API exposes over a model (planning/resources.md). */
491
+ /** `resource(Model, options)`: what the API exposes over a model. */
484
492
  readonly resource: typeof resource;
485
493
  readonly fail: typeof fail;
486
494
  }
@@ -491,7 +499,7 @@ interface CreatedApp<Served, TenantHost = undefined> {
491
499
  declare function createApp<Env, Served, TenantHost = undefined>(runtime: Runtime<Env, Served, TenantHost>, configure: (env: Env) => AppConfig, options?: AppOptions): CreatedApp<Served, TenantHost>;
492
500
  //#endregion
493
501
  //#region src/app/core.d.ts
494
- /** The app modules (`apps/*\/routes.ts`, `resources.ts`), each a lazy import (planning/README.md → Instances). */
502
+ /** The app modules (`apps/*\/routes.ts`, `resources.ts`), each a lazy import. */
495
503
  type AppModules = () => readonly Promise<object>[];
496
504
  /**
497
505
  * The tenant database's history, as a lazy import of the manifest `isai makemigrations` writes
@@ -525,7 +533,7 @@ declare class NotImplementedError extends Error {
525
533
  interface InviteInput {
526
534
  readonly email: string;
527
535
  readonly role: string;
528
- /** `"platform"`: the tenant's people can't take the role away (planning/tenancy.md → End of v1). */
536
+ /** `"platform"`: the tenant's people can't take the role away. */
529
537
  readonly held?: (typeof HELD)[number];
530
538
  }
531
539
  //#endregion
@@ -556,8 +564,10 @@ interface AuthDefinition {
556
564
  /** Who may make an account by signing in: anyone (`"open"`, the default), or the invited. */
557
565
  readonly signUp?: "open" | "invite";
558
566
  /**
559
- * Signs what crosses the browser during sign-in (Google's `state`, the hand-off between hosts):
560
- * 32 random bytes, base64url, kept as a secret of the deployment (`env.AUTH_SECRET`).
567
+ * Signs what crosses the browser during sign-in (Google's `state`, the hand-off between hosts)
568
+ * and private files' links: 32 random bytes, base64url, kept as a secret of the deployment
569
+ * (`env.AUTH_SECRET`). Sessions don't depend on it, so rotating it only ends sign-ins in progress
570
+ * and links already given out.
561
571
  */
562
572
  readonly secret: string;
563
573
  }
@@ -580,7 +590,7 @@ declare class Auth<Env = unknown> {
580
590
  readonly invite: "auth.invite";
581
591
  /** See the people with accounts, give and take their roles, suspend them. */
582
592
  readonly manageUsers: "auth.manageUsers";
583
- /** Read the whole audit log (`GET /api/audit`), with the log on (planning/audit.md). */
593
+ /** Read the whole audit log (`GET /api/audit`), with the log on. */
584
594
  readonly readAudit: "audit.read";
585
595
  };
586
596
  /** The policies made with this instance's `policy()`. */
@@ -591,7 +601,7 @@ declare class Auth<Env = unknown> {
591
601
  /**
592
602
  * Gives `email` a role from `app.ts`, from code (a tenant's setup service, a seed): now, if it has
593
603
  * an account in the current database, else when it first signs in. Safe to repeat.
594
- * `held: "platform"` puts the assignment beyond the tenant's reach (planning/tenancy.md → End of v1).
604
+ * `held: "platform"` puts the assignment beyond the tenant's reach.
595
605
  */
596
606
  invite(input: InviteInput): Promise<void>;
597
607
  /** The core's: where `invite` writes. */
@@ -1,4 +1,4 @@
1
- import { i as Result, r as Path } from "./issues-Bxq4JExX.js";
1
+ import { i as Result, r as Path } from "./issues-CKto95IW.js";
2
2
  //#region src/validation/standard.d.ts
3
3
  /**
4
4
  * Standard Schema v1 (standardschema.dev), the interface Zod, Valibot and ArkType share. Every
@@ -39,7 +39,7 @@ interface Measure {
39
39
  }
40
40
  /**
41
41
  * How sensitive a value is: `hidden` never leaves the server; `personal` is redacted in logs,
42
- * exports and AI context. The output layer applies it (planning/fields.md → Sensitive fields).
42
+ * exports and AI context. The output layer applies it.
43
43
  */
44
44
  type Sensitivity = "hidden" | "personal";
45
45
  interface KindSpec {
@@ -76,7 +76,7 @@ interface KindSpec {
76
76
  */
77
77
  readonly fake: (fake: Fake, context: FakeContext) => unknown;
78
78
  }
79
- /** What a file kind accepts, and where its files go (planning/uploads.md). */
79
+ /** What a file kind accepts, and where its files go. */
80
80
  interface FileSpec {
81
81
  /** The storage its files go to: a key of `storage` in `app.ts`. */
82
82
  readonly storage: string;
@@ -346,9 +346,8 @@ declare const slug: () => Kind<string, string, false, false>;
346
346
  */
347
347
  declare const host: () => Kind<string, string, false, false>;
348
348
  /**
349
- * One DNS label for a tenant's host on the platform (`demo` in `demo.thiraiangadi.shop`,
350
- * planning/tenancy.md → End of v1): lowercase letters, digits and inner dashes, 3–63 characters,
351
- * none of the reserved names.
349
+ * One DNS label for a tenant's host on the platform (`demo` in `demo.thiraiangadi.shop`):
350
+ * lowercase letters, digits and inner dashes, 3–63 characters, none of the reserved names.
352
351
  */
353
352
  declare const subdomain: () => Kind<string, string, false, false>;
354
353
  /** A slug made from text: `"Summer Fest 2026!"` → `"summer-fest-2026"`. Empty when nothing survives. */
@@ -31,7 +31,7 @@ interface AppCore {
31
31
  readonly seeded: boolean;
32
32
  }>;
33
33
  }
34
- /** A tenant's entry points, where its database lives (planning/tenancy.md → Tenant databases). */
34
+ /** A tenant's entry points, where its database lives. */
35
35
  interface TenantCore {
36
36
  /** Brings the tenant's database to the deployed schema: on wake, before any request. */
37
37
  wake(): Promise<void>;
@@ -125,7 +125,7 @@ interface StoredObject {
125
125
  readonly contentType?: string;
126
126
  }
127
127
  /**
128
- * Where files live (planning/uploads.md). A storage with a `publicUrl` is public: its files are
128
+ * Where files live. A storage with a `publicUrl` is public: its files are
129
129
  * served from there, by the bucket. Without one it's private, and the app serves its files
130
130
  * through signed links.
131
131
  */
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { p as Runtime, t as AppCore } from "./index-BKwTOLop.js";
2
- import { C as Params, D as RouteHandler, E as RouteContext, M as fail, O as RouteOptions, S as Method, T as RouteBuilder, _ as CustomAction, b as AppConfig, d as NotImplementedError, f as AppModules, g as createApp, h as CreatedApp, j as Rejected, k as User, m as AppOptions, p as App, v as Resource, w as Route, x as Named, y as ResourceOptions } from "./index-CBWFoL3s.js";
1
+ import { p as Runtime, t as AppCore } from "./index-Ce5p2-EB.js";
2
+ import { C as Params, D as RouteHandler, E as RouteContext, M as fail, O as RouteOptions, S as Method, T as RouteBuilder, _ as CustomAction, b as AppConfig, d as NotImplementedError, f as AppModules, g as createApp, h as CreatedApp, j as Rejected, k as User, m as AppOptions, p as App, v as Resource, w as Route, x as Named, y as ResourceOptions } from "./index-B6ipokoP.js";
3
3
  export { type App, type AppConfig, type AppCore, type AppModules, type AppOptions, type CreatedApp, type CustomAction, type Method, type Named, NotImplementedError, type Params, Rejected, type Resource, type ResourceOptions, Route, type RouteBuilder, type RouteContext, type RouteHandler, type RouteOptions, type Runtime, type User, createApp, fail };
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
- import { B as toKind, R as object, V as validate, W as ValidationError, a as fileTypeNames, f as email, m as line } from "./validation-l5JG-PFn.js";
2
- import { B as modelOfRow, C as database, D as planInsert, E as recordAction, F as systemClock, G as UniqueFailed, L as NeedsDatabase, O as planUpdate, R as evaluate, S as clockOf, U as NotFound, j as conditionOf, k as keyCondition, r as sync, t as migrate, w as decodeStored } from "./migrator-C65RM_K-.js";
3
- import { C as tooLarge, E as fail, S as tenantSuspended, T as Rejected, _ as json, b as routeNotFound, c as fromBody, d as route, f as HttpError, g as fileTypeRefused, h as fileTooLarge, l as readInput, m as errorResponse, o as parseListQuery, p as crossSiteRequest, r as resource, s as BODY_LIMIT, t as Resource, u as Route, v as lengthRequired, w as tooManyRequests, x as signInRequired, y as methodNotAllowed } from "./resource-9fjxJgmz.js";
4
- import { F as sensitivity, G as ulid, K as Scope, V as asActor, a as columnName, b as asCondition$1, c as fieldOf, f as Condition, m as Expr, n as toBase64url, r as Model, t as fromBase64url, u as rowOf } from "./base64url-DwWPqoOT.js";
1
+ import { B as toKind, R as object, V as validate, W as ValidationError, a as fileTypeNames, f as email, m as line } from "./validation-FyOpSZqE.js";
2
+ import { B as modelOfRow, C as database, D as planInsert, E as recordAction, F as systemClock, G as UniqueFailed, L as NeedsDatabase, O as planUpdate, R as evaluate, S as clockOf, U as NotFound, j as conditionOf, k as keyCondition, r as sync, t as migrate, w as decodeStored } from "./migrator-6oWUefLA.js";
3
+ import { C as tooLarge, E as fail, S as tenantSuspended, T as Rejected, _ as json, b as routeNotFound, c as fromBody, d as route, f as HttpError, g as fileTypeRefused, h as fileTooLarge, l as readInput, m as errorResponse, o as parseListQuery, p as crossSiteRequest, r as resource, s as BODY_LIMIT, t as Resource, u as Route, v as lengthRequired, w as tooManyRequests, x as signInRequired, y as methodNotAllowed } from "./resource-C17NvNa3.js";
4
+ import { F as sensitivity, G as ulid, K as Scope, V as asActor, a as columnName, b as asCondition$1, c as fieldOf, f as Condition, m as Expr, n as toBase64url, r as Model, t as fromBase64url, u as rowOf } from "./base64url-K_wCmvxR.js";
5
5
  import { t as kebabCase } from "./case-BnpcvhdW.js";
6
- import { t as PLATFORM_PREFIX } from "./tables-C3Uza4qK.js";
6
+ import { t as PLATFORM_PREFIX } from "./tables-C4ipMFjg.js";
7
7
  import { t as Seed } from "./seed-BObmdKdP.js";
8
8
  import { n as notImplemented, t as NotImplementedError } from "./errors-mCNLlXg9.js";
9
- import { d as afterUpdate, f as bindFor, h as ownerFieldOf, m as isFalse, p as check, u as FALSE } from "./policies-yVzFCjxU.js";
10
- import { i as checkRoles, n as authPerms, r as checkAuth } from "./create-auth-Byf1-VJw.js";
11
- import { C as syncRoles, S as seedAs, _ as cookieOf, a as authModels, b as permissionsOf, c as hashOf, d as GoogleError, f as consentUrl, g as cookie, h as verifyIdToken, i as makers, l as sessions, n as TENANT, p as exchangeCode, r as checkTenancy, s as clearedCookie, t as PLATFORM, v as redirect, x as roleKeys, y as safeReturn } from "./config-D6PexpSC.js";
9
+ import { d as afterUpdate, f as bindFor, h as ownerFieldOf, m as isFalse, p as check, u as FALSE } from "./policies-pFzslmSB.js";
10
+ import { i as checkRoles, n as authPerms, r as checkAuth } from "./create-auth-y8E8cxkn.js";
11
+ import { C as syncRoles, S as seedAs, _ as cookieOf, a as authModels, b as permissionsOf, c as hashOf, d as GoogleError, f as consentUrl, g as cookie, h as verifyIdToken, i as makers, l as sessions, n as TENANT, p as exchangeCode, r as checkTenancy, s as clearedCookie, t as PLATFORM, v as redirect, x as roleKeys, y as safeReturn } from "./config-0PGFbJh6.js";
12
12
  import { t as WrongSize } from "./storage-body-7dWyMszh.js";
13
13
  import { RegExpRouter } from "hono/router/reg-exp-router";
14
14
  import { SmartRouter } from "hono/router/smart-router";
@@ -61,10 +61,10 @@ var Router = class {
61
61
  //#endregion
62
62
  //#region src/api/output.ts
63
63
  /**
64
- * What a handler returns, as JSON (planning/requests.md → Output). Rows the ORM returned are found
64
+ * What a handler returns, as JSON. Rows the ORM returned are found
65
65
  * anywhere in the value by their tag: each field goes out in its kind's JSON form, and hidden
66
66
  * fields (`password`, `secret`, hidden slots) never leave. A file field's files go out as a client
67
- * sees them, with a `url` and without where they're kept (planning/uploads.md → Reading).
67
+ * sees them, with a `url` and without where they're kept.
68
68
  */
69
69
  /** The value as JSON, each file with its URL. */
70
70
  async function toJSON(value, urlOf) {
@@ -354,7 +354,7 @@ function secure(response, url) {
354
354
  //#endregion
355
355
  //#region src/auth/boundary.ts
356
356
  /**
357
- * The route context's `db` (planning/policies.md → Enforcement): the same queries as `app.db`,
357
+ * The route context's `db`: the same queries as `app.db`,
358
358
  * but each one ends in `.allowed(action)` (the model's policy applies) or `.unchecked()` (it
359
359
  * doesn't, visibly). Until then it can't be awaited: a type error, and a rejection at runtime.
360
360
  *
@@ -976,7 +976,7 @@ function rescope(node, scope) {
976
976
  //#endregion
977
977
  //#region src/resources/custom.ts
978
978
  /**
979
- * A resource's custom actions (planning/resources.md, planning/requests.md → Custom actions):
979
+ * A resource's custom actions:
980
980
  * `POST /api/<r>/:key/<action>` (a singleton's: `/api/<r>/<action>`) runs the app's service on one
981
981
  * row:
982
982
  *
@@ -989,7 +989,7 @@ function rescope(node, scope) {
989
989
  * and the action's own rule must hold on it (else 403, or 401 anonymous). Then the service runs:
990
990
  * trusted, with `app.db`, given the row and `{ input, user, tenant }`. What it returns is the
991
991
  * response, as a route's is (`undefined` → 204). With the audit log on, the action is an entry on
992
- * the row (planning/audit.md).
992
+ * the row.
993
993
  */
994
994
  async function runCustom(resource, action, context) {
995
995
  const declared = resource.custom[action];
@@ -1047,7 +1047,7 @@ async function assertHeld(resource, context, rowId, action) {
1047
1047
  //#endregion
1048
1048
  //#region src/audit/entries.ts
1049
1049
  /**
1050
- * Audit entries as a client reads them (planning/audit.md → Reading): each changed field's
1050
+ * Audit entries as a client reads them: each changed field's
1051
1051
  * `[before, after]` back from how it was stored to its kind's JSON form. A hidden field (a secret,
1052
1052
  * a hidden slot) is `"changed"`, and a file field shows its files' ids, never where they're kept.
1053
1053
  */
@@ -1277,7 +1277,7 @@ function fileWrites(upload, storages) {
1277
1277
  //#endregion
1278
1278
  //#region src/resources/write.ts
1279
1279
  /**
1280
- * A resource's writes and trait actions (planning/resources.md), through the route's `db`, so the
1280
+ * A resource's writes and trait actions, through the route's `db`, so the
1281
1281
  * model's policy applies (404 / 403 / 401) and the resource can only narrow it:
1282
1282
  *
1283
1283
  * POST /api/<r> create: only `writable` fields; the resource's `filter` holds for
@@ -1290,7 +1290,7 @@ function fileWrites(upload, storages) {
1290
1290
  * the resource and allowed `reorder`; 204
1291
1291
  *
1292
1292
  * A ref written must name a row the user may read (else a 400 on that field). A file field takes file
1293
- * ids: the row's own files, or uploads pending for it (planning/uploads.md); once the write succeeds
1293
+ * ids: the row's own files, or uploads pending for it; once the write succeeds
1294
1294
  * they're attached, and the files it dropped (a delete drops them all) are removed from storage.
1295
1295
  * Creates and updates answer with the row in the resource's shape, read back through it.
1296
1296
  */
@@ -1483,7 +1483,7 @@ async function reorder(resource, context) {
1483
1483
  //#endregion
1484
1484
  //#region src/resources/mount.ts
1485
1485
  /**
1486
- * Mounting resources (planning/resources.md → Generated): each resource an app module exports is
1486
+ * Mounting resources: each resource an app module exports is
1487
1487
  * mounted at `/api/<export name in kebab-case>`, one route per action:
1488
1488
  *
1489
1489
  * read GET /api/products · GET /api/products/:key (a singleton: GET /api/store)
@@ -1574,7 +1574,7 @@ const engine = (resource, action, context, files) => {
1574
1574
  //#endregion
1575
1575
  //#region src/audit/routes.ts
1576
1576
  /**
1577
- * The whole log (planning/audit.md → Reading), a built-in route:
1577
+ * The whole log, a built-in route:
1578
1578
  *
1579
1579
  * GET /api/audit?model=Variant&user=01H…&createdAt__gte=2026-10-21 newest first audit.read
1580
1580
  *
@@ -1840,7 +1840,7 @@ async function verify(token, purpose, secret, now) {
1840
1840
  //#endregion
1841
1841
  //#region src/auth/google.ts
1842
1842
  /**
1843
- * Sign-in with Google (planning/auth.md → Sign-in with Google). Google redirects only to URIs
1843
+ * Sign-in with Google. Google redirects only to URIs
1844
1844
  * registered in advance, so with tenancy the redirect flow goes through one callback on the
1845
1845
  * platform's host and hands the sign-in back to the tenant's host; without, the callback is on the
1846
1846
  * app's own host and there's no hand-off.
@@ -2040,7 +2040,7 @@ function googleHost(options) {
2040
2040
  //#endregion
2041
2041
  //#region src/auth/people.ts
2042
2042
  /**
2043
- * People and invites (planning/auth.md → Endpoints), built-in routes under `/api/auth/`:
2043
+ * People and invites, built-in routes under `/api/auth/`:
2044
2044
  *
2045
2045
  * GET /api/auth/users the people with accounts, with their roles manageUsers
2046
2046
  * POST /api/auth/users/:id/roles { role } gives a role manageUsers
@@ -2276,7 +2276,7 @@ async function invite(db, models, input) {
2276
2276
  //#endregion
2277
2277
  //#region src/auth/routes.ts
2278
2278
  /**
2279
- * The auth module's built-in routes (planning/auth.md → Endpoints), under `/api/auth/`, the same in
2279
+ * The auth module's built-in routes, under `/api/auth/`, the same in
2280
2280
  * every app:
2281
2281
  *
2282
2282
  * GET /api/auth/me the user, their roles and permission keys (for the UI), or 401
@@ -2313,7 +2313,7 @@ function authRoutes(models, sessions, db, allKeys) {
2313
2313
  //#endregion
2314
2314
  //#region src/files/targets.ts
2315
2315
  /**
2316
- * Upload targets (planning/uploads.md → Uploading): where a file may be uploaded to, named by the
2316
+ * Upload targets: where a file may be uploaded to, named by the
2317
2317
  * resource or route that will take it and its field, both in kebab-case. The target says who may
2318
2318
  * upload and which limits apply, so there's no upload config:
2319
2319
  *
@@ -2369,7 +2369,7 @@ function uploadTargets(modules, resources) {
2369
2369
  //#endregion
2370
2370
  //#region src/files/sniff.ts
2371
2371
  /**
2372
- * What a file is, from its first bytes (planning/uploads.md): its media type, never the one the
2372
+ * What a file is, from its first bytes: its media type, never the one the
2373
2373
  * client declares, and an image's width and height. A file whose bytes say nothing (text, an
2374
2374
  * Office file inside a zip) is told apart by its name's extension.
2375
2375
  */
@@ -2532,7 +2532,7 @@ async function headOf(stream, bytes) {
2532
2532
  //#endregion
2533
2533
  //#region src/files/uploads.ts
2534
2534
  /**
2535
- * Uploading (planning/uploads.md → Uploading): `POST /api/uploads/<target>/<field>` takes one file
2535
+ * Uploading: `POST /api/uploads/<target>/<field>` takes one file
2536
2536
  * as the raw body, with its name in `X-File-Name` (URI-encoded) and its size in `Content-Length`.
2537
2537
  *
2538
2538
  * 1. the target: who may upload, and the field's kind (storage, types, size)
@@ -2622,7 +2622,7 @@ function nameOf(header) {
2622
2622
  //#endregion
2623
2623
  //#region src/files/links.ts
2624
2624
  /**
2625
- * Where a client reads a file (planning/uploads.md → Reading): a public storage's file at its bucket's
2625
+ * Where a client reads a file: a public storage's file at its bucket's
2626
2626
  * URL; a private storage's through a signed link the app serves, `/api/files/<id>?t=<signed>`.
2627
2627
  *
2628
2628
  * The link is made while writing a row the user may read, so making it is the access check. It
@@ -2760,8 +2760,25 @@ function asBytes(body) {
2760
2760
  //#endregion
2761
2761
  //#region src/files/index.ts
2762
2762
  /**
2763
- * The files module (planning/uploads.md): uploads into the app's storages, file ids in writes, and
2764
- * each file's `url` on the way out.
2763
+ * The files module: uploads into the app's storages, file ids in writes, and each file's `url` on
2764
+ * the way out.
2765
+ *
2766
+ * storage: { default: r2(env.MEDIA, { publicUrl: env.MEDIA_URL }), private: r2(env.FILES) } // app.ts
2767
+ * images: f.images() // a model field: the kind owns count, size and type
2768
+ * files: f.files({ storage: "private" }) // a storage other than `default`
2769
+ *
2770
+ * POST /api/uploads/manage-products/images raw bytes → 201 { id: "fil_…", url, … } (pending)
2771
+ * PATCH /api/manage-products/:key { images: ["fil_…", …] } attaches them, in that order
2772
+ *
2773
+ * Upload first, then reference by id: a form uploads each file while it's being filled, and Save is
2774
+ * a small JSON write. Uploads go through the app, not straight to the bucket: it checks the target,
2775
+ * the type (from the bytes) and the size before anything lands, with no S3 credentials beside the
2776
+ * binding and no CORS on the bucket; a presigned upload only pays past the Workers body limit.
2777
+ *
2778
+ * Visibility belongs to the storage (a `publicUrl` or not), never a flag that could disagree with
2779
+ * the bucket. Keys are the framework's (`<tenant>/<fileId>`, or `<fileId>` without tenancy); app
2780
+ * code never builds a key, path or URL. A stored value keeps its storage and key, so old files keep
2781
+ * working if a field's storage changes.
2765
2782
  */
2766
2783
  /**
2767
2784
  * Checks that every file field (a model's or a route input's) stores in a storage `app.ts` has, and
@@ -2838,7 +2855,7 @@ function files(options) {
2838
2855
  //#endregion
2839
2856
  //#region src/tenancy/front-door.ts
2840
2857
  /**
2841
- * The front door's tenant step (planning/tenancy.md → The front door): the host decides. The
2858
+ * The front door's tenant step: the host decides. The
2842
2859
  * platform's host is the platform's; any other is a `Domain` row, behind which is a `Tenant`. A
2843
2860
  * suspended tenant answers 503; an active one gets the whole request, where it lives.
2844
2861
  */
@@ -2892,7 +2909,7 @@ async function releaseOf(manifest) {
2892
2909
  //#endregion
2893
2910
  //#region src/tenancy/services.ts
2894
2911
  /**
2895
- * The app's services by reference (planning/tenancy.md → Creating tenants): what crosses into a
2912
+ * The app's services by reference: what crosses into a
2896
2913
  * tenant is a service's place among `createApp`'s `services` modules and its export name, never
2897
2914
  * the function. The Worker and every tenant's object load the same bundle, so a reference names
2898
2915
  * the same function on both sides.
@@ -3155,7 +3172,7 @@ function createCore(config, env, options = {}) {
3155
3172
  },
3156
3173
  platform: { get db() {
3157
3174
  if (!platform) throw new TypeError("isai: app.platform.db is the platform's database, which only an app with isolated tenancy has");
3158
- if (scope.current()) throw new TypeError("isai: app.platform.db isn't reachable inside a tenant; a tenant's own row is ctx.tenant (planning/tenancy.md)");
3175
+ if (scope.current()) throw new TypeError("isai: app.platform.db isn't reachable inside a tenant; a tenant's own row is ctx.tenant");
3159
3176
  return platform;
3160
3177
  } },
3161
3178
  files: appFiles({
@@ -1,6 +1,8 @@
1
1
  //#region src/validation/issues.d.ts
2
2
  /** What a failed validation reports: one issue per problem, each at a path into the value. */
3
- type IssueCode = "required" | "type" | "format" | "tooShort" | "tooLong" | "tooSmall" | "tooLarge" | "notInteger" | "notAllowed" | "unknown" | "fixed";
3
+ type IssueCode = "required" | "type" | "format" | "tooShort" | "tooLong" | "tooSmall" | "tooLarge" | "notInteger" | "notAllowed" | "unknown" | "fixed" |
4
+ /** Another row already has this value (a unique, or a slug made from this field). */
5
+ "taken";
4
6
  /** Where in the value: field names and list indexes (`["members", 2, "email"]`). */
5
7
  type Path = readonly (string | number)[];
6
8
  interface Issue {
@@ -1,4 +1,4 @@
1
- import { F as systemClock, f as dayOf, h as snapshotOf, p as generateMigration, y as MigrationError } from "./migrator-C65RM_K-.js";
1
+ import { F as systemClock, f as dayOf, h as snapshotOf, p as generateMigration, y as MigrationError } from "./migrator-6oWUefLA.js";
2
2
  //#region src/db/migrations/cleanup.ts
3
3
  /** Dropped tables, those that refer to others first, so no row is left pointing at a dropped one. */
4
4
  function dropOrder(tables) {
@@ -76,7 +76,22 @@ function cleanupMigration(options) {
76
76
  *
77
77
  * Generating never reads the live database. Where existing rows may clash with a change, it
78
78
  * writes the data step that resolves them, from an answer or with a `TODO` that fails the deploy
79
- * until it's filled in (planning/migrations.md → Generating).
79
+ * until it's filled in.
80
+ *
81
+ * add a model or a field CREATE TABLE / ADD COLUMN (nullable in SQL); a fill data step
82
+ * for existing rows (the default, or a value asked)
83
+ * add a count, copy or sequence ADD COLUMN, its triggers, a fill computed from it
84
+ * remove a field or a model nothing dropped: retired until `isai cleanup` (its indexes,
85
+ * counts and copies stay; a required one is filled in new rows)
86
+ * rename nothing: `.column("old_name")` / `.table("old")`, asked about
87
+ * loosen a one-field rule nothing: code only
88
+ * tighten one, or add a check a check data step: passes when no row breaks it, else a `fix`
89
+ * add a unique a resolution step before the index (duplicates as chosen)
90
+ * remove a unique retired until cleanup (an earlier release may rely on it)
91
+ *
92
+ * Refused, each with the safe sequence: an incompatible kind change (or toggling `secret`), a field
93
+ * becoming or ceasing to be kept by the database, a new required unique field over existing rows,
94
+ * a rebuild (a ref's `onDelete`, an FK on an existing column), reusing a retired name.
80
95
  */
81
96
  const pad = (n) => String(n).padStart(4, "0");
82
97
  const json = (value) => JSON.stringify(value);