@astram/engine 0.1.0

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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/mariadb-audit-sink.d.ts +24 -0
  3. package/dist/adapters/mariadb-audit-sink.d.ts.map +1 -0
  4. package/dist/adapters/mariadb-audit-sink.js +40 -0
  5. package/dist/adapters/mariadb-audit-sink.js.map +1 -0
  6. package/dist/adapters/mariadb-unit-of-work.d.ts +27 -0
  7. package/dist/adapters/mariadb-unit-of-work.d.ts.map +1 -0
  8. package/dist/adapters/mariadb-unit-of-work.js +58 -0
  9. package/dist/adapters/mariadb-unit-of-work.js.map +1 -0
  10. package/dist/decorators/access-bundle.decorator.d.ts +66 -0
  11. package/dist/decorators/access-bundle.decorator.d.ts.map +1 -0
  12. package/dist/decorators/access-bundle.decorator.js +76 -0
  13. package/dist/decorators/access-bundle.decorator.js.map +1 -0
  14. package/dist/decorators/object-type.decorator.d.ts +116 -0
  15. package/dist/decorators/object-type.decorator.d.ts.map +1 -0
  16. package/dist/decorators/object-type.decorator.js +146 -0
  17. package/dist/decorators/object-type.decorator.js.map +1 -0
  18. package/dist/errors.d.ts +46 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +67 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/index.d.ts +30 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +80 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/module/engine.module.d.ts +90 -0
  27. package/dist/module/engine.module.d.ts.map +1 -0
  28. package/dist/module/engine.module.js +218 -0
  29. package/dist/module/engine.module.js.map +1 -0
  30. package/dist/module/tokens.d.ts +12 -0
  31. package/dist/module/tokens.d.ts.map +1 -0
  32. package/dist/module/tokens.js +14 -0
  33. package/dist/pipe/context.d.ts +55 -0
  34. package/dist/pipe/context.d.ts.map +1 -0
  35. package/dist/pipe/context.js +18 -0
  36. package/dist/pipe/context.js.map +1 -0
  37. package/dist/pipe/record-engine.d.ts +72 -0
  38. package/dist/pipe/record-engine.d.ts.map +1 -0
  39. package/dist/pipe/record-engine.js +279 -0
  40. package/dist/pipe/record-engine.js.map +1 -0
  41. package/dist/pipe/records-service.d.ts +47 -0
  42. package/dist/pipe/records-service.d.ts.map +1 -0
  43. package/dist/pipe/records-service.js +108 -0
  44. package/dist/pipe/stages/audit.d.ts +15 -0
  45. package/dist/pipe/stages/audit.d.ts.map +1 -0
  46. package/dist/pipe/stages/audit.js +42 -0
  47. package/dist/pipe/stages/audit.js.map +1 -0
  48. package/dist/pipe/stages/derive-write-context.d.ts +22 -0
  49. package/dist/pipe/stages/derive-write-context.d.ts.map +1 -0
  50. package/dist/pipe/stages/derive-write-context.js +61 -0
  51. package/dist/pipe/stages/derive-write-context.js.map +1 -0
  52. package/dist/pipe/stages/fetch-owner.d.ts +28 -0
  53. package/dist/pipe/stages/fetch-owner.d.ts.map +1 -0
  54. package/dist/pipe/stages/fetch-owner.js +90 -0
  55. package/dist/pipe/stages/fetch-owner.js.map +1 -0
  56. package/dist/pipe/stages/junction-sync.d.ts +14 -0
  57. package/dist/pipe/stages/junction-sync.d.ts.map +1 -0
  58. package/dist/pipe/stages/junction-sync.js +63 -0
  59. package/dist/pipe/stages/junction-sync.js.map +1 -0
  60. package/dist/pipe/stages/permission-gate.d.ts +11 -0
  61. package/dist/pipe/stages/permission-gate.d.ts.map +1 -0
  62. package/dist/pipe/stages/permission-gate.js +53 -0
  63. package/dist/pipe/stages/permission-gate.js.map +1 -0
  64. package/dist/pipe/stages/validate.d.ts +11 -0
  65. package/dist/pipe/stages/validate.d.ts.map +1 -0
  66. package/dist/pipe/stages/validate.js +89 -0
  67. package/dist/pipe/stages/validate.js.map +1 -0
  68. package/dist/pipe/stages/write.d.ts +12 -0
  69. package/dist/pipe/stages/write.d.ts.map +1 -0
  70. package/dist/pipe/stages/write.js +109 -0
  71. package/dist/pipe/stages/write.js.map +1 -0
  72. package/dist/ports.d.ts +37 -0
  73. package/dist/ports.d.ts.map +1 -0
  74. package/dist/ports.js +11 -0
  75. package/dist/ports.js.map +1 -0
  76. package/dist/registry.d.ts +42 -0
  77. package/dist/registry.d.ts.map +1 -0
  78. package/dist/registry.js +184 -0
  79. package/dist/registry.js.map +1 -0
  80. package/dist/types.d.ts +207 -0
  81. package/dist/types.d.ts.map +1 -0
  82. package/dist/types.js +15 -0
  83. package/dist/types.js.map +1 -0
  84. package/package.json +49 -0
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ /**
3
+ * Stage: write. Persists the validated columns to the real physical table via the
4
+ * bound transactional query fn (parameterized — never string-built). Handles the
5
+ * create (INSERT with a pre-allocated uuid PK), update (UPDATE by id), and delete
6
+ * (DELETE by id). This is the ONLY stage that writes the base row; extension-field
7
+ * sidecar writes attach here too when the table is extensible.
8
+ *
9
+ * uuid generation uses node:crypto (guard/engine own node built-ins; core does not).
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.writeStage = writeStage;
13
+ const node_crypto_1 = require("node:crypto");
14
+ const errors_js_1 = require("../../errors.js");
15
+ const registry_js_1 = require("../../registry.js");
16
+ function qualified(ctx) {
17
+ const schema = ctx.registration.schema;
18
+ const t = ctx.registration.table;
19
+ return schema ? `\`${schema}\`.\`${t}\`` : `\`${t}\``;
20
+ }
21
+ /** The physical column of a `tenantId`-trusted field, if the table has one. */
22
+ function tenantColumn(ctx) {
23
+ const f = ctx.registration.fields.find((x) => x.trusted === 'tenantId');
24
+ return f ? (0, registry_js_1.columnOf)(f) : null;
25
+ }
26
+ /**
27
+ * Build a WHERE clause + params for a record id (uuid or composite). CRITICAL
28
+ * cross-tenant safety: when the table is tenant-owned (has a `tenantId`-trusted
29
+ * column), the WHERE is ALSO constrained to the ACTOR's tenant — so an update/
30
+ * delete can NEVER touch another tenant's row even if the id is known and the gate
31
+ * (which sees the actor's own tenant on a record-scoped resource) would allow it.
32
+ * This is the write-path twin of the read-path scoping (defense-in-depth).
33
+ */
34
+ function whereById(ctx, id) {
35
+ const pk = ctx.registration.primaryKey;
36
+ let sql;
37
+ let params;
38
+ if (pk.kind === 'uuid') {
39
+ if (typeof id !== 'string') {
40
+ throw new errors_js_1.RegistrationError(`uuid PK table '${ctx.registration.typeKey}' needs a string id`);
41
+ }
42
+ sql = `\`${pk.column}\` = ?`;
43
+ params = [id];
44
+ }
45
+ else {
46
+ if (typeof id === 'string') {
47
+ throw new errors_js_1.RegistrationError(`composite PK table '${ctx.registration.typeKey}' needs a key map, not a string id`);
48
+ }
49
+ sql = pk.columns.map((c) => `\`${c}\` = ?`).join(' AND ');
50
+ params = pk.columns.map((c) => id[c]);
51
+ }
52
+ const tenantCol = tenantColumn(ctx);
53
+ if (tenantCol) {
54
+ sql += ` AND \`${tenantCol}\` = ?`;
55
+ params = [...params, ctx.actor.tenantId];
56
+ }
57
+ return { sql, params };
58
+ }
59
+ function writeStage() {
60
+ return {
61
+ name: 'write',
62
+ async run(ctx) {
63
+ const table = qualified(ctx);
64
+ if (ctx.op === 'create') {
65
+ const pk = ctx.registration.primaryKey;
66
+ // Pre-allocate the uuid PK (composite-PK tables set their key columns via fields).
67
+ if (pk.kind === 'uuid') {
68
+ const pkCol = (0, registry_js_1.uuidPkColumn)(pk);
69
+ if (ctx.columns[pkCol] === undefined)
70
+ ctx.columns[pkCol] = (0, node_crypto_1.randomUUID)();
71
+ ctx.resultId = ctx.columns[pkCol];
72
+ }
73
+ else {
74
+ const key = {};
75
+ for (const c of pk.columns)
76
+ key[c] = String(ctx.columns[c]);
77
+ ctx.resultId = key;
78
+ }
79
+ const cols = Object.keys(ctx.columns);
80
+ if (cols.length === 0) {
81
+ throw new errors_js_1.RegistrationError(`create '${ctx.registration.typeKey}': no columns to insert`);
82
+ }
83
+ const placeholders = cols.map(() => '?').join(', ');
84
+ const sql = `INSERT INTO ${table} (${cols.map((c) => `\`${c}\``).join(', ')}) VALUES (${placeholders})`;
85
+ await ctx.query(sql, cols.map((c) => ctx.columns[c]));
86
+ return;
87
+ }
88
+ if (ctx.op === 'update') {
89
+ if (ctx.id === undefined)
90
+ throw new errors_js_1.RegistrationError('update requires an id');
91
+ const cols = Object.keys(ctx.columns);
92
+ if (cols.length === 0)
93
+ return; // nothing to patch
94
+ const setSql = cols.map((c) => `\`${c}\` = ?`).join(', ');
95
+ const where = whereById(ctx, ctx.id);
96
+ const sql = `UPDATE ${table} SET ${setSql} WHERE ${where.sql}`;
97
+ await ctx.query(sql, [...cols.map((c) => ctx.columns[c]), ...where.params]);
98
+ ctx.resultId = ctx.id;
99
+ return;
100
+ }
101
+ // delete
102
+ if (ctx.id === undefined)
103
+ throw new errors_js_1.RegistrationError('delete requires an id');
104
+ const where = whereById(ctx, ctx.id);
105
+ await ctx.query(`DELETE FROM ${table} WHERE ${where.sql}`, where.params);
106
+ ctx.resultId = ctx.id;
107
+ },
108
+ };
109
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.js","sourceRoot":"","sources":["../../../src/pipe/stages/write.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The ports the engine depends on — injected adapters, so the engine's pipe logic
3
+ * is pure over its collaborators (ADR-0003 port/adapter grain). Concrete adapters
4
+ * (the sealed mariadb pool, the decision-authority binding, the audit sink) are
5
+ * wired in the engine's Nest module.
6
+ *
7
+ * Core types are TYPE-ONLY imports (erased at build — no runtime require of the
8
+ * ESM-only core).
9
+ */
10
+ import type { Principal, Action, Resource, DecisionContext, Decision, AuditEvent } from '@astram/core';
11
+ /** Raw parameterized query — the ONE DB primitive the pipe uses (sealed pool). */
12
+ export type QueryFn = (sql: string, params?: readonly unknown[]) => Promise<unknown>;
13
+ /** Opens a transaction and runs `body` with a bound {@link QueryFn}; commit/rollback. */
14
+ export interface UnitOfWork {
15
+ run<T>(body: (query: QueryFn) => Promise<T>): Promise<T>;
16
+ }
17
+ /**
18
+ * The authorization port — the permission-gate stage calls this. Bound to the
19
+ * framework's single {@link import('@astram/core').DecisionAuthority} (guard's
20
+ * `SHIELD_DECISION_AUTHORITY`). The engine NEVER makes its own allow/deny logic;
21
+ * it asks `decide()` and obeys.
22
+ */
23
+ export interface DecisionPort {
24
+ decide(principal: Principal, action: Action, resource: Resource, context: DecisionContext): Promise<Decision>;
25
+ }
26
+ /**
27
+ * The audit port — the audit stage calls `record()` (append-only). It receives the
28
+ * pipe's bound {@link QueryFn} so the audit row commits IN THE SAME TRANSACTION as
29
+ * the data write: there is never data without its audit row, and never an audit row
30
+ * for a rolled-back write. (This differs from core's connection-less `AuditSink`
31
+ * seam by design — atomicity with the record write is a paved-road invariant.)
32
+ */
33
+ export interface AuditPort {
34
+ record(event: AuditEvent, query: QueryFn): Promise<void>;
35
+ }
36
+ export type { Principal, Action, Resource, DecisionContext, Decision, AuditEvent };
37
+ //# sourceMappingURL=ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../src/ports.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,MAAM,EACN,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,UAAU,EACX,MAAM,cAAc,CAAC;AAEtB,kFAAkF;AAClF,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAErF,yFAAyF;AACzF,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC1D;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CACJ,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D;AAED,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC"}
package/dist/ports.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /**
3
+ * The ports the engine depends on — injected adapters, so the engine's pipe logic
4
+ * is pure over its collaborators (ADR-0003 port/adapter grain). Concrete adapters
5
+ * (the sealed mariadb pool, the decision-authority binding, the audit sink) are
6
+ * wired in the engine's Nest module.
7
+ *
8
+ * Core types are TYPE-ONLY imports (erased at build — no runtime require of the
9
+ * ESM-only core).
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.js","sourceRoot":"","sources":["../src/ports.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The table registry — the in-memory source of truth for every registered table's
3
+ * shape (the "SchemaOverview" of the Directus pattern). Features register a
4
+ * {@link ObjectType} at boot (code-registered) — the engine resolves by
5
+ * `typeKey` on every operation. One code path serves all tables because the schema
6
+ * is data.
7
+ *
8
+ * Runtime-registered (no-code) tables will register through the SAME registry later
9
+ * (the no-code phase); the pipe does not distinguish how a registration arrived.
10
+ */
11
+ import type { FieldSpec, PkStrategy, ObjectType } from './types.js';
12
+ export declare class TableRegistry {
13
+ private readonly byType;
14
+ /** Register a table. Throws on a duplicate typeKey or an invalid registration. */
15
+ register(reg: ObjectType): void;
16
+ /** Resolve a registration, or throw a RegistrationError if unknown. */
17
+ require(typeKey: string): ObjectType;
18
+ has(typeKey: string): boolean;
19
+ all(): readonly ObjectType[];
20
+ }
21
+ /** The physical column name for a field (explicit `column` or the field key). */
22
+ export declare function columnOf(field: FieldSpec): string;
23
+ /** The single uuid PK column name, or throw for a composite PK. */
24
+ export declare function uuidPkColumn(pk: PkStrategy): string;
25
+ /**
26
+ * Whether the type declares person-ownership (`access.owned === true`). An owned type
27
+ * has exactly one `trusted:'actorId'` field (enforced by chokepoint 1) — its column is
28
+ * the owner column. Only owned types pre-fetch their owner before the gate.
29
+ */
30
+ export declare function isOwned(reg: ObjectType): boolean;
31
+ /**
32
+ * The physical column of the `trusted:'actorId'` (owner) field, or `null` if the type
33
+ * has none. For an owned type chokepoint 1 guarantees exactly one exists.
34
+ */
35
+ export declare function ownerColumn(reg: ObjectType): string | null;
36
+ /** The physical column of the `trusted:'tenantId'` field, or `null` if the type has none. */
37
+ export declare function tenantColumnOf(reg: ObjectType): string | null;
38
+ /** The physical column of the field bearing `role`, or null if unclaimed. */
39
+ export declare function columnForRole(reg: ObjectType, role: FieldSpec['role']): string | null;
40
+ /** Boot-enforce that exactly-one field claims `role`; throw if none. */
41
+ export declare function requireRole(reg: ObjectType, role: NonNullable<FieldSpec['role']>): void;
42
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEpE,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAExD,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAQ/B,uEAAuE;IACvE,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;IAWpC,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAI7B,GAAG,IAAI,SAAS,UAAU,EAAE;CAG7B;AAED,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAEjD;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,EAAE,EAAE,UAAU,GAAG,MAAM,CAKnD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAEhD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAG1D;AAED,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAG7D;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAGrF;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAMvF"}
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ /**
3
+ * The table registry — the in-memory source of truth for every registered table's
4
+ * shape (the "SchemaOverview" of the Directus pattern). Features register a
5
+ * {@link ObjectType} at boot (code-registered) — the engine resolves by
6
+ * `typeKey` on every operation. One code path serves all tables because the schema
7
+ * is data.
8
+ *
9
+ * Runtime-registered (no-code) tables will register through the SAME registry later
10
+ * (the no-code phase); the pipe does not distinguish how a registration arrived.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.TableRegistry = void 0;
14
+ exports.columnOf = columnOf;
15
+ exports.uuidPkColumn = uuidPkColumn;
16
+ exports.isOwned = isOwned;
17
+ exports.ownerColumn = ownerColumn;
18
+ exports.tenantColumnOf = tenantColumnOf;
19
+ exports.columnForRole = columnForRole;
20
+ exports.requireRole = requireRole;
21
+ const errors_js_1 = require("./errors.js");
22
+ class TableRegistry {
23
+ byType = new Map();
24
+ /** Register a table. Throws on a duplicate typeKey or an invalid registration. */
25
+ register(reg) {
26
+ validate(reg);
27
+ if (this.byType.has(reg.typeKey)) {
28
+ throw new errors_js_1.RegistrationError(`duplicate table registration for typeKey '${reg.typeKey}'`);
29
+ }
30
+ this.byType.set(reg.typeKey, reg);
31
+ }
32
+ /** Resolve a registration, or throw a RegistrationError if unknown. */
33
+ require(typeKey) {
34
+ const reg = this.byType.get(typeKey);
35
+ if (!reg) {
36
+ throw new errors_js_1.RegistrationError(`no table registered for typeKey '${typeKey}' — a feature must register it ` +
37
+ `before the engine can operate on it.`);
38
+ }
39
+ return reg;
40
+ }
41
+ has(typeKey) {
42
+ return this.byType.has(typeKey);
43
+ }
44
+ all() {
45
+ return [...this.byType.values()];
46
+ }
47
+ }
48
+ exports.TableRegistry = TableRegistry;
49
+ /** The physical column name for a field (explicit `column` or the field key). */
50
+ function columnOf(field) {
51
+ return field.column ?? field.key;
52
+ }
53
+ /** The single uuid PK column name, or throw for a composite PK. */
54
+ function uuidPkColumn(pk) {
55
+ if (pk.kind !== 'uuid') {
56
+ throw new errors_js_1.RegistrationError('expected a uuid primary key');
57
+ }
58
+ return pk.column;
59
+ }
60
+ /**
61
+ * Whether the type declares person-ownership (`access.owned === true`). An owned type
62
+ * has exactly one `trusted:'actorId'` field (enforced by chokepoint 1) — its column is
63
+ * the owner column. Only owned types pre-fetch their owner before the gate.
64
+ */
65
+ function isOwned(reg) {
66
+ return reg.access.owned === true;
67
+ }
68
+ /**
69
+ * The physical column of the `trusted:'actorId'` (owner) field, or `null` if the type
70
+ * has none. For an owned type chokepoint 1 guarantees exactly one exists.
71
+ */
72
+ function ownerColumn(reg) {
73
+ const f = reg.fields.find((x) => x.trusted === 'actorId');
74
+ return f ? columnOf(f) : null;
75
+ }
76
+ /** The physical column of the `trusted:'tenantId'` field, or `null` if the type has none. */
77
+ function tenantColumnOf(reg) {
78
+ const f = reg.fields.find((x) => x.trusted === 'tenantId');
79
+ return f ? columnOf(f) : null;
80
+ }
81
+ /** The physical column of the field bearing `role`, or null if unclaimed. */
82
+ function columnForRole(reg, role) {
83
+ const f = reg.fields.find((x) => x.role === role);
84
+ return f?.column ?? f?.key ?? null;
85
+ }
86
+ /** Boot-enforce that exactly-one field claims `role`; throw if none. */
87
+ function requireRole(reg, role) {
88
+ if (!reg.fields.some((f) => f.role === role)) {
89
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': requires a field with role '${role}'.`);
90
+ }
91
+ }
92
+ function validate(reg) {
93
+ if (!reg.typeKey)
94
+ throw new errors_js_1.RegistrationError('registration.typeKey is required');
95
+ if (!reg.table)
96
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': table is required`);
97
+ if (reg.fields.length === 0) {
98
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': at least one field is required`);
99
+ }
100
+ const keys = new Set();
101
+ for (const f of reg.fields) {
102
+ if (keys.has(f.key)) {
103
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': duplicate field key '${f.key}'`);
104
+ }
105
+ keys.add(f.key);
106
+ }
107
+ if (reg.primaryKey.kind === 'composite' && reg.primaryKey.columns.length < 2) {
108
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': composite primary key needs >= 2 columns`);
109
+ }
110
+ validateAccess(reg);
111
+ }
112
+ /**
113
+ * Chokepoint 1 (§4.1 of the framework-owns-data-layer design). Every registered
114
+ * object type MUST declare its access explicitly — this closes tenant-leak-by-omission
115
+ * and ungoverned-record holes at BOOT (dev/CI), never as a silent prod hole:
116
+ *
117
+ * 1. `access` is REQUIRED — absent → boot fails (no silent default).
118
+ * 2. `system` is MUTUALLY EXCLUSIVE with `tenantScoped`/`owned` — a contradictory
119
+ * combo → boot fails.
120
+ * 3. `owned:true` REQUIRES a `trusted:'actorId'` owner column — else boot fails.
121
+ * 4. `tenantScoped:true` REQUIRES a `trusted:'tenantId'` column — else boot fails.
122
+ *
123
+ * NOTE: a `system` type MAY still carry a `trusted:'actorId'` field (a `created_by`
124
+ * audit column, e.g. company_membership/membership_posting) — `system` only forbids
125
+ * being DECLARED owned/tenantScoped, NOT having such a column. (No record-rule check
126
+ * here — that is Phase 2's completeness sweep.)
127
+ */
128
+ function validateAccess(reg) {
129
+ const access = reg.access;
130
+ if (!access) {
131
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': an access declaration is required ` +
132
+ `(access: { plane, tenantScoped?, owned?, system? }) — no silent default.`);
133
+ }
134
+ // SECURITY (root-cap axis): `plane` is REQUIRED + explicit. Omitting it would default a
135
+ // new administrative type to `data` and wrongly deny root, with NO error — the exact
136
+ // posture-by-omission hole `access` itself already forbids. Only the two literal
137
+ // CapabilityPlane values are accepted; absent/other fails loud at boot. (`decide()`
138
+ // still keeps `data` as its runtime fail-safe floor; this enforces the CHOICE at boot.)
139
+ if (access.plane !== 'control' && access.plane !== 'data') {
140
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': access.plane is required and must be ` +
141
+ `'control' or 'data', got ${access.plane === undefined ? 'undefined' : `'${String(access.plane)}'`}. ` +
142
+ `control = who-may-act administration (root allowed); data = business content ` +
143
+ `(root denied). No silent default.`);
144
+ }
145
+ // SECURITY: gate on real booleans, NOT `=== true`. A truthy-non-boolean flag
146
+ // (`owned: 1`, `system: 'yes'`) is a MALFORMED declaration — rejecting it here closes
147
+ // the bypass where such a value would (a) escape the mutual-exclusion / column-required
148
+ // checks below (which key off `=== true`) yet (b) mark the type "governed", producing a
149
+ // type with no owner/tenant column and thus ZERO isolation. Only `true`/`false`/absent
150
+ // are valid; anything else fails loud at boot.
151
+ const flag = (name) => {
152
+ const v = access[name];
153
+ if (v !== undefined && typeof v !== 'boolean') {
154
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': access.${name} must be a boolean, got ` +
155
+ `${typeof v} (${String(v)}).`);
156
+ }
157
+ return v === true;
158
+ };
159
+ const tenantScoped = flag('tenantScoped');
160
+ const owned = flag('owned');
161
+ const system = flag('system');
162
+ // No silent "global by empty object": {} / all-false is "declared nothing" — the same
163
+ // hole as an absent declaration. At least ONE posture must be an explicit choice. Truly
164
+ // global (no tenant, no owner) is expressed with `system: true` (grant-governed) — a
165
+ // DELIBERATE decision, never the accidental default of an empty/all-false object.
166
+ if (!tenantScoped && !owned && !system) {
167
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': access declares no posture ` +
168
+ `(need at least one of tenantScoped/owned/system=true). A global, ungoverned ` +
169
+ `type must be declared system:true — 'global' is a decision, not an empty object.`);
170
+ }
171
+ if (system && (tenantScoped || owned)) {
172
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': access.system is mutually exclusive with ` +
173
+ `tenantScoped/owned.`);
174
+ }
175
+ const hasTrusted = (source) => reg.fields.some((f) => f.trusted === source);
176
+ if (owned && !hasTrusted('actorId')) {
177
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': access.owned requires a field with ` +
178
+ `trusted:'actorId' (the owner column).`);
179
+ }
180
+ if (tenantScoped && !hasTrusted('tenantId')) {
181
+ throw new errors_js_1.RegistrationError(`registration '${reg.typeKey}': access.tenantScoped requires a field with ` +
182
+ `trusted:'tenantId' (the tenant column).`);
183
+ }
184
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAuCH,4BAEC;AAGD,oCAKC;AAOD,0BAEC;AAMD,kCAGC;AAGD,wCAGC;AAvED,2CAAgD;AAGhD,MAAa,aAAa;IACP,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;IAExD,kFAAkF;IAClF,QAAQ,CAAC,GAAe;QACtB,QAAQ,CAAC,GAAG,CAAC,CAAC;QACd,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,6BAAiB,CAAC,6CAA6C,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC;QAC3F,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,uEAAuE;IACvE,OAAO,CAAC,OAAe;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,6BAAiB,CACzB,oCAAoC,OAAO,iCAAiC;gBAC1E,sCAAsC,CACzC,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,GAAG,CAAC,OAAe;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,GAAG;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACnC,CAAC;CACF;AA/BD,sCA+BC;AAED,iFAAiF;AACjF,SAAgB,QAAQ,CAAC,KAAgB;IACvC,OAAO,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC;AACnC,CAAC;AAED,mEAAmE;AACnE,SAAgB,YAAY,CAAC,EAAc;IACzC,IAAI,EAAE,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,6BAAiB,CAAC,6BAA6B,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,EAAE,CAAC,MAAM,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,SAAgB,OAAO,CAAC,GAAe;IACrC,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,GAAe;IACzC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC;IAC1D,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,CAAC;AAED,6FAA6F;AAC7F,SAAgB,cAAc,CAAC,GAAe;IAC5C,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC;IAC3D,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,GAAe;IAC/B,IAAI,CAAC,GAAG,CAAC,OAAO;QAAE,MAAM,IAAI,6BAAiB,CAAC,kCAAkC,CAAC,CAAC;IAClF,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,MAAM,IAAI,6BAAiB,CAAC,iBAAiB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC;IAChG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,6BAAiB,CAAC,iBAAiB,GAAG,CAAC,OAAO,mCAAmC,CAAC,CAAC;IAC/F,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,6BAAiB,CAAC,iBAAiB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAC/F,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,KAAK,WAAW,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7E,MAAM,IAAI,6BAAiB,CACzB,iBAAiB,GAAG,CAAC,OAAO,6CAA6C,CAC1E,CAAC;IACJ,CAAC;IACD,cAAc,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,cAAc,CAAC,GAAe;IACrC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,6BAAiB,CACzB,iBAAiB,GAAG,CAAC,OAAO,uCAAuC;YACjE,0EAA0E,CAC7E,CAAC;IACJ,CAAC;IAED,wFAAwF;IACxF,qFAAqF;IACrF,iFAAiF;IACjF,oFAAoF;IACpF,wFAAwF;IACxF,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QAC1D,MAAM,IAAI,6BAAiB,CACzB,iBAAiB,GAAG,CAAC,OAAO,0CAA0C;YACpE,4BAA4B,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI;YACtG,+EAA+E;YAC/E,mCAAmC,CACtC,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,sFAAsF;IACtF,wFAAwF;IACxF,wFAAwF;IACxF,uFAAuF;IACvF,+CAA+C;IAC/C,MAAM,IAAI,GAAG,CAAC,IAAyC,EAAW,EAAE;QAClE,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,SAAS,EAAE,CAAC;YAC9C,MAAM,IAAI,6BAAiB,CACzB,iBAAiB,GAAG,CAAC,OAAO,aAAa,IAAI,0BAA0B;gBACrE,GAAG,OAAO,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAChC,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,KAAK,IAAI,CAAC;IACpB,CAAC,CAAC;IACF,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAE9B,sFAAsF;IACtF,wFAAwF;IACxF,qFAAqF;IACrF,kFAAkF;IAClF,IAAI,CAAC,YAAY,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QACvC,MAAM,IAAI,6BAAiB,CACzB,iBAAiB,GAAG,CAAC,OAAO,gCAAgC;YAC1D,8EAA8E;YAC9E,kFAAkF,CACrF,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,6BAAiB,CACzB,iBAAiB,GAAG,CAAC,OAAO,8CAA8C;YACxE,qBAAqB,CACxB,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,CAAC,MAA4B,EAAW,EAAE,CAC3D,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;IAC/C,IAAI,KAAK,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,6BAAiB,CACzB,iBAAiB,GAAG,CAAC,OAAO,wCAAwC;YAClE,uCAAuC,CAC1C,CAAC;IACJ,CAAC;IACD,IAAI,YAAY,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,6BAAiB,CACzB,iBAAiB,GAAG,CAAC,OAAO,+CAA+C;YACzE,yCAAyC,CAC5C,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Public types for @astram/engine — the paved road.
3
+ *
4
+ * A feature contributes exactly ONE thing: a {@link ObjectType} (metadata =
5
+ * the contract). From it the engine derives the physical table shape, validation,
6
+ * the `decide()` resource identity, and the audit event. The feature then calls the
7
+ * {@link RecordEngine} facade for all CRUD — it never touches a pool/query-runner.
8
+ *
9
+ * Core types (`Principal`, `Action`, `Resource`) are imported TYPE-ONLY from
10
+ * `@astram/core` (erased at build; the CJS↔ESM lesson — no runtime require of the
11
+ * ESM-only core). Re-exported from the engine barrel so a consumer has ONE import
12
+ * surface.
13
+ */
14
+ import type { Principal, CapabilityPlane } from '@astram/core';
15
+ /** Supported field value kinds the pipe can validate + persist (Phase 1 set). */
16
+ export type FieldType = 'string' | 'number' | 'boolean' | 'uuid' | 'timestamp' | 'json';
17
+ /** One field on a registered table (core column). */
18
+ export interface FieldSpec {
19
+ /** Stable field key (business-meaningful; maps to a physical column). */
20
+ readonly key: string;
21
+ /** Physical column name (defaults to `key` if omitted). */
22
+ readonly column?: string;
23
+ readonly type: FieldType;
24
+ /** Required on create (validate stage rejects a missing value). */
25
+ readonly required?: boolean;
26
+ /**
27
+ * A server-trusted attribute: on write the engine OVERWRITES any client-supplied
28
+ * value with a value derived from the actor/context (anti-forgery). The resolver
29
+ * key names which trusted source fills it (e.g. `tenantId`, `actorId`). Client
30
+ * input for this field is IGNORED.
31
+ */
32
+ readonly trusted?: TrustedSource;
33
+ /**
34
+ * An identity-role marker (see {@link IdentityRole}) — lets the framework find a
35
+ * field by ROLE regardless of the app's chosen column name. Orthogonal to
36
+ * `trusted`: `trusted` marks WRITE-CONTEXT roles (tenantId/actorId, enforced by
37
+ * the derive-write-context stage); `role` marks IDENTITY roles (username,
38
+ * password, etc.) and carries no write-time behavior of its own.
39
+ */
40
+ readonly role?: IdentityRole;
41
+ }
42
+ /** Server-trusted write-context sources (derive-write-context stage). */
43
+ export type TrustedSource = 'tenantId' | 'actorId';
44
+ /** Identity/field ROLE vocabulary — marks WHAT a field IS, so the framework finds it
45
+ * by role regardless of the app's chosen column name. `trusted` (tenantId/actorId) are
46
+ * the WRITE-CONTEXT roles and remain separate; these are the IDENTITY roles. */
47
+ export type IdentityRole = 'primaryKey' | 'username' | 'password' | 'status' | 'active' | 'effectiveFrom' | 'effectiveUntil';
48
+ /**
49
+ * The REQUIRED access declaration on every {@link ObjectType} — two orthogonal flags
50
+ * plus a `system` escape. Replaces "tenant/owner by omission" (a forgotten column =
51
+ * a silent cross-tenant or ungoverned hole) with "by explicit, boot-enforced
52
+ * declaration". Enforced at registration (chokepoint 1 in {@link TableRegistry}):
53
+ *
54
+ * - `owned: true` REQUIRES exactly-one field with `trusted:'actorId'` (the owner
55
+ * column) — else registration throws.
56
+ * - `tenantScoped: true` REQUIRES exactly-one field with `trusted:'tenantId'` — else
57
+ * registration throws.
58
+ * - `system: true` is MUTUALLY EXCLUSIVE with `tenantScoped`/`owned`. A `system`
59
+ * type may still carry a `trusted:'actorId'` field (e.g. a `created_by` audit
60
+ * column) — `system` only forbids being DECLARED owned/tenantScoped, not having
61
+ * such a column.
62
+ *
63
+ * `tenantScoped` and `owned` are independent (a normal data record is BOTH). An
64
+ * absent `access` fails registration (no silent default).
65
+ *
66
+ * `plane` is a SECOND, ORTHOGONAL axis (do not confuse it with the ownership flags
67
+ * above). The ownership flags answer "who reaches an individual ROW?" (gate 6, the
68
+ * record owner-baseline). `plane` answers "may the capped ROOT super-user perform this?"
69
+ * (gate 5, the root cap). They do NOT correlate: a `system` type may be `control`
70
+ * (`role` — administration, root allowed) OR `data` (`company` — business content, root
71
+ * denied). `plane` is REQUIRED and boot-validated for the same reason `access` itself is
72
+ * — a plane is a security posture, and posture-by-omission is a silent hole (a forgotten
73
+ * `plane` would default a new administrative type to `data` and wrongly deny root, with
74
+ * no error). It is DECLARED per type (uniform across the type's actions); the framework
75
+ * defines only the `CapabilityPlane` vocabulary — the consumer app owns the assignment.
76
+ */
77
+ export interface AccessDeclaration {
78
+ /** true → tenant-isolated; a `trusted:'tenantId'` field is REQUIRED. */
79
+ readonly tenantScoped?: boolean;
80
+ /**
81
+ * true → records are person-owned; a `trusted:'actorId'` (owner) field is REQUIRED.
82
+ * Owner = the CREATING principal (DAC).
83
+ */
84
+ readonly owned?: boolean;
85
+ /**
86
+ * true → identity/infrastructure type (company/user/role): neither person-owned nor
87
+ * simply tenant-scoped; grant-governed, framework-managed. MUTUALLY EXCLUSIVE with
88
+ * `tenantScoped`/`owned`.
89
+ */
90
+ readonly system?: boolean;
91
+ /**
92
+ * REQUIRED capability plane (the root-cap axis; see the interface note above).
93
+ * `'control'` → identity/access administration; the capped root MAY perform it without
94
+ * a grant. `'data'` → tenant business content; root is DENIED by construction
95
+ * (`root_data_plane_denied`), even fully granted. Absent → registration fails
96
+ * (chokepoint 1) — no silent `data` default at the declaration layer. (`decide()`
97
+ * keeps `data` as its RUNTIME fail-safe floor, but boot requires an explicit choice.)
98
+ */
99
+ readonly plane: CapabilityPlane;
100
+ }
101
+ /** Primary-key strategy for a registered table. */
102
+ export type PkStrategy = {
103
+ readonly kind: 'uuid';
104
+ readonly column: string;
105
+ } | {
106
+ readonly kind: 'composite';
107
+ readonly columns: readonly string[];
108
+ };
109
+ /** A composite-key junction table this object owns/syncs. */
110
+ export interface JunctionSpec {
111
+ /** Stable name (business key, e.g. `user_roles`). */
112
+ readonly name: string;
113
+ /** Physical junction table. */
114
+ readonly table: string;
115
+ /** Column holding THIS record's id (the local side). */
116
+ readonly localColumn: string;
117
+ /** Column holding the referenced id (the foreign side). */
118
+ readonly foreignColumn: string;
119
+ /** Optional extra payload columns (e.g. `is_active`) with server-set defaults. */
120
+ readonly payloadDefaults?: Readonly<Record<string, unknown>>;
121
+ }
122
+ /** A feature's declaration — the complete contract for one table on the pipe. */
123
+ export interface ObjectType {
124
+ /** Stable object-type key — the `decide()` resource typeKey + audit type key. */
125
+ readonly typeKey: string;
126
+ /** Physical table name. */
127
+ readonly table: string;
128
+ /** Schema/database the table lives in (defaults to the engine's app schema). */
129
+ readonly schema?: string;
130
+ readonly primaryKey: PkStrategy;
131
+ readonly fields: readonly FieldSpec[];
132
+ readonly junctions?: readonly JunctionSpec[];
133
+ /** Opt into the runtime extension-field sidecar for this table. */
134
+ readonly extensible?: boolean;
135
+ /**
136
+ * REQUIRED access declaration (tenantScoped/owned/system). Absent → registration
137
+ * fails (chokepoint 1). See {@link AccessDeclaration}.
138
+ */
139
+ readonly access: AccessDeclaration;
140
+ }
141
+ /** Input to a create/update — field keys → values (+ optional junction refs). */
142
+ export interface RecordInput {
143
+ /** Core + extension field values, keyed by field key. */
144
+ readonly fields: Readonly<Record<string, unknown>>;
145
+ /**
146
+ * Multi-reference junction values, keyed by junction name → array of foreign ids
147
+ * (DELETE+INSERT synced by the junction-sync stage).
148
+ */
149
+ readonly junctions?: Readonly<Record<string, readonly string[]>>;
150
+ }
151
+ /** A record id — a uuid string, or a composite key map for junction-style tables. */
152
+ export type RecordId = string | Readonly<Record<string, string>>;
153
+ /** A read-projected record (UUID→label resolution + unauthorized columns hidden). */
154
+ export interface RecordView {
155
+ readonly id: RecordId;
156
+ readonly fields: Readonly<Record<string, unknown>>;
157
+ readonly junctions?: Readonly<Record<string, readonly string[]>>;
158
+ }
159
+ /**
160
+ * Free-text search across one or more registered field keys — a single search box
161
+ * that ORs a `LIKE '%term%'` over each named field. Field keys are whitelisted via
162
+ * the registration (unknown → RegistrationError); the term is bound as a `?` param
163
+ * (never interpolated), and LIKE wildcards in the term are escaped so a literal `%`
164
+ * or `_` the user types matches literally instead of acting as a wildcard.
165
+ */
166
+ export interface SearchQuery {
167
+ /** Registered field keys to search (each resolved to its column + whitelisted). */
168
+ readonly fields: readonly string[];
169
+ /** The search term — bound as `%term%` per field (wildcards escaped). */
170
+ readonly term: string;
171
+ }
172
+ /**
173
+ * A list query — tenant-scoped filtering + simple paging, plus optional search
174
+ * (LIKE across fields) and sort (ORDER BY). All field references are registered
175
+ * field KEYS, resolved+whitelisted to columns; all user values are bound `?` params.
176
+ */
177
+ export interface ListQuery {
178
+ readonly limit?: number;
179
+ readonly offset?: number;
180
+ /** Equality filters on core field keys. */
181
+ readonly where?: Readonly<Record<string, unknown>>;
182
+ /** Free-text LIKE search across named field keys (ANDed with `where` + tenant). */
183
+ readonly search?: SearchQuery;
184
+ /** A registered field KEY to ORDER BY (unknown → RegistrationError). */
185
+ readonly sortBy?: string;
186
+ /** Sort direction; anything other than 'desc' is treated as ascending. */
187
+ readonly sortOrder?: 'asc' | 'desc';
188
+ }
189
+ /** A page of read-projected records. */
190
+ export interface RecordPage {
191
+ readonly rows: readonly RecordView[];
192
+ readonly total: number;
193
+ }
194
+ /**
195
+ * The ONLY data-access surface a feature is given. Every method flows through the
196
+ * full pipe (permission gate + audit are mandatory). No pool, query runner, or
197
+ * repository is ever handed to feature scope — that is the physical wall.
198
+ */
199
+ export interface RecordEngine {
200
+ create(typeKey: string, input: RecordInput, actor: Principal): Promise<RecordId>;
201
+ update(typeKey: string, id: RecordId, input: RecordInput, actor: Principal): Promise<void>;
202
+ delete(typeKey: string, id: RecordId, actor: Principal): Promise<void>;
203
+ get(typeKey: string, id: RecordId, actor: Principal): Promise<RecordView | null>;
204
+ list(typeKey: string, query: ListQuery, actor: Principal): Promise<RecordPage>;
205
+ }
206
+ export type { Principal };
207
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}