@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.
- package/dist/adapters/cache/cloudflare-kv/index.d.ts +1 -1
- package/dist/adapters/database/d1/index.d.ts +1 -1
- package/dist/adapters/database/durable-object/index.d.ts +1 -1
- package/dist/adapters/database/sqlite-node/index.d.ts +1 -1
- package/dist/adapters/mail/gmail/index.d.ts +1 -1
- package/dist/adapters/pdf/cloudflare-browser/index.d.ts +1 -1
- package/dist/adapters/queue/cloudflare/index.d.ts +1 -1
- package/dist/adapters/ratelimit/cloudflare/index.d.ts +1 -1
- package/dist/adapters/ratelimit/memory/index.d.ts +1 -1
- package/dist/adapters/runtime/cloudflare/index.d.ts +1 -1
- package/dist/adapters/runtime/cloudflare/index.js +1 -1
- package/dist/adapters/storage/disk/index.d.ts +1 -1
- package/dist/adapters/storage/memory/index.d.ts +1 -1
- package/dist/adapters/storage/r2/index.d.ts +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.js +2 -2
- package/dist/{base64url-DwWPqoOT.js → base64url-K_wCmvxR.js} +23 -9
- package/dist/cli/index.js +12 -12
- package/dist/client/index.d.ts +5 -5
- package/dist/client/index.js +3 -3
- package/dist/{config-D6PexpSC.js → config-0PGFbJh6.js} +13 -6
- package/dist/{create-auth-Byf1-VJw.js → create-auth-y8E8cxkn.js} +5 -5
- package/dist/{data-layer-BNtz40PV.js → data-layer-D9UPOK6L.js} +9 -7
- package/dist/{data-layer-BMviRJaW.d.ts → data-layer-ubPfZvFV.d.ts} +20 -11
- package/dist/db/index.d.ts +3 -3
- package/dist/db/index.js +5 -5
- package/dist/{index-CBWFoL3s.d.ts → index-B6ipokoP.d.ts} +35 -25
- package/dist/{index-BVCrbMav.d.ts → index-C-WmIKxT.d.ts} +5 -6
- package/dist/{index-BKwTOLop.d.ts → index-Ce5p2-EB.d.ts} +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +47 -30
- package/dist/{issues-Bxq4JExX.d.ts → issues-CKto95IW.d.ts} +3 -1
- package/dist/{make-DKzPd2b_.js → make-WQpkYW8N.js} +17 -2
- package/dist/{migrator-C65RM_K-.js → migrator-6oWUefLA.js} +68 -9
- package/dist/{policies-yVzFCjxU.js → policies-pFzslmSB.js} +3 -3
- package/dist/ports/index.d.ts +1 -1
- package/dist/{resource-9fjxJgmz.js → resource-C17NvNa3.js} +42 -15
- package/dist/{tables-C3Uza4qK.js → tables-C4ipMFjg.js} +4 -4
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +6 -6
- package/dist/{types-BFc_NEx8.d.ts → types-DpOcIuDl.d.ts} +15 -12
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +1 -1
- package/dist/{validation-l5JG-PFn.js → validation-FyOpSZqE.js} +2 -3
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { W as ValidationError, j as id } from "./validation-
|
|
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-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
2203
|
-
*
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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.
|
package/dist/ports/index.d.ts
CHANGED
|
@@ -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-
|
|
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-
|
|
2
|
-
import { G as UniqueFailed, U as NotFound, V as CheckFailed, W as ReferenceFailed } from "./migrator-
|
|
3
|
-
import { a as columnName, b as asCondition, j as IMPLICIT_FIELDS, l as computedFieldOf, m as Expr, p as Embed } from "./base64url-
|
|
4
|
-
import { r as actionsOf } from "./policies-
|
|
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
|
|
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
|
|
28
|
-
*
|
|
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
|
|
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
|
|
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
|
|
213
|
-
*
|
|
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
|
|
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)
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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()
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as DatabaseDriver, g as Storage } from "../index-
|
|
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-
|
|
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:
|
package/dist/testing/index.js
CHANGED
|
@@ -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-
|
|
3
|
-
import { n as toBase64url, r as Model } from "../base64url-
|
|
4
|
-
import { t as createDataLayer } from "../data-layer-
|
|
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-
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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
|
|
172
|
-
* never written. The path's
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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 };
|
package/dist/validation/index.js
CHANGED
|
@@ -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-
|
|
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
|
-
*
|
|
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,
|