@sequenceholdings/orm 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.
- package/README.md +37 -0
- package/dist/cli.cjs +2369 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +9 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +2364 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.cjs +223 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +171 -0
- package/dist/client.d.ts +171 -0
- package/dist/client.js +217 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +2308 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +830 -0
- package/dist/index.d.ts +830 -0
- package/dist/index.js +2204 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
|
|
3
|
+
|
|
4
|
+
// src/client/transport.ts
|
|
5
|
+
var OrmClientError = class extends Error {
|
|
6
|
+
constructor(message, status = null) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
}
|
|
10
|
+
name = "OrmClientError";
|
|
11
|
+
};
|
|
12
|
+
function createFetchTransport({
|
|
13
|
+
baseUrl,
|
|
14
|
+
getToken,
|
|
15
|
+
getDataToken,
|
|
16
|
+
fetchImpl = globalThis.fetch
|
|
17
|
+
}) {
|
|
18
|
+
if (!fetchImpl) throw new OrmClientError("no fetch implementation available");
|
|
19
|
+
if ((getToken ? 1 : 0) + (getDataToken ? 1 : 0) !== 1) {
|
|
20
|
+
throw new OrmClientError("provide exactly one of getToken (bearer) or getDataToken (delegated)");
|
|
21
|
+
}
|
|
22
|
+
const root = baseUrl.replace(/\/+$/, "");
|
|
23
|
+
const request = async (path, init) => {
|
|
24
|
+
const token = await (getDataToken ?? getToken)();
|
|
25
|
+
if (!token) throw new OrmClientError("not authenticated \u2014 no token available");
|
|
26
|
+
const auth = getDataToken ? { [ORM_DATA_TOKEN_HEADER]: token } : { Authorization: `Bearer ${token}` };
|
|
27
|
+
const response = await fetchImpl(`${root}${path}`, {
|
|
28
|
+
...init,
|
|
29
|
+
headers: { ...auth, "Content-Type": "application/json" }
|
|
30
|
+
});
|
|
31
|
+
const payload = await response.json().catch(() => null);
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const detail = payload && typeof payload === "object" && "detail" in payload ? String(payload.detail) : `HTTP ${response.status}`;
|
|
34
|
+
throw new OrmClientError(detail, response.status);
|
|
35
|
+
}
|
|
36
|
+
return payload;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
get: (path, params) => {
|
|
40
|
+
const serialized = params.toString();
|
|
41
|
+
return request(serialized === "" ? path : `${path}?${serialized}`, {});
|
|
42
|
+
},
|
|
43
|
+
post: (path, body) => request(path, { method: "POST", body: JSON.stringify(body ?? {}) })
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/client/query-builder.ts
|
|
48
|
+
var IN_LIST_UNREPRESENTABLE = /[,()]/;
|
|
49
|
+
var EMPTY_STATE = {
|
|
50
|
+
filters: [],
|
|
51
|
+
orderings: [],
|
|
52
|
+
selection: null,
|
|
53
|
+
limitValue: null,
|
|
54
|
+
offsetValue: null
|
|
55
|
+
};
|
|
56
|
+
var TableQuery = class _TableQuery {
|
|
57
|
+
constructor(transport, namespace, table, state = EMPTY_STATE) {
|
|
58
|
+
this.transport = transport;
|
|
59
|
+
this.namespace = namespace;
|
|
60
|
+
this.table = table;
|
|
61
|
+
this.state = state;
|
|
62
|
+
}
|
|
63
|
+
with(patch) {
|
|
64
|
+
return new _TableQuery(this.transport, this.namespace, this.table, { ...this.state, ...patch });
|
|
65
|
+
}
|
|
66
|
+
select(...columns) {
|
|
67
|
+
return new _TableQuery(this.transport, this.namespace, this.table, {
|
|
68
|
+
...this.state,
|
|
69
|
+
selection: columns
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
eq(column, value) {
|
|
73
|
+
return this.filter(column, "eq", value);
|
|
74
|
+
}
|
|
75
|
+
ne(column, value) {
|
|
76
|
+
return this.filter(column, "ne", value);
|
|
77
|
+
}
|
|
78
|
+
gt(column, value) {
|
|
79
|
+
return this.filter(column, "gt", value);
|
|
80
|
+
}
|
|
81
|
+
gte(column, value) {
|
|
82
|
+
return this.filter(column, "gte", value);
|
|
83
|
+
}
|
|
84
|
+
lt(column, value) {
|
|
85
|
+
return this.filter(column, "lt", value);
|
|
86
|
+
}
|
|
87
|
+
lte(column, value) {
|
|
88
|
+
return this.filter(column, "lte", value);
|
|
89
|
+
}
|
|
90
|
+
like(column, pattern) {
|
|
91
|
+
return this.filter(column, "like", pattern);
|
|
92
|
+
}
|
|
93
|
+
ilike(column, pattern) {
|
|
94
|
+
return this.filter(column, "ilike", pattern);
|
|
95
|
+
}
|
|
96
|
+
in(column, values) {
|
|
97
|
+
const rendered = values.map((value) => {
|
|
98
|
+
const text = String(value);
|
|
99
|
+
if (IN_LIST_UNREPRESENTABLE.test(text) || text !== text.trim()) {
|
|
100
|
+
throw new OrmClientError(`in() value '${text}' cannot be represented \u2014 the list grammar has no escaping`);
|
|
101
|
+
}
|
|
102
|
+
return text;
|
|
103
|
+
});
|
|
104
|
+
if (rendered.length === 1 && rendered[0] === "") {
|
|
105
|
+
throw new OrmClientError("in() with a lone '' cannot be represented \u2014 it would read as the empty list");
|
|
106
|
+
}
|
|
107
|
+
return this.with({ filters: [...this.state.filters, [column, `in.(${rendered.join(",")})`]] });
|
|
108
|
+
}
|
|
109
|
+
is(column, value) {
|
|
110
|
+
return this.with({
|
|
111
|
+
filters: [...this.state.filters, [column, `is.${value === null ? "null" : value}`]]
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
order(column, direction = "asc") {
|
|
115
|
+
return this.with({ orderings: [...this.state.orderings, `${column}.${direction}`] });
|
|
116
|
+
}
|
|
117
|
+
limit(value) {
|
|
118
|
+
return this.with({ limitValue: value });
|
|
119
|
+
}
|
|
120
|
+
offset(value) {
|
|
121
|
+
return this.with({ offsetValue: value });
|
|
122
|
+
}
|
|
123
|
+
/** The exact query string sent to the Data API — exposed for debugging and the grammar contract test. */
|
|
124
|
+
searchParams() {
|
|
125
|
+
const params = new URLSearchParams();
|
|
126
|
+
for (const [column, expression] of this.state.filters) params.append(column, expression);
|
|
127
|
+
if (this.state.orderings.length > 0) params.set("order", this.state.orderings.join(","));
|
|
128
|
+
if (this.state.selection) params.set("select", this.state.selection.join(","));
|
|
129
|
+
if (this.state.limitValue !== null) params.set("limit", String(this.state.limitValue));
|
|
130
|
+
if (this.state.offsetValue !== null) params.set("offset", String(this.state.offsetValue));
|
|
131
|
+
return params;
|
|
132
|
+
}
|
|
133
|
+
async rows() {
|
|
134
|
+
const payload = await this.transport.get(
|
|
135
|
+
`/api/data/${this.namespace}/${this.table}`,
|
|
136
|
+
this.searchParams()
|
|
137
|
+
);
|
|
138
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.rows)) {
|
|
139
|
+
throw new OrmClientError("unexpected Data API response shape \u2014 missing rows array");
|
|
140
|
+
}
|
|
141
|
+
return payload.rows;
|
|
142
|
+
}
|
|
143
|
+
filter(column, op, value) {
|
|
144
|
+
return this.with({ filters: [...this.state.filters, [column, `${op}.${String(value)}`]] });
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/client/index.ts
|
|
149
|
+
function createOrmClient({
|
|
150
|
+
namespace,
|
|
151
|
+
baseUrl,
|
|
152
|
+
getToken,
|
|
153
|
+
fetchImpl,
|
|
154
|
+
transport,
|
|
155
|
+
dataToken
|
|
156
|
+
}) {
|
|
157
|
+
const resolved = transport ?? createFetchTransport({
|
|
158
|
+
baseUrl: requireOption(baseUrl, "baseUrl"),
|
|
159
|
+
...dataToken !== void 0 ? { getDataToken: typeof dataToken === "function" ? dataToken : () => dataToken } : { getToken: requireOption(getToken, "getToken") },
|
|
160
|
+
fetchImpl
|
|
161
|
+
});
|
|
162
|
+
return buildOrmClient(resolved, namespace);
|
|
163
|
+
}
|
|
164
|
+
function createServiceOrmClient({
|
|
165
|
+
namespace,
|
|
166
|
+
baseUrl,
|
|
167
|
+
serviceDataToken,
|
|
168
|
+
fetchImpl,
|
|
169
|
+
transport
|
|
170
|
+
}) {
|
|
171
|
+
const resolved = transport ?? createFetchTransport({
|
|
172
|
+
baseUrl: requireOption(baseUrl, "baseUrl"),
|
|
173
|
+
getDataToken: typeof serviceDataToken === "function" ? serviceDataToken : () => requireOption(serviceDataToken, "serviceDataToken"),
|
|
174
|
+
fetchImpl
|
|
175
|
+
});
|
|
176
|
+
return buildOrmClient(resolved, namespace);
|
|
177
|
+
}
|
|
178
|
+
function buildOrmClient(resolved, namespace) {
|
|
179
|
+
return {
|
|
180
|
+
table(name) {
|
|
181
|
+
return new TableQuery(resolved, namespace, name);
|
|
182
|
+
},
|
|
183
|
+
async query(sql, params) {
|
|
184
|
+
const payload = await resolved.post(`/api/data/${namespace}/query`, {
|
|
185
|
+
sql,
|
|
186
|
+
...params ? { params: [...params] } : {}
|
|
187
|
+
});
|
|
188
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.rows)) {
|
|
189
|
+
throw new OrmClientError("unexpected Data API response shape \u2014 missing rows array");
|
|
190
|
+
}
|
|
191
|
+
return payload.rows;
|
|
192
|
+
},
|
|
193
|
+
async action(name, ...args) {
|
|
194
|
+
const payload = await resolved.post(`/api/data/${namespace}/actions/${name}`, args[0] ?? {});
|
|
195
|
+
if (payload && typeof payload === "object") {
|
|
196
|
+
if (Array.isArray(payload.rows)) {
|
|
197
|
+
return { rows: payload.rows };
|
|
198
|
+
}
|
|
199
|
+
if ("rows_affected" in payload) {
|
|
200
|
+
const rowsAffected = Number(payload.rows_affected);
|
|
201
|
+
if (!Number.isNaN(rowsAffected)) {
|
|
202
|
+
return { rowsAffected };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
throw new OrmClientError("unexpected action response shape \u2014 expected rows or rows_affected");
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function requireOption(value, name) {
|
|
211
|
+
if (value === void 0) throw new Error(`createOrmClient requires ${name} when no transport is injected`);
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export { OrmClientError, TableQuery, createFetchTransport, createOrmClient, createServiceOrmClient };
|
|
216
|
+
//# sourceMappingURL=client.js.map
|
|
217
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/client/transport.ts","../src/client/query-builder.ts","../src/client/index.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["/** Namespace names become Postgres identifiers (`orm_<ns>` schema, `orm_<ns>_consumer` role) — the tight grammar is a security control, not cosmetics. */\nexport const NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/\n\nexport const IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/\nexport const MAX_IDENTIFIER_LENGTH = 63\n\n/**\n * The Data API carries a delegated, attenuated data token on this header (not\n * `Authorization`, which stays the app-user/session bearer). One definition\n * shared by the client transport, the server's caller resolver, and the\n * function-invoke minter, so all three agree.\n */\nexport const ORM_DATA_TOKEN_HEADER = 'x-sequence-data-token'\n\n/**\n * The SEPARATE header the invoke proxy uses to hand a managed function its\n * *service* data token — the escalation path. A function receives BOTH this and\n * `ORM_DATA_TOKEN_HEADER`; the default `createOrmClient` reads the user token,\n * the deliberately-named `createServiceOrmClient` reads THIS one. Two distinct\n * headers so reaching for the service identity is always an explicit act, never\n * an accidental default. Both are sent to the Data API on `ORM_DATA_TOKEN_HEADER`\n * — the server stays mode-agnostic and resolves whatever sub the token carries.\n */\nexport const ORM_SERVICE_DATA_TOKEN_HEADER = 'x-sequence-data-token-service'\n\n/**\n * Custom Roles & Capabilities claim keys as policies reference them: a\n * host-scoped registry key, or an org pin's `org/<key>`. Matches the registry\n * key grammar EXACTLY (`[a-z0-9-]`, 1–64 chars, no `_` — the FGA object-id\n * delimiter) so any capability that's declarable/attachable in namespace.yaml\n * can also be referenced here — a stricter pattern would let a valid key\n * register yet fail at apply (e.g. `org/2fa`, a one-char or digit-led key).\n */\nexport const CAPABILITY_KEY_PATTERN = /^(?:org\\/)?[a-z0-9-]{1,64}$/\n\nexport const CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/\n\n/** Qualified reference to a table outside the defining file: `<schema>.<table>`. */\nexport const QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]*\\.[a-z][a-z0-9_]*$/\n\n/** Synced reflected sources are restricted to the gold layer. */\nexport const SYNCED_SOURCE_PATTERN = /^gold\\.[a-z][a-z0-9_]*$/\n\nexport const ORM_SCHEMA_PREFIX = 'orm_'\nexport const CONSUMER_ROLE_SUFFIX = '_consumer'\n\nexport const ormSchemaName = (namespace: string): string => `${ORM_SCHEMA_PREFIX}${namespace}`\nexport const consumerRoleName = (namespace: string): string =>\n `${ORM_SCHEMA_PREFIX}${namespace}${CONSUMER_ROLE_SUFFIX}`\n\nexport const DEFINITION_FILE_EXTENSIONS = ['.yml', '.yaml'] as const\n\n/** Reserved basenames for the namespace-level config file (description, org capability pins). */\nexport const NAMESPACE_CONFIG_FILENAMES = new Set(['namespace.yaml', 'namespace.yml'])\n\nexport const HASH_DOMAIN = 'sequence-orm-definitions\\0'\n\n/**\n * The one size guard the package owns: a per-file byte cap where the loader\n * reads untrusted YAML into memory — a real failure mode, not a product\n * limit. Counts of tables/indexes/params and policy/SQL lengths are\n * deliberately uncapped; Postgres enforces its own limits loudly at apply.\n */\nexport const MAX_DEFINITION_FILE_BYTES = 1024 * 1024\n\n/** `/api/data/<ns>/actions/…` and `/api/data/<ns>/query` share the table path segment. */\nexport const RESERVED_TABLE_NAMES = new Set(['actions', 'query'])\n\n/**\n * The Data API's query-string grammar owns these parameter names — a column\n * with one of them could never be filtered over the wire, so definitions\n * refuse it up front (\"validate rejects only what can never work\").\n */\nexport const RESERVED_COLUMN_NAMES = new Set(['select', 'order', 'limit', 'offset'])\n","/**\n * Fetch-based transport for the Data API. Deliberately free of node builtins\n * so the client runs anywhere a `fetch` exists — browsers, artifact bridges,\n * Cloud Functions, `node:vm` sandboxes.\n */\nimport { ORM_DATA_TOKEN_HEADER } from '../constants.js'\n\nexport class OrmClientError extends Error {\n override readonly name = 'OrmClientError'\n\n constructor(\n message: string,\n readonly status: number | null = null,\n ) {\n super(message)\n }\n}\n\nexport interface OrmTransport {\n get(path: string, params: URLSearchParams): Promise<unknown>\n post(path: string, body: unknown): Promise<unknown>\n}\n\nexport function createFetchTransport({\n baseUrl,\n getToken,\n getDataToken,\n fetchImpl = globalThis.fetch,\n}: {\n baseUrl: string\n /** App-user / session bearer → `Authorization: Bearer`. */\n getToken?: () => Promise<string | null> | string | null\n /** Delegated, attenuated function token → the `ORM_DATA_TOKEN_HEADER`. */\n getDataToken?: () => Promise<string | null> | string | null\n fetchImpl?: typeof globalThis.fetch\n}): OrmTransport {\n if (!fetchImpl) throw new OrmClientError('no fetch implementation available')\n if ((getToken ? 1 : 0) + (getDataToken ? 1 : 0) !== 1) {\n throw new OrmClientError('provide exactly one of getToken (bearer) or getDataToken (delegated)')\n }\n const root = baseUrl.replace(/\\/+$/, '')\n\n const request = async (path: string, init: Omit<RequestInit, 'headers'>): Promise<unknown> => {\n const token = await (getDataToken ?? getToken)!()\n if (!token) throw new OrmClientError('not authenticated — no token available')\n // The delegated token rides its own header; the app-user bearer rides Authorization.\n const auth: Record<string, string> = getDataToken\n ? { [ORM_DATA_TOKEN_HEADER]: token }\n : { Authorization: `Bearer ${token}` }\n\n const response = await fetchImpl(`${root}${path}`, {\n ...init,\n headers: { ...auth, 'Content-Type': 'application/json' },\n })\n const payload: unknown = await response.json().catch(() => null)\n if (!response.ok) {\n const detail =\n payload && typeof payload === 'object' && 'detail' in payload\n ? String((payload as { detail: unknown }).detail)\n : `HTTP ${response.status}`\n throw new OrmClientError(detail, response.status)\n }\n return payload\n }\n\n return {\n get: (path, params) => {\n // Not params.size — that's Node 19.8+/2023-browser surface, newer than\n // the fetch this module documents as its only requirement.\n const serialized = params.toString()\n return request(serialized === '' ? path : `${path}?${serialized}`, {})\n },\n post: (path, body) => request(path, { method: 'POST', body: JSON.stringify(body ?? {}) }),\n }\n}\n","import { OrmClientError, type OrmTransport } from './transport.js'\n\n/**\n * Fluent read query over one table. Serializes to exactly the PostgREST\n * subset the Data API's query parser accepts — the round-trip contract is\n * asserted server-side (builder output → parseReadQuery) so the two ends\n * can't drift apart silently.\n *\n * IMMUTABLE: every method returns a new query, so narrowing `select()` can\n * never leave an alias holding a stale wider row type, and a base query can\n * be safely forked into variants.\n */\n\nexport type FilterValue = string | number | boolean\n\nexport interface ReadResponse<Row> {\n readonly rows: readonly Row[]\n readonly limit: number\n readonly offset: number\n}\n\n/** Characters the in-list grammar cannot carry — there is no escaping, so the builder hard-fails. */\nconst IN_LIST_UNREPRESENTABLE = /[,()]/\n\ninterface QueryState {\n readonly filters: ReadonlyArray<[column: string, expression: string]>\n readonly orderings: readonly string[]\n readonly selection: readonly string[] | null\n readonly limitValue: number | null\n readonly offsetValue: number | null\n}\n\nconst EMPTY_STATE: QueryState = {\n filters: [],\n orderings: [],\n selection: null,\n limitValue: null,\n offsetValue: null,\n}\n\n/**\n * `Row` is the TABLE's row (filters and order always accept every column);\n * `Result` is what rows() yields — narrowed by select(), re-widenable by a\n * later select() since keys always come from Row.\n */\nexport class TableQuery<Row extends Record<string, unknown>, Result = Row> {\n constructor(\n private readonly transport: OrmTransport,\n private readonly namespace: string,\n private readonly table: string,\n private readonly state: QueryState = EMPTY_STATE,\n ) {}\n\n private with(patch: Partial<QueryState>): TableQuery<Row, Result> {\n return new TableQuery(this.transport, this.namespace, this.table, { ...this.state, ...patch })\n }\n\n select<K extends keyof Row & string>(...columns: [K, ...K[]]): TableQuery<Row, Pick<Row, K>> {\n return new TableQuery<Row, Pick<Row, K>>(this.transport, this.namespace, this.table, {\n ...this.state,\n selection: columns,\n })\n }\n\n eq<K extends keyof Row & string>(column: K, value: FilterValue): TableQuery<Row, Result> {\n return this.filter(column, 'eq', value)\n }\n\n ne<K extends keyof Row & string>(column: K, value: FilterValue): TableQuery<Row, Result> {\n return this.filter(column, 'ne', value)\n }\n\n gt<K extends keyof Row & string>(column: K, value: FilterValue): TableQuery<Row, Result> {\n return this.filter(column, 'gt', value)\n }\n\n gte<K extends keyof Row & string>(column: K, value: FilterValue): TableQuery<Row, Result> {\n return this.filter(column, 'gte', value)\n }\n\n lt<K extends keyof Row & string>(column: K, value: FilterValue): TableQuery<Row, Result> {\n return this.filter(column, 'lt', value)\n }\n\n lte<K extends keyof Row & string>(column: K, value: FilterValue): TableQuery<Row, Result> {\n return this.filter(column, 'lte', value)\n }\n\n like<K extends keyof Row & string>(column: K, pattern: string): TableQuery<Row, Result> {\n return this.filter(column, 'like', pattern)\n }\n\n ilike<K extends keyof Row & string>(column: K, pattern: string): TableQuery<Row, Result> {\n return this.filter(column, 'ilike', pattern)\n }\n\n in<K extends keyof Row & string>(column: K, values: readonly FilterValue[]): TableQuery<Row, Result> {\n // An empty list is representable — `in.()` matches nothing, the same\n // semantics the server executes. Blank entries are legitimate values in\n // multi-entry lists (`in.(a,)` carries ['a','']), but a LONE '' renders\n // as `in.()` and silently becomes the empty list — unrepresentable.\n // Beyond that only characters the grammar can't escape, and whitespace\n // padding the server-side split would silently swallow, are refused.\n const rendered = values.map((value) => {\n const text = String(value)\n if (IN_LIST_UNREPRESENTABLE.test(text) || text !== text.trim()) {\n throw new OrmClientError(`in() value '${text}' cannot be represented — the list grammar has no escaping`)\n }\n return text\n })\n if (rendered.length === 1 && rendered[0] === '') {\n throw new OrmClientError(\"in() with a lone '' cannot be represented — it would read as the empty list\")\n }\n return this.with({ filters: [...this.state.filters, [column, `in.(${rendered.join(',')})`]] })\n }\n\n is<K extends keyof Row & string>(column: K, value: null | boolean): TableQuery<Row, Result> {\n return this.with({\n filters: [...this.state.filters, [column, `is.${value === null ? 'null' : value}`]],\n })\n }\n\n order<K extends keyof Row & string>(column: K, direction: 'asc' | 'desc' = 'asc'): TableQuery<Row, Result> {\n return this.with({ orderings: [...this.state.orderings, `${column}.${direction}`] })\n }\n\n limit(value: number): TableQuery<Row, Result> {\n return this.with({ limitValue: value })\n }\n\n offset(value: number): TableQuery<Row, Result> {\n return this.with({ offsetValue: value })\n }\n\n /** The exact query string sent to the Data API — exposed for debugging and the grammar contract test. */\n searchParams(): URLSearchParams {\n const params = new URLSearchParams()\n for (const [column, expression] of this.state.filters) params.append(column, expression)\n if (this.state.orderings.length > 0) params.set('order', this.state.orderings.join(','))\n if (this.state.selection) params.set('select', this.state.selection.join(','))\n if (this.state.limitValue !== null) params.set('limit', String(this.state.limitValue))\n if (this.state.offsetValue !== null) params.set('offset', String(this.state.offsetValue))\n return params\n }\n\n async rows(): Promise<Result[]> {\n const payload = await this.transport.get(\n `/api/data/${this.namespace}/${this.table}`,\n this.searchParams(),\n )\n if (!payload || typeof payload !== 'object' || !Array.isArray((payload as { rows?: unknown }).rows)) {\n throw new OrmClientError('unexpected Data API response shape — missing rows array')\n }\n return (payload as { rows: Result[] }).rows\n }\n\n private filter(column: string, op: string, value: FilterValue): TableQuery<Row, Result> {\n return this.with({ filters: [...this.state.filters, [column, `${op}.${String(value)}`]] })\n }\n}\n","import { OrmClientError, createFetchTransport, type OrmTransport } from './transport.js'\nimport { TableQuery } from './query-builder.js'\n\n/** Bindable scalar for `query()` placeholders ($1, $2, …). */\nexport type QueryParam = string | number | boolean | null\n\n/** Shape of one action in the generated `<Ns>Actions` map — params in, result out. */\nexport interface OrmActionShape {\n readonly params?: Record<string, unknown>\n readonly result: unknown\n}\n\n/** The generated `<Ns>Actions` map: action name → its param and result shapes. */\nexport type OrmActions = Record<string, OrmActionShape>\n\n/**\n * `action()`'s argument list: `params` is required when the action declares a\n * required field, optional otherwise. The empty-object type is assignable to\n * the params shape only when every field is optional, so that assignability is\n * the discriminant.\n */\ntype ActionArgs<Action extends OrmActionShape> =\n Record<string, never> extends NonNullable<Action['params']>\n ? [params?: NonNullable<Action['params']>]\n : [params: NonNullable<Action['params']>]\n\n/**\n * Typed read client for one namespace's Data API. `Tables` comes from\n * `types.gen.ts` (wire types the apply refreshes from the YAML definitions),\n * so column names and row shapes are checked at compile time; the server\n * re-validates everything and Postgres RLS decides row visibility.\n *\n * ```ts\n * const client = createOrmClient<LendingTables>({ namespace: 'lending', baseUrl, getToken })\n * const rows = await client.table('loans').select('id', 'balance').gte('balance', 1000).rows()\n * const top = await client.query<{ officer: string; total: string }>(\n * 'select officer, sum(balance) as total from loans group by officer order by total desc limit $1',\n * [5],\n * )\n * ```\n */\nexport interface OrmClient<\n Tables extends Record<string, Record<string, unknown>>,\n Actions extends OrmActions = OrmActions,\n> {\n table<Name extends keyof Tables & string>(name: Name): TableQuery<Tables[Name]>\n /**\n * Raw read-only SQL for everything the filter grammar can't say —\n * aggregates, joins, OR, expressions, CTEs. The same sandbox as every\n * read: the namespace consumer role (grants stop at the namespace schema),\n * Postgres RLS over the caller's claims, and a READ ONLY transaction —\n * Postgres itself rejects writes, which stay on declared actions. Values\n * bind as $1, $2, … placeholders; never interpolate them into the string.\n */\n query<Row = Record<string, unknown>>(sql: string, params?: readonly QueryParam[]): Promise<Row[]>\n /**\n * Invoke a declared action — the only write path the Data API exposes. The\n * params and result are typed from the namespace's `<Ns>Actions`: an action\n * with `returns:` resolves to `{ rows: Row[] }`, a count action to\n * `{ rowsAffected: number }`.\n */\n action<Name extends keyof Actions & string>(\n name: Name,\n ...args: ActionArgs<Actions[Name]>\n ): Promise<Actions[Name]['result']>\n}\n\nexport function createOrmClient<\n Tables extends Record<string, Record<string, unknown>>,\n Actions extends OrmActions = OrmActions,\n>({\n namespace,\n baseUrl,\n getToken,\n fetchImpl,\n transport,\n dataToken,\n}: {\n namespace: string\n baseUrl?: string\n /** App-user / session bearer (CLI, browser). Mutually exclusive with dataToken. */\n getToken?: () => Promise<string | null> | string | null\n /**\n * Delegated function data token — the inbound `ORM_DATA_TOKEN_HEADER` value a\n * managed function receives. Pass it and the client authenticates as the\n * invoking user, scoped to the function's declared refs; no manual transport.\n */\n dataToken?: string | (() => Promise<string | null> | string | null)\n fetchImpl?: typeof globalThis.fetch\n /** Injected transport (tests, bridges); overrides baseUrl/getToken/dataToken. */\n transport?: OrmTransport\n}): OrmClient<Tables, Actions> {\n const resolved =\n transport ??\n createFetchTransport({\n baseUrl: requireOption(baseUrl, 'baseUrl'),\n ...(dataToken !== undefined\n ? { getDataToken: typeof dataToken === 'function' ? dataToken : () => dataToken }\n : { getToken: requireOption(getToken, 'getToken') }),\n fetchImpl,\n })\n\n return buildOrmClient<Tables, Actions>(resolved, namespace)\n}\n\n/**\n * The ESCALATION path — use only deliberately. A managed function receives a\n * separate *service* data token on `ORM_SERVICE_DATA_TOKEN_HEADER`; pass that\n * value here and the client authenticates as the function's SERVICE IDENTITY,\n * not the invoking user. Reads run with the service identity's grants (clamped\n * to the function's declared refs), NOT the caller's — so the caller's own row\n * visibility does not apply. The invoking user is still recorded for audit.\n *\n * Default to `createOrmClient` (acts as the invoking user, full per-user RLS).\n * Reach for this only when the function must intentionally act beyond the\n * caller — e.g. enforcing per-user rules in code over a coarse upstream that\n * has no granular permissions of its own. The distinct name is the guardrail:\n * choosing the service identity is always explicit and greppable.\n *\n * ```ts\n * const client = createServiceOrmClient<LendingTables>({\n * namespace: 'lending',\n * baseUrl,\n * serviceDataToken: req.headers['x-sequence-data-token-service'],\n * })\n * ```\n */\nexport function createServiceOrmClient<\n Tables extends Record<string, Record<string, unknown>>,\n Actions extends OrmActions = OrmActions,\n>({\n namespace,\n baseUrl,\n serviceDataToken,\n fetchImpl,\n transport,\n}: {\n namespace: string\n baseUrl?: string\n /** The inbound `ORM_SERVICE_DATA_TOKEN_HEADER` value — the function's service token. */\n serviceDataToken?: string | (() => Promise<string | null> | string | null)\n fetchImpl?: typeof globalThis.fetch\n /** Injected transport (tests, bridges); overrides baseUrl/serviceDataToken. */\n transport?: OrmTransport\n}): OrmClient<Tables, Actions> {\n const resolved =\n transport ??\n createFetchTransport({\n baseUrl: requireOption(baseUrl, 'baseUrl'),\n getDataToken:\n typeof serviceDataToken === 'function'\n ? serviceDataToken\n : () => requireOption(serviceDataToken, 'serviceDataToken'),\n fetchImpl,\n })\n\n return buildOrmClient<Tables, Actions>(resolved, namespace)\n}\n\n/** Shared client surface over a resolved transport — the token/identity choice is made by the factory above. */\nfunction buildOrmClient<\n Tables extends Record<string, Record<string, unknown>>,\n Actions extends OrmActions = OrmActions,\n>(resolved: OrmTransport, namespace: string): OrmClient<Tables, Actions> {\n return {\n table(name) {\n return new TableQuery(resolved, namespace, name)\n },\n async query(sql, params) {\n const payload = await resolved.post(`/api/data/${namespace}/query`, {\n sql,\n ...(params ? { params: [...params] } : {}),\n })\n if (!payload || typeof payload !== 'object' || !Array.isArray((payload as { rows?: unknown }).rows)) {\n throw new OrmClientError('unexpected Data API response shape — missing rows array')\n }\n return (payload as { rows: never[] }).rows\n },\n async action(name, ...args) {\n const payload = await resolved.post(`/api/data/${namespace}/actions/${name}`, args[0] ?? {})\n if (payload && typeof payload === 'object') {\n // A returns action responds with `rows`; a count action with\n // `rows_affected` (mapped to `rowsAffected` for the client).\n if (Array.isArray((payload as { rows?: unknown }).rows)) {\n return { rows: (payload as { rows: unknown[] }).rows } as Awaited<ReturnType<OrmClient<Tables, Actions>['action']>>\n }\n if ('rows_affected' in payload) {\n const rowsAffected = Number((payload as { rows_affected: unknown }).rows_affected)\n if (!Number.isNaN(rowsAffected)) {\n return { rowsAffected } as Awaited<ReturnType<OrmClient<Tables, Actions>['action']>>\n }\n }\n }\n throw new OrmClientError('unexpected action response shape — expected rows or rows_affected')\n },\n }\n}\n\nfunction requireOption<T>(value: T | undefined, name: string): T {\n if (value === undefined) throw new Error(`createOrmClient requires ${name} when no transport is injected`)\n return value\n}\n\nexport { TableQuery, type FilterValue, type ReadResponse } from './query-builder.js'\nexport { createFetchTransport, OrmClientError, type OrmTransport } from './transport.js'\n"]}
|