@pramen/server 0.0.7 → 0.0.8
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/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime/db.d.ts +6 -1
- package/dist/runtime/db.js +34 -10
- package/dist/sdk/schema.d.ts +13 -0
- package/dist/sdk/schema.js +7 -0
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/runtime/db.ts +32 -10
- package/src/sdk/schema.ts +14 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, defaultTo, primaryKey, generated, expr, ExprDefault } from "./sdk/schema";
|
|
1
|
+
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, hidden, defaultTo, primaryKey, generated, expr, ExprDefault } from "./sdk/schema";
|
|
2
2
|
export { isValidUuid } from "./sdk/uuid";
|
|
3
3
|
export type { DefaultValue, FieldType, FieldDef, EntityFields, EntityDef, SchemaDef, RelationDef, RelationDefs, BelongsToDef, HasManyDef, } from "./sdk/schema";
|
|
4
4
|
export { createApp } from "./sdk/app";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// which only exists in the Workers runtime; keeping it separate lets the CLI, tests,
|
|
8
8
|
// and codegen load an app.ts for its schema without dragging in the DO runtime.
|
|
9
9
|
// --- schema authoring ---
|
|
10
|
-
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, defaultTo, primaryKey, generated, expr, ExprDefault } from "./sdk/schema";
|
|
10
|
+
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, hidden, defaultTo, primaryKey, generated, expr, ExprDefault } from "./sdk/schema";
|
|
11
11
|
export { isValidUuid } from "./sdk/uuid";
|
|
12
12
|
// --- app + handlers ---
|
|
13
13
|
export { createApp } from "./sdk/app";
|
package/dist/runtime/db.d.ts
CHANGED
|
@@ -120,6 +120,10 @@ export declare class Db<S extends SchemaDef = SchemaDef> {
|
|
|
120
120
|
private assertReadableWhere;
|
|
121
121
|
private selectRaw;
|
|
122
122
|
private jsonColsOf;
|
|
123
|
+
/** Columns marked `hidden()` — never projected on an ORM read (even SYSTEM/full). */
|
|
124
|
+
private hiddenColsOf;
|
|
125
|
+
/** Drop hidden columns from a row (copying only if any are present). */
|
|
126
|
+
private stripHidden;
|
|
123
127
|
/** Mint a UUID for every `generated()` uuid column the caller omitted, mutating
|
|
124
128
|
* `vals`. Returns the columns it filled — server-minted, so the insert path treats
|
|
125
129
|
* them like forced `set` values (bypassing the writable-field ACL check). */
|
|
@@ -145,7 +149,8 @@ export declare class Db<S extends SchemaDef = SchemaDef> {
|
|
|
145
149
|
* would: the caller's readable fields for this row, PLUS the columns they just
|
|
146
150
|
* wrote (which they already know) and the primary key (so a write-only caller
|
|
147
151
|
* still gets the generated id). Full read access -> the whole row; SYSTEM -> as-is.
|
|
148
|
-
* This makes create/update echoes field-ACL-safe without ever collapsing to {}.
|
|
152
|
+
* This makes create/update echoes field-ACL-safe without ever collapsing to {}.
|
|
153
|
+
* Hidden columns are never echoed, even when written or under SYSTEM. */
|
|
149
154
|
private projectWrite;
|
|
150
155
|
/** Update a row by id. ACL row-scope is AND-ed into the WHERE, so a caller can
|
|
151
156
|
* only update rows within scope; returns undefined if none matched. */
|
package/dist/runtime/db.js
CHANGED
|
@@ -264,6 +264,25 @@ export class Db {
|
|
|
264
264
|
.filter(([, f]) => f.type === "json" || f.type === "fileRef")
|
|
265
265
|
.map(([n]) => n);
|
|
266
266
|
}
|
|
267
|
+
/** Columns marked `hidden()` — never projected on an ORM read (even SYSTEM/full). */
|
|
268
|
+
hiddenColsOf(table) {
|
|
269
|
+
const fields = this.schema[table]?.fields;
|
|
270
|
+
if (!fields)
|
|
271
|
+
return [];
|
|
272
|
+
return Object.entries(fields)
|
|
273
|
+
.filter(([, f]) => f.hidden)
|
|
274
|
+
.map(([n]) => n);
|
|
275
|
+
}
|
|
276
|
+
/** Drop hidden columns from a row (copying only if any are present). */
|
|
277
|
+
stripHidden(table, row) {
|
|
278
|
+
const hidden = this.hiddenColsOf(table);
|
|
279
|
+
if (hidden.length === 0)
|
|
280
|
+
return row;
|
|
281
|
+
const out = { ...row };
|
|
282
|
+
for (const c of hidden)
|
|
283
|
+
delete out[c];
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
267
286
|
/** Mint a UUID for every `generated()` uuid column the caller omitted, mutating
|
|
268
287
|
* `vals`. Returns the columns it filled — server-minted, so the insert path treats
|
|
269
288
|
* them like forced `set` values (bypassing the writable-field ACL check). */
|
|
@@ -325,7 +344,8 @@ export class Db {
|
|
|
325
344
|
}
|
|
326
345
|
/** Fetch one row by id within an ACL row-scope (for per-row write evaluation). */
|
|
327
346
|
async fetchOne(from, id, scopeWhere) {
|
|
328
|
-
const
|
|
347
|
+
const pk = this.pkOf(from);
|
|
348
|
+
const where = scopeWhere ? and(eq(pk, id), scopeWhere) : eq(pk, id);
|
|
329
349
|
const { sql, params } = compileSelect({ from, where, limit: 1 }, this.dialect);
|
|
330
350
|
return this.decodeRow(from, (await this.driver.exec(sql, params))[0]);
|
|
331
351
|
}
|
|
@@ -333,10 +353,11 @@ export class Db {
|
|
|
333
353
|
const relNames = withSel ? Object.keys(withSel).filter((k) => withSel[k]) : [];
|
|
334
354
|
for (const relName of relNames)
|
|
335
355
|
await this.loadRelation(from, raw, relName);
|
|
356
|
+
// Hidden columns are stripped even under an unrestricted/SYSTEM scope (never readable).
|
|
336
357
|
if (scope.fields === null)
|
|
337
|
-
return raw
|
|
358
|
+
return raw.map((r) => this.stripHidden(from, r));
|
|
338
359
|
return raw.map((r) => {
|
|
339
|
-
const projected = projectRow(r, effectiveFields(scope, r, this.acl.identity));
|
|
360
|
+
const projected = this.stripHidden(from, projectRow(r, effectiveFields(scope, r, this.acl.identity)));
|
|
340
361
|
for (const relName of relNames)
|
|
341
362
|
projected[relName] = r[relName]; // relations survive projection
|
|
342
363
|
return projected;
|
|
@@ -382,7 +403,7 @@ export class Db {
|
|
|
382
403
|
// parent[column] -> target.id
|
|
383
404
|
const keys = [...new Set(rows.map((r) => r[rel.column]).filter((v) => v != null))];
|
|
384
405
|
const byId = new Map();
|
|
385
|
-
for (const { key, row } of await fetchBy(
|
|
406
|
+
for (const { key, row } of await fetchBy(this.pkOf(rel.target), keys))
|
|
386
407
|
byId.set(key, row);
|
|
387
408
|
for (const r of rows)
|
|
388
409
|
r[relName] = r[rel.column] != null ? (byId.get(r[rel.column]) ?? null) : null;
|
|
@@ -428,19 +449,22 @@ export class Db {
|
|
|
428
449
|
* would: the caller's readable fields for this row, PLUS the columns they just
|
|
429
450
|
* wrote (which they already know) and the primary key (so a write-only caller
|
|
430
451
|
* still gets the generated id). Full read access -> the whole row; SYSTEM -> as-is.
|
|
431
|
-
* This makes create/update echoes field-ACL-safe without ever collapsing to {}.
|
|
452
|
+
* This makes create/update echoes field-ACL-safe without ever collapsing to {}.
|
|
453
|
+
* Hidden columns are never echoed, even when written or under SYSTEM. */
|
|
432
454
|
projectWrite(table, row, writtenCols) {
|
|
433
455
|
if (this.acl.system)
|
|
434
|
-
return row;
|
|
456
|
+
return this.stripHidden(table, row);
|
|
435
457
|
const visible = new Set([this.pkOf(table), ...writtenCols]);
|
|
436
458
|
const readScope = this.scopeFor(table, "read");
|
|
437
459
|
if (readScope.allowed) {
|
|
438
460
|
const readable = effectiveFields(readScope, row, this.acl.identity);
|
|
439
461
|
if (readable === null)
|
|
440
|
-
return row; // unrestricted read -> echo everything
|
|
462
|
+
return this.stripHidden(table, row); // unrestricted read -> echo everything (minus hidden)
|
|
441
463
|
for (const f of readable)
|
|
442
464
|
visible.add(f);
|
|
443
465
|
}
|
|
466
|
+
for (const c of this.hiddenColsOf(table))
|
|
467
|
+
visible.delete(c);
|
|
444
468
|
return projectRow(row, [...visible]);
|
|
445
469
|
}
|
|
446
470
|
/** Update a row by id. ACL row-scope is AND-ed into the WHERE, so a caller can
|
|
@@ -478,7 +502,7 @@ export class Db {
|
|
|
478
502
|
})
|
|
479
503
|
.join(", ");
|
|
480
504
|
params.push(this.dialect.encode(id));
|
|
481
|
-
let sql = `UPDATE ${this.dialect.id(table)} SET ${assignments} WHERE ${this.dialect.id(
|
|
505
|
+
let sql = `UPDATE ${this.dialect.id(table)} SET ${assignments} WHERE ${this.dialect.id(this.pkOf(table))} = ${this.dialect.placeholder(params.length)}`;
|
|
482
506
|
sql += this.scopeClause(scope.where, params);
|
|
483
507
|
sql += this.returningClause("*");
|
|
484
508
|
const updated = this.decodeRow(table, (await this.driver.exec(sql, params))[0]);
|
|
@@ -492,9 +516,9 @@ export class Db {
|
|
|
492
516
|
if (!scope.allowed)
|
|
493
517
|
throw new AclDenied(table, "delete");
|
|
494
518
|
const params = [this.dialect.encode(id)];
|
|
495
|
-
let sql = `DELETE FROM ${this.dialect.id(table)} WHERE ${this.dialect.id(
|
|
519
|
+
let sql = `DELETE FROM ${this.dialect.id(table)} WHERE ${this.dialect.id(this.pkOf(table))} = ${this.dialect.placeholder(1)}`;
|
|
496
520
|
sql += this.scopeClause(scope.where, params);
|
|
497
|
-
sql += this.returningClause("
|
|
521
|
+
sql += this.returningClause("*");
|
|
498
522
|
return (await this.driver.exec(sql, params)).length > 0;
|
|
499
523
|
}
|
|
500
524
|
/** Escape hatch — raw SQL, NOT ACL-checked. For system/internal use only. */
|
package/dist/sdk/schema.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface FieldDef {
|
|
|
24
24
|
* rebuilds the table, copying data from the old column. A diff cannot tell a
|
|
25
25
|
* rename from a drop+add, so the rename must be declared explicitly. */
|
|
26
26
|
readonly renamedFrom?: string;
|
|
27
|
+
/** Never project this column on any ORM read — find/get, mutation echoes, relation
|
|
28
|
+
* loads, and the SYSTEM-mode admin data API all strip it, even under a full-access
|
|
29
|
+
* (allow()) or SYSTEM scope. Writable on insert/update, and still visible to raw
|
|
30
|
+
* `ctx.db.exec` (the escape hatch credential code uses). For secrets/internal
|
|
31
|
+
* columns like a password hash. Set by the `hidden()` modifier. */
|
|
32
|
+
readonly hidden?: boolean;
|
|
27
33
|
}
|
|
28
34
|
declare const builders: {
|
|
29
35
|
id: () => {
|
|
@@ -124,6 +130,13 @@ export declare function unique<F extends FieldDef>(field: F): F & {
|
|
|
124
130
|
export declare function indexed<F extends FieldDef>(field: F): F & {
|
|
125
131
|
readonly index: true;
|
|
126
132
|
};
|
|
133
|
+
/** Mark a column never-readable through the ORM: stripped from every read projection
|
|
134
|
+
* (find/get, mutation echoes, relation loads, admin data) even under full/SYSTEM
|
|
135
|
+
* access. Still writable, and still visible to raw `ctx.db.exec`. For secrets such as
|
|
136
|
+
* a password hash, e.g. `passwordHash: hidden(t.text())`. */
|
|
137
|
+
export declare function hidden<F extends FieldDef>(field: F): F & {
|
|
138
|
+
readonly hidden: true;
|
|
139
|
+
};
|
|
127
140
|
/** A raw-SQL column DEFAULT (emitted unquoted in the DDL), produced by the `expr`
|
|
128
141
|
* helpers below. Distinct from a literal default so `defaultTo` can render
|
|
129
142
|
* `DEFAULT datetime('now')` rather than the quoted string `DEFAULT 'datetime(...)'`. */
|
package/dist/sdk/schema.js
CHANGED
|
@@ -57,6 +57,13 @@ export function unique(field) {
|
|
|
57
57
|
export function indexed(field) {
|
|
58
58
|
return { ...field, index: true };
|
|
59
59
|
}
|
|
60
|
+
/** Mark a column never-readable through the ORM: stripped from every read projection
|
|
61
|
+
* (find/get, mutation echoes, relation loads, admin data) even under full/SYSTEM
|
|
62
|
+
* access. Still writable, and still visible to raw `ctx.db.exec`. For secrets such as
|
|
63
|
+
* a password hash, e.g. `passwordHash: hidden(t.text())`. */
|
|
64
|
+
export function hidden(field) {
|
|
65
|
+
return { ...field, hidden: true };
|
|
66
|
+
}
|
|
60
67
|
/** A raw-SQL column DEFAULT (emitted unquoted in the DDL), produced by the `expr`
|
|
61
68
|
* helpers below. Distinct from a literal default so `defaultTo` can render
|
|
62
69
|
* `DEFAULT datetime('now')` rather than the quoted string `DEFAULT 'datetime(...)'`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pramen/server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "pramen server runtime — schema, ACL, ORM, live queries, files, and the createPramen(app) factory for Cloudflare Workers + Durable Objects.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// and codegen load an app.ts for its schema without dragging in the DO runtime.
|
|
9
9
|
|
|
10
10
|
// --- schema authoring ---
|
|
11
|
-
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, defaultTo, primaryKey, generated, expr, ExprDefault } from "./sdk/schema";
|
|
11
|
+
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, hidden, defaultTo, primaryKey, generated, expr, ExprDefault } from "./sdk/schema";
|
|
12
12
|
export { isValidUuid } from "./sdk/uuid";
|
|
13
13
|
export type {
|
|
14
14
|
DefaultValue,
|
package/src/runtime/db.ts
CHANGED
|
@@ -385,6 +385,24 @@ export class Db<S extends SchemaDef = SchemaDef> {
|
|
|
385
385
|
.map(([n]) => n);
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
+
/** Columns marked `hidden()` — never projected on an ORM read (even SYSTEM/full). */
|
|
389
|
+
private hiddenColsOf(table: string): string[] {
|
|
390
|
+
const fields = this.schema[table]?.fields;
|
|
391
|
+
if (!fields) return [];
|
|
392
|
+
return Object.entries(fields)
|
|
393
|
+
.filter(([, f]) => (f as FieldDef).hidden)
|
|
394
|
+
.map(([n]) => n);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Drop hidden columns from a row (copying only if any are present). */
|
|
398
|
+
private stripHidden(table: string, row: Row): Row {
|
|
399
|
+
const hidden = this.hiddenColsOf(table);
|
|
400
|
+
if (hidden.length === 0) return row;
|
|
401
|
+
const out = { ...row };
|
|
402
|
+
for (const c of hidden) delete out[c];
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
|
|
388
406
|
/** Mint a UUID for every `generated()` uuid column the caller omitted, mutating
|
|
389
407
|
* `vals`. Returns the columns it filled — server-minted, so the insert path treats
|
|
390
408
|
* them like forced `set` values (bypassing the writable-field ACL check). */
|
|
@@ -444,7 +462,8 @@ export class Db<S extends SchemaDef = SchemaDef> {
|
|
|
444
462
|
|
|
445
463
|
/** Fetch one row by id within an ACL row-scope (for per-row write evaluation). */
|
|
446
464
|
private async fetchOne(from: string, id: Id, scopeWhere: SqlExpr | null): Promise<Row | undefined> {
|
|
447
|
-
const
|
|
465
|
+
const pk = this.pkOf(from);
|
|
466
|
+
const where = scopeWhere ? and(eq(pk, id), scopeWhere) : eq(pk, id);
|
|
448
467
|
const { sql, params } = compileSelect({ from, where, limit: 1 }, this.dialect);
|
|
449
468
|
return this.decodeRow(from, (await this.driver.exec(sql, params))[0] as Row | undefined);
|
|
450
469
|
}
|
|
@@ -452,9 +471,10 @@ export class Db<S extends SchemaDef = SchemaDef> {
|
|
|
452
471
|
private async finishRows(from: string, raw: Row[], scope: Scope, withSel: Selected): Promise<Row[]> {
|
|
453
472
|
const relNames = withSel ? Object.keys(withSel).filter((k) => withSel[k]) : [];
|
|
454
473
|
for (const relName of relNames) await this.loadRelation(from, raw, relName);
|
|
455
|
-
|
|
474
|
+
// Hidden columns are stripped even under an unrestricted/SYSTEM scope (never readable).
|
|
475
|
+
if (scope.fields === null) return raw.map((r) => this.stripHidden(from, r));
|
|
456
476
|
return raw.map((r) => {
|
|
457
|
-
const projected = projectRow(r, effectiveFields(scope, r, this.acl.identity));
|
|
477
|
+
const projected = this.stripHidden(from, projectRow(r, effectiveFields(scope, r, this.acl.identity)));
|
|
458
478
|
for (const relName of relNames) projected[relName] = r[relName]; // relations survive projection
|
|
459
479
|
return projected;
|
|
460
480
|
});
|
|
@@ -498,7 +518,7 @@ export class Db<S extends SchemaDef = SchemaDef> {
|
|
|
498
518
|
// parent[column] -> target.id
|
|
499
519
|
const keys = [...new Set(rows.map((r) => r[rel.column]).filter((v) => v != null))];
|
|
500
520
|
const byId = new Map<unknown, Row>();
|
|
501
|
-
for (const { key, row } of await fetchBy(
|
|
521
|
+
for (const { key, row } of await fetchBy(this.pkOf(rel.target), keys)) byId.set(key, row);
|
|
502
522
|
for (const r of rows) r[relName] = r[rel.column] != null ? (byId.get(r[rel.column]) ?? null) : null;
|
|
503
523
|
} else {
|
|
504
524
|
// hasMany: target[column] -> parent.id
|
|
@@ -545,16 +565,18 @@ export class Db<S extends SchemaDef = SchemaDef> {
|
|
|
545
565
|
* would: the caller's readable fields for this row, PLUS the columns they just
|
|
546
566
|
* wrote (which they already know) and the primary key (so a write-only caller
|
|
547
567
|
* still gets the generated id). Full read access -> the whole row; SYSTEM -> as-is.
|
|
548
|
-
* This makes create/update echoes field-ACL-safe without ever collapsing to {}.
|
|
568
|
+
* This makes create/update echoes field-ACL-safe without ever collapsing to {}.
|
|
569
|
+
* Hidden columns are never echoed, even when written or under SYSTEM. */
|
|
549
570
|
private projectWrite(table: string, row: Row, writtenCols: string[]): Row {
|
|
550
|
-
if (this.acl.system) return row;
|
|
571
|
+
if (this.acl.system) return this.stripHidden(table, row);
|
|
551
572
|
const visible = new Set<string>([this.pkOf(table), ...writtenCols]);
|
|
552
573
|
const readScope = this.scopeFor(table, "read");
|
|
553
574
|
if (readScope.allowed) {
|
|
554
575
|
const readable = effectiveFields(readScope, row, this.acl.identity);
|
|
555
|
-
if (readable === null) return row; // unrestricted read -> echo everything
|
|
576
|
+
if (readable === null) return this.stripHidden(table, row); // unrestricted read -> echo everything (minus hidden)
|
|
556
577
|
for (const f of readable) visible.add(f);
|
|
557
578
|
}
|
|
579
|
+
for (const c of this.hiddenColsOf(table)) visible.delete(c);
|
|
558
580
|
return projectRow(row, [...visible]);
|
|
559
581
|
}
|
|
560
582
|
|
|
@@ -596,7 +618,7 @@ export class Db<S extends SchemaDef = SchemaDef> {
|
|
|
596
618
|
})
|
|
597
619
|
.join(", ");
|
|
598
620
|
params.push(this.dialect.encode(id));
|
|
599
|
-
let sql = `UPDATE ${this.dialect.id(table)} SET ${assignments} WHERE ${this.dialect.id(
|
|
621
|
+
let sql = `UPDATE ${this.dialect.id(table)} SET ${assignments} WHERE ${this.dialect.id(this.pkOf(table))} = ${this.dialect.placeholder(params.length)}`;
|
|
600
622
|
sql += this.scopeClause(scope.where, params);
|
|
601
623
|
sql += this.returningClause("*");
|
|
602
624
|
const updated = this.decodeRow(table, (await this.driver.exec(sql, params))[0]);
|
|
@@ -610,9 +632,9 @@ export class Db<S extends SchemaDef = SchemaDef> {
|
|
|
610
632
|
const scope = this.scopeFor(table, "delete");
|
|
611
633
|
if (!scope.allowed) throw new AclDenied(table, "delete");
|
|
612
634
|
const params: unknown[] = [this.dialect.encode(id)];
|
|
613
|
-
let sql = `DELETE FROM ${this.dialect.id(table)} WHERE ${this.dialect.id(
|
|
635
|
+
let sql = `DELETE FROM ${this.dialect.id(table)} WHERE ${this.dialect.id(this.pkOf(table))} = ${this.dialect.placeholder(1)}`;
|
|
614
636
|
sql += this.scopeClause(scope.where, params);
|
|
615
|
-
sql += this.returningClause("
|
|
637
|
+
sql += this.returningClause("*");
|
|
616
638
|
return (await this.driver.exec(sql, params)).length > 0;
|
|
617
639
|
}
|
|
618
640
|
|
package/src/sdk/schema.ts
CHANGED
|
@@ -39,6 +39,12 @@ export interface FieldDef {
|
|
|
39
39
|
* rebuilds the table, copying data from the old column. A diff cannot tell a
|
|
40
40
|
* rename from a drop+add, so the rename must be declared explicitly. */
|
|
41
41
|
readonly renamedFrom?: string;
|
|
42
|
+
/** Never project this column on any ORM read — find/get, mutation echoes, relation
|
|
43
|
+
* loads, and the SYSTEM-mode admin data API all strip it, even under a full-access
|
|
44
|
+
* (allow()) or SYSTEM scope. Writable on insert/update, and still visible to raw
|
|
45
|
+
* `ctx.db.exec` (the escape hatch credential code uses). For secrets/internal
|
|
46
|
+
* columns like a password hash. Set by the `hidden()` modifier. */
|
|
47
|
+
readonly hidden?: boolean;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
const builders = {
|
|
@@ -134,6 +140,14 @@ export function indexed<F extends FieldDef>(field: F): F & { readonly index: tru
|
|
|
134
140
|
return { ...field, index: true };
|
|
135
141
|
}
|
|
136
142
|
|
|
143
|
+
/** Mark a column never-readable through the ORM: stripped from every read projection
|
|
144
|
+
* (find/get, mutation echoes, relation loads, admin data) even under full/SYSTEM
|
|
145
|
+
* access. Still writable, and still visible to raw `ctx.db.exec`. For secrets such as
|
|
146
|
+
* a password hash, e.g. `passwordHash: hidden(t.text())`. */
|
|
147
|
+
export function hidden<F extends FieldDef>(field: F): F & { readonly hidden: true } {
|
|
148
|
+
return { ...field, hidden: true };
|
|
149
|
+
}
|
|
150
|
+
|
|
137
151
|
/** A raw-SQL column DEFAULT (emitted unquoted in the DDL), produced by the `expr`
|
|
138
152
|
* helpers below. Distinct from a literal default so `defaultTo` can render
|
|
139
153
|
* `DEFAULT datetime('now')` rather than the quoted string `DEFAULT 'datetime(...)'`. */
|