@pramen/server 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/auth.d.ts +35 -0
  2. package/dist/auth.js +189 -0
  3. package/dist/durable-object.d.ts +48 -0
  4. package/dist/durable-object.js +282 -0
  5. package/dist/index.d.ts +14 -0
  6. package/dist/index.js +20 -0
  7. package/dist/pramen.d.ts +42 -0
  8. package/dist/pramen.js +19 -0
  9. package/dist/runtime/acl.d.ts +62 -0
  10. package/dist/runtime/acl.js +289 -0
  11. package/dist/runtime/db.d.ts +139 -0
  12. package/dist/runtime/db.js +425 -0
  13. package/dist/runtime/ddl.d.ts +16 -0
  14. package/dist/runtime/ddl.js +64 -0
  15. package/dist/runtime/digest.d.ts +1 -0
  16. package/dist/runtime/digest.js +29 -0
  17. package/dist/runtime/dispatch.d.ts +12 -0
  18. package/dist/runtime/dispatch.js +37 -0
  19. package/dist/runtime/driver.d.ts +45 -0
  20. package/dist/runtime/driver.js +70 -0
  21. package/dist/runtime/errors.d.ts +34 -0
  22. package/dist/runtime/errors.js +43 -0
  23. package/dist/runtime/kv.d.ts +23 -0
  24. package/dist/runtime/kv.js +41 -0
  25. package/dist/runtime/migrate.d.ts +22 -0
  26. package/dist/runtime/migrate.js +158 -0
  27. package/dist/runtime/protocol.d.ts +40 -0
  28. package/dist/runtime/protocol.js +12 -0
  29. package/dist/runtime/read-engine.d.ts +73 -0
  30. package/dist/runtime/read-engine.js +219 -0
  31. package/dist/runtime/schema-diff.d.ts +14 -0
  32. package/dist/runtime/schema-diff.js +41 -0
  33. package/dist/runtime/storage.d.ts +74 -0
  34. package/dist/runtime/storage.js +0 -0
  35. package/dist/sdk/acl.d.ts +130 -0
  36. package/dist/sdk/acl.js +55 -0
  37. package/dist/sdk/app.d.ts +7 -0
  38. package/dist/sdk/app.js +11 -0
  39. package/dist/sdk/files.d.ts +51 -0
  40. package/dist/sdk/files.js +4 -0
  41. package/dist/sdk/handlers.d.ts +36 -0
  42. package/dist/sdk/handlers.js +11 -0
  43. package/dist/sdk/infer.d.ts +79 -0
  44. package/dist/sdk/infer.js +5 -0
  45. package/dist/sdk/schema.d.ts +112 -0
  46. package/dist/sdk/schema.js +56 -0
  47. package/dist/worker-entry.d.ts +3 -0
  48. package/dist/worker-entry.js +8 -0
  49. package/dist/worker.d.ts +41 -0
  50. package/dist/worker.js +213 -0
  51. package/package.json +43 -0
  52. package/src/auth.ts +215 -0
  53. package/src/durable-object.ts +346 -0
  54. package/src/index.ts +77 -0
  55. package/src/pramen.ts +58 -0
  56. package/src/runtime/acl.ts +362 -0
  57. package/src/runtime/db.ts +550 -0
  58. package/src/runtime/ddl.ts +67 -0
  59. package/src/runtime/digest.ts +31 -0
  60. package/src/runtime/dispatch.ts +65 -0
  61. package/src/runtime/driver.ts +95 -0
  62. package/src/runtime/errors.ts +56 -0
  63. package/src/runtime/kv.ts +47 -0
  64. package/src/runtime/migrate.ts +193 -0
  65. package/src/runtime/protocol.ts +46 -0
  66. package/src/runtime/read-engine.ts +243 -0
  67. package/src/runtime/schema-diff.ts +57 -0
  68. package/src/runtime/storage.ts +0 -0
  69. package/src/sdk/acl.ts +196 -0
  70. package/src/sdk/app.ts +25 -0
  71. package/src/sdk/files.ts +53 -0
  72. package/src/sdk/handlers.ts +65 -0
  73. package/src/sdk/infer.ts +105 -0
  74. package/src/sdk/schema.ts +122 -0
  75. package/src/worker-entry.ts +9 -0
  76. package/src/worker.ts +253 -0
@@ -0,0 +1,62 @@
1
+ import { type Action, type FieldsFn, type Identity, type PolicyRule, type ResolverDb, type Role, type Validator } from "../sdk/acl";
2
+ import { type SqlExpr } from "./read-engine";
3
+ import { PramenError } from "./errors";
4
+ export declare class AclDenied extends PramenError {
5
+ readonly entity: string;
6
+ readonly action: Action;
7
+ readonly field?: string | undefined;
8
+ constructor(entity: string, action: Action, field?: string | undefined);
9
+ }
10
+ export interface CompiledAcl {
11
+ /** roleName -> entity\0action -> rules that grant it. */
12
+ readonly byRole: Map<string, Map<string, PolicyRule[]>>;
13
+ }
14
+ /** Per-request ACL context: the compiled policies plus the caller's identity. */
15
+ export interface AclContext {
16
+ readonly acl: CompiledAcl;
17
+ readonly identity: Identity | null;
18
+ /** The request input (handler args), so policy `where` rules can reference an
19
+ * `$input(...)` marker — a capability / by-unguessable-key read grant. */
20
+ readonly input?: unknown;
21
+ /** Resolver results for this request (resolverId -> rule), from warmup(). */
22
+ readonly resolved?: Map<number, PolicyRule>;
23
+ /** SYSTEM mode bypasses all ACL — used for warmup reads and internal ops. */
24
+ readonly system?: boolean;
25
+ }
26
+ /** Evaluate every resolver reachable by the identity's roles, once per request.
27
+ * Resolvers read through a SYSTEM-mode db (ACL bypassed) to avoid recursion. */
28
+ export declare function warmup(acl: CompiledAcl, identity: Identity | null, db: ResolverDb): Promise<Map<number, PolicyRule>>;
29
+ export declare function compileAcl(roles: Role[]): CompiledAcl;
30
+ /** A per-row additive field grant: `fields` apply to rows where `when` holds. */
31
+ export interface ConditionalGrant {
32
+ readonly when: SqlExpr;
33
+ readonly fields: string[];
34
+ }
35
+ export interface Scope {
36
+ readonly allowed: boolean;
37
+ /** Row-level predicate to AND into the query. null = unrestricted. */
38
+ readonly where: SqlExpr | null;
39
+ /** Statically-granted base fields. null = all fields (no per-row narrowing). */
40
+ readonly fields: string[] | null;
41
+ /** Cell-level grants evaluated per row; additive over `fields`. */
42
+ readonly conditional: ConditionalGrant[];
43
+ /** Cell-level function resolvers evaluated per row; additive over `fields`. */
44
+ readonly fieldsFns: FieldsFn[];
45
+ }
46
+ export declare const ALLOW_ALL: Scope;
47
+ export declare function resolveScope(ctx: AclContext, entity: string, action: Action): Scope;
48
+ /** Effective visible fields for one row = base ∪ matching-conditional ∪ fn-output.
49
+ * Returns null (all fields) when the base is null or a resolver grants everything. */
50
+ export declare function effectiveFields(scope: Scope, row: Record<string, unknown>, identity: Identity | null): string[] | null;
51
+ /** Forced values + validators for a write, gathered from matched write policies.
52
+ * `set` values are resolved against the identity; later policies override earlier. */
53
+ export interface WriteRules {
54
+ set: Record<string, unknown>;
55
+ validators: Validator[];
56
+ }
57
+ export declare function resolveWriteRules(ctx: AclContext, entity: string, action: Action): WriteRules;
58
+ /** Scope for traversing `parentEntity.relName` to `target`. Grants come from the
59
+ * target's own read scope OR a parent read policy's relation rule with directAccess. */
60
+ export declare function resolveRelationScope(ctx: AclContext, parentEntity: string, relName: string, target: string): Scope;
61
+ /** Project a row to the permitted fields. null = all. */
62
+ export declare function projectRow(row: Record<string, unknown>, fields: string[] | null): Record<string, unknown>;
@@ -0,0 +1,289 @@
1
+ // ACL resolution — the runtime counterpart of sdk/acl.ts.
2
+ // Given an identity + (entity, action), resolves a Scope: whether access is
3
+ // granted, the row-level predicate to merge into the query, and any field
4
+ // restriction. Deny-by-default; grants OR-merge across the identity's roles.
5
+ import { deny, isAllow, isDeny, isIdentityMarker, isInputMarker, isResolver, } from "../sdk/acl";
6
+ import { compileWhere, evalExpr, FALSE, or } from "./read-engine";
7
+ import { PramenError } from "./errors";
8
+ export class AclDenied extends PramenError {
9
+ entity;
10
+ action;
11
+ field;
12
+ constructor(entity, action, field) {
13
+ super(field ? `access denied: ${entity}.${field} (${action})` : `access denied: ${action} ${entity}`, 403, "forbidden");
14
+ this.entity = entity;
15
+ this.action = action;
16
+ this.field = field;
17
+ this.name = "AclDenied";
18
+ }
19
+ }
20
+ /** Evaluate every resolver reachable by the identity's roles, once per request.
21
+ * Resolvers read through a SYSTEM-mode db (ACL bypassed) to avoid recursion. */
22
+ export async function warmup(acl, identity, db) {
23
+ const out = new Map();
24
+ for (const roleName of rolesOf(identity)) {
25
+ const byKey = acl.byRole.get(roleName);
26
+ if (!byKey)
27
+ continue;
28
+ for (const rules of byKey.values()) {
29
+ for (const rule of rules) {
30
+ if (!isResolver(rule) || out.has(rule.id))
31
+ continue;
32
+ try {
33
+ out.set(rule.id, await rule.fn({ identity, db }));
34
+ }
35
+ catch {
36
+ out.set(rule.id, deny()); // a throwing resolver denies
37
+ }
38
+ }
39
+ }
40
+ }
41
+ return out;
42
+ }
43
+ const key = (entity, action) => `${entity}\0${action}`;
44
+ export function compileAcl(roles) {
45
+ const byRole = new Map();
46
+ for (const r of roles) {
47
+ const byKey = byRole.get(r.name) ?? new Map();
48
+ for (const p of r.policies) {
49
+ const k = key(p.entity, p.action);
50
+ (byKey.get(k) ?? byKey.set(k, []).get(k)).push(p.rule);
51
+ }
52
+ byRole.set(r.name, byKey);
53
+ }
54
+ return { byRole };
55
+ }
56
+ export const ALLOW_ALL = { allowed: true, where: null, fields: null, conditional: [], fieldsFns: [] };
57
+ const DENIED = { allowed: false, where: null, fields: null, conditional: [], fieldsFns: [] };
58
+ /** Build a grant from a policy/relation rule, resolving $identity markers in
59
+ * `where` and each conditional `when`. */
60
+ function grantOf(rule, where, identity, input) {
61
+ return {
62
+ where,
63
+ fields: rule.fields ?? null,
64
+ conditional: (rule.conditionalFields ?? []).map((cf) => ({
65
+ when: whereToExpr(cf.when, identity, input),
66
+ fields: cf.fields,
67
+ })),
68
+ fieldsFns: rule.fieldsFn ? [rule.fieldsFn] : [],
69
+ };
70
+ }
71
+ /** The roles to evaluate for a caller. An unauthenticated caller (no verified
72
+ * token) is treated as the `anonymous` role, so an app can grant first-class
73
+ * public reads/writes; if no `anonymous` role is defined this matches nothing
74
+ * (still deny-by-default). */
75
+ function rolesOf(identity) {
76
+ if (!identity)
77
+ return ["anonymous"];
78
+ if (identity.roles?.length)
79
+ return identity.roles;
80
+ return identity.role ? [identity.role] : ["anonymous"];
81
+ }
82
+ function getPath(obj, path) {
83
+ return path.split(".").reduce((acc, seg) => (acc == null ? undefined : acc[seg]), obj ?? undefined);
84
+ }
85
+ const UNRESOLVED = Symbol("unresolved");
86
+ // Resolve a value that may be an $identity marker (against the caller) or an
87
+ // $input marker (against the request input — a capability/by-key grant). An
88
+ // unresolvable marker yields UNRESOLVED, which makes its rule match nothing.
89
+ function resolveValue(v, identity, input) {
90
+ if (isIdentityMarker(v)) {
91
+ const value = getPath(identity, v.path);
92
+ return value === undefined ? UNRESOLVED : value;
93
+ }
94
+ if (isInputMarker(v)) {
95
+ const value = getPath(input, v.path);
96
+ return value === undefined ? UNRESOLVED : value;
97
+ }
98
+ return v;
99
+ }
100
+ // Resolve every $identity marker in a policy where-rule (bare values, operator
101
+ // objects, in/notIn arrays, AND/OR groups). Returns a plain WhereInput, or null
102
+ // if any marker is unresolvable — in which case the rule matches nothing.
103
+ function resolveMarkers(rule, identity, input) {
104
+ const out = {};
105
+ for (const [key, v] of Object.entries(rule)) {
106
+ if (key === "AND" || key === "OR") {
107
+ const groups = [];
108
+ for (const g of v) {
109
+ const resolved = resolveMarkers(g, identity, input);
110
+ if (resolved === null)
111
+ return null;
112
+ groups.push(resolved);
113
+ }
114
+ out[key] = groups;
115
+ continue;
116
+ }
117
+ const isMarker = isIdentityMarker(v) || isInputMarker(v);
118
+ if (v !== null && typeof v === "object" && !isMarker && !Array.isArray(v)) {
119
+ const ops = {};
120
+ for (const [op, val] of Object.entries(v)) {
121
+ if (op === "in" || op === "notIn") {
122
+ let arr;
123
+ if (isIdentityMarker(val) || isInputMarker(val)) {
124
+ arr = resolveValue(val, identity, input);
125
+ if (arr === UNRESOLVED)
126
+ return null;
127
+ }
128
+ else {
129
+ const mapped = val.map((x) => resolveValue(x, identity, input));
130
+ if (mapped.some((x) => x === UNRESOLVED))
131
+ return null;
132
+ arr = mapped;
133
+ }
134
+ if (!Array.isArray(arr))
135
+ return null; // marker must resolve to a list
136
+ ops[op] = arr;
137
+ }
138
+ else {
139
+ const rv = resolveValue(val, identity, input);
140
+ if (rv === UNRESOLVED)
141
+ return null;
142
+ ops[op] = rv;
143
+ }
144
+ }
145
+ out[key] = ops;
146
+ }
147
+ else {
148
+ const rv = resolveValue(v, identity, input);
149
+ if (rv === UNRESOLVED)
150
+ return null;
151
+ out[key] = rv;
152
+ }
153
+ }
154
+ return out;
155
+ }
156
+ /** Turn a policy where-rule into an expression. Supports the full user query
157
+ * surface (operators, AND/OR) with $identity / $input markers; an unresolvable
158
+ * marker makes the rule match nothing. */
159
+ function whereToExpr(rule, identity, input) {
160
+ const resolved = resolveMarkers(rule, identity, input);
161
+ return resolved === null ? FALSE : compileWhere(resolved);
162
+ }
163
+ /** The concrete rules that apply for (entity, action) under this identity — with
164
+ * resolvers replaced by their warmup result and resolver/unresolved entries dropped. */
165
+ function matchedRules(ctx, entity, action) {
166
+ const k = key(entity, action);
167
+ const out = [];
168
+ for (const roleName of rolesOf(ctx.identity)) {
169
+ const forRole = ctx.acl.byRole.get(roleName)?.get(k);
170
+ if (!forRole)
171
+ continue;
172
+ for (const raw of forRole) {
173
+ const rule = isResolver(raw) ? ctx.resolved?.get(raw.id) : raw;
174
+ if (rule && !isResolver(rule))
175
+ out.push(rule);
176
+ }
177
+ }
178
+ return out;
179
+ }
180
+ const ALLOW_GRANT = { where: null, fields: null, conditional: [], fieldsFns: [] };
181
+ /** OR-merge a set of grants into a Scope. No grants -> denied. Conditional/function
182
+ * field grants concatenate; they only ADD fields to a non-null base. */
183
+ function mergeGrants(grants) {
184
+ if (grants.length === 0)
185
+ return DENIED;
186
+ let unrestrictedWhere = false;
187
+ let unrestrictedFields = false;
188
+ const orParts = [];
189
+ const fields = new Set();
190
+ const conditional = [];
191
+ const fieldsFns = [];
192
+ for (const g of grants) {
193
+ if (g.where === null || g.where.t === "true")
194
+ unrestrictedWhere = true;
195
+ else
196
+ orParts.push(g.where);
197
+ if (g.fields === null)
198
+ unrestrictedFields = true;
199
+ else
200
+ for (const f of g.fields)
201
+ fields.add(f);
202
+ conditional.push(...g.conditional);
203
+ fieldsFns.push(...g.fieldsFns);
204
+ }
205
+ const where = unrestrictedWhere ? null : orParts.length === 1 ? orParts[0] : or(...orParts);
206
+ return {
207
+ allowed: true,
208
+ where,
209
+ fields: unrestrictedFields ? null : [...fields],
210
+ conditional,
211
+ fieldsFns,
212
+ };
213
+ }
214
+ export function resolveScope(ctx, entity, action) {
215
+ const grants = [];
216
+ for (const rule of matchedRules(ctx, entity, action)) {
217
+ if (isDeny(rule))
218
+ continue;
219
+ if (isAllow(rule))
220
+ grants.push(ALLOW_GRANT);
221
+ else
222
+ grants.push(grantOf(rule, whereToExpr(rule.where ?? {}, ctx.identity, ctx.input), ctx.identity, ctx.input));
223
+ }
224
+ return mergeGrants(grants);
225
+ }
226
+ /** Effective visible fields for one row = base ∪ matching-conditional ∪ fn-output.
227
+ * Returns null (all fields) when the base is null or a resolver grants everything. */
228
+ export function effectiveFields(scope, row, identity) {
229
+ if (scope.fields === null)
230
+ return null;
231
+ const out = new Set(scope.fields);
232
+ for (const g of scope.conditional)
233
+ if (evalExpr(g.when, row))
234
+ for (const f of g.fields)
235
+ out.add(f);
236
+ for (const fn of scope.fieldsFns) {
237
+ const extra = fn(identity, row);
238
+ if (extra === null)
239
+ return null;
240
+ for (const f of extra)
241
+ out.add(f);
242
+ }
243
+ return [...out];
244
+ }
245
+ export function resolveWriteRules(ctx, entity, action) {
246
+ const set = {};
247
+ const validators = [];
248
+ for (const rule of matchedRules(ctx, entity, action)) {
249
+ if (isAllow(rule) || isDeny(rule))
250
+ continue;
251
+ if (rule.set) {
252
+ for (const [col, v] of Object.entries(rule.set)) {
253
+ set[col] = typeof v === "function" ? v(ctx.identity) : v;
254
+ }
255
+ }
256
+ if (rule.validate)
257
+ validators.push(rule.validate);
258
+ }
259
+ return { set, validators };
260
+ }
261
+ /** Scope for traversing `parentEntity.relName` to `target`. Grants come from the
262
+ * target's own read scope OR a parent read policy's relation rule with directAccess. */
263
+ export function resolveRelationScope(ctx, parentEntity, relName, target) {
264
+ if (ctx.system)
265
+ return ALLOW_ALL;
266
+ const grants = [];
267
+ const base = resolveScope(ctx, target, "read");
268
+ if (base.allowed)
269
+ grants.push({ where: base.where, fields: base.fields, conditional: base.conditional, fieldsFns: base.fieldsFns });
270
+ for (const rule of matchedRules(ctx, parentEntity, "read")) {
271
+ if (isAllow(rule) || isDeny(rule))
272
+ continue;
273
+ const rel = rule.relations?.[relName];
274
+ if (rel?.directAccess) {
275
+ grants.push(grantOf(rel, rel.where ? whereToExpr(rel.where, ctx.identity, ctx.input) : null, ctx.identity, ctx.input));
276
+ }
277
+ }
278
+ return mergeGrants(grants);
279
+ }
280
+ /** Project a row to the permitted fields. null = all. */
281
+ export function projectRow(row, fields) {
282
+ if (!fields)
283
+ return row;
284
+ const out = {};
285
+ for (const f of fields)
286
+ if (f in row)
287
+ out[f] = row[f];
288
+ return out;
289
+ }
@@ -0,0 +1,139 @@
1
+ import { type AclContext } from "./acl";
2
+ import { type AggFn } from "./read-engine";
3
+ import type { Driver } from "./driver";
4
+ import type { EntityFields, SchemaDef } from "../sdk/schema";
5
+ import type { Cell, FieldsOf, InferInsert, InferRow, InferUpdate, RelationsOf, RelationsResult, WhereInput } from "../sdk/infer";
6
+ type Row = Record<string, unknown>;
7
+ type Id = string | number | bigint;
8
+ type OrderSpec<S extends SchemaDef, T extends keyof S> = {
9
+ column: keyof FieldsOf<S[T]> & string;
10
+ dir?: "asc" | "desc";
11
+ };
12
+ export interface FindSpec<S extends SchemaDef, T extends keyof S> {
13
+ from: T;
14
+ where?: WhereInput<FieldsOf<S[T]>>;
15
+ orderBy?: OrderSpec<S, T> | OrderSpec<S, T>[];
16
+ limit?: number;
17
+ offset?: number;
18
+ /** Eager-load relations. Each loaded relation is independently ACL-checked. */
19
+ with?: Partial<Record<keyof RelationsOf<S[T]> & string, true>>;
20
+ }
21
+ /** Cursor (keyset) pagination input — `after` is an opaque cursor from a prior page. */
22
+ export interface PageSpec<S extends SchemaDef, T extends keyof S> {
23
+ from: T;
24
+ where?: WhereInput<FieldsOf<S[T]>>;
25
+ orderBy?: OrderSpec<S, T> | OrderSpec<S, T>[];
26
+ limit?: number;
27
+ after?: string;
28
+ with?: Partial<Record<keyof RelationsOf<S[T]> & string, true>>;
29
+ }
30
+ export interface Page<R> {
31
+ items: R[];
32
+ /** Opaque cursor for the last item — pass as `after` to fetch the next page. */
33
+ cursor: string | null;
34
+ hasMore: boolean;
35
+ }
36
+ /** The aggregations map of an aggregate spec, keyed by output column name. */
37
+ type Aggregations<F extends EntityFields> = Record<string, {
38
+ fn: AggFn;
39
+ column?: keyof F & string;
40
+ }>;
41
+ export interface AggregateSpec<S extends SchemaDef, T extends keyof S> {
42
+ from: T;
43
+ where?: WhereInput<FieldsOf<S[T]>>;
44
+ groupBy?: (keyof FieldsOf<S[T]> & string) | (keyof FieldsOf<S[T]> & string)[];
45
+ aggregations: Aggregations<FieldsOf<S[T]>>;
46
+ }
47
+ /** One aggregate result row: loosely typed (any output column -> value). */
48
+ export type AggregateRow = Record<string, number | string | null>;
49
+ type GroupKeys<G> = G extends readonly (infer K)[] ? K : G;
50
+ /** The value type of a single aggregation: count -> number; min/max -> the column's
51
+ * own type (nullable); sum/avg -> number | null. */
52
+ type AggValue<Fn extends AggFn, Col, F extends EntityFields> = Fn extends "count" ? number : Fn extends "min" | "max" ? Col extends keyof F ? Cell<F[Col]> | null : number | null : number | null;
53
+ /** A result row inferred from a (groupBy, aggregations) spec: group columns keep
54
+ * their schema type, each aggregation gets its computed value type. */
55
+ export type AggregateResult<F extends EntityFields, G, A extends Aggregations<F>> = {
56
+ [K in Extract<GroupKeys<G>, keyof F & string>]: Cell<F[K]>;
57
+ } & {
58
+ [K in keyof A]: AggValue<A[K]["fn"], A[K]["column"], F>;
59
+ };
60
+ export declare class Db<S extends SchemaDef = SchemaDef> {
61
+ private readonly driver;
62
+ private readonly acl;
63
+ private readonly schema;
64
+ /** Tables read or written during this Db's lifetime. */
65
+ readonly touched: Set<string>;
66
+ private readonly dialect;
67
+ constructor(driver: Driver, acl: AclContext, schema: SchemaDef);
68
+ /** Resolve the ACL scope for an operation, or grant everything in SYSTEM mode. */
69
+ private scopeFor;
70
+ /** Forced `set` values + validators for a write (empty in SYSTEM mode). The two
71
+ * halves are applied separately so the cell-level field check can run AFTER `set`
72
+ * (so a conditional `when` sees forced columns) but BEFORE `validate`. */
73
+ private writeRules;
74
+ /** Run write validators against the final values; a throw surfaces as a 400. */
75
+ private runValidators;
76
+ /** Enforce field-level (incl. cell-level) write permission for one row. `setCols`
77
+ * are server-forced values that bypass the restriction. `evalRow` is the row the
78
+ * per-row grants are evaluated against (candidate on insert, post-merge on update). */
79
+ private checkWriteFields;
80
+ /** Reject ordering by a column the caller cannot read (closes an info-leak: order
81
+ * and the keyset cursor would otherwise expose a hidden column's values). Columns
82
+ * granted only conditionally are NOT orderable. */
83
+ private assertReadableCols;
84
+ /** Structured read; ACL row-scope is AND-ed in, permitted fields projected.
85
+ * Selected relations are eager-loaded, each independently ACL-checked. */
86
+ find<T extends keyof S & string>(spec: FindSpec<S, T>): Promise<(InferRow<FieldsOf<S[T]>> & RelationsResult<S, T>)[]>;
87
+ /** Cursor (keyset) pagination. Stable under inserts/deletes; the PK is appended
88
+ * to `orderBy` as a tiebreaker so the keyset is unique. Returns the page plus an
89
+ * opaque `cursor` (pass back as `after`) and whether more rows remain. */
90
+ page<T extends keyof S & string>(spec: PageSpec<S, T>): Promise<Page<InferRow<FieldsOf<S[T]>> & RelationsResult<S, T>>>;
91
+ /** Count rows visible to the caller (ACL read scope applied). */
92
+ count<T extends keyof S & string>(spec: {
93
+ from: T;
94
+ where?: WhereInput<FieldsOf<S[T]>>;
95
+ }): Promise<number>;
96
+ /** Grouped aggregation (count/sum/avg/min/max). ACL read scope is applied, and
97
+ * every referenced column must be readable under field permissions. The result
98
+ * row type is inferred from the spec: group columns keep their schema type and
99
+ * each aggregation gets its computed value type. */
100
+ aggregate<T extends keyof S & string, A extends Aggregations<FieldsOf<S[T]>>, G extends (keyof FieldsOf<S[T]> & string) | (keyof FieldsOf<S[T]> & string)[] = never>(spec: {
101
+ from: T;
102
+ where?: WhereInput<FieldsOf<S[T]>>;
103
+ groupBy?: G;
104
+ aggregations: A;
105
+ }): Promise<AggregateResult<FieldsOf<S[T]>, G, A>[]>;
106
+ private readWhere;
107
+ private selectRaw;
108
+ private jsonColsOf;
109
+ private decodeRows;
110
+ private decodeRow;
111
+ /** Encode one write cell: JSON-stringify a json/fileRef value, then dialect-encode. */
112
+ private encodeCell;
113
+ /** Fetch one row by id within an ACL row-scope (for per-row write evaluation). */
114
+ private fetchOne;
115
+ private finishRows;
116
+ private orderWithPk;
117
+ private pkOf;
118
+ /** Eager-load one relation onto `rows` (mutates them). Traversal is ACL-checked
119
+ * via resolveRelationScope: the related read scope OR a parent directAccess grant. */
120
+ private loadRelation;
121
+ /** Insert a single row, returning the persisted row. */
122
+ insert<T extends keyof S & string>(table: T, values: InferInsert<FieldsOf<S[T]>>): Promise<InferRow<FieldsOf<S[T]>>>;
123
+ /** Project a mutation's RETURNING row so the echo never reveals more than a read
124
+ * would: the caller's readable fields for this row, PLUS the columns they just
125
+ * wrote (which they already know) and the primary key (so a write-only caller
126
+ * still gets the generated id). Full read access -> the whole row; SYSTEM -> as-is.
127
+ * This makes create/update echoes field-ACL-safe without ever collapsing to {}. */
128
+ private projectWrite;
129
+ /** Update a row by id. ACL row-scope is AND-ed into the WHERE, so a caller can
130
+ * only update rows within scope; returns undefined if none matched. */
131
+ update<T extends keyof S & string>(table: T, id: Id, patch: InferUpdate<FieldsOf<S[T]>>): Promise<InferRow<FieldsOf<S[T]>> | undefined>;
132
+ /** Delete a row by id within scope. Returns whether a row was deleted. */
133
+ delete<T extends keyof S & string>(table: T, id: Id): Promise<boolean>;
134
+ /** Escape hatch — raw SQL, NOT ACL-checked. For system/internal use only. */
135
+ exec(sql: string, ...params: unknown[]): Promise<Row[]>;
136
+ private returningClause;
137
+ private scopeClause;
138
+ }
139
+ export {};