@somewhere-tech/cli 0.31.23 → 0.32.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.
@@ -0,0 +1,1571 @@
1
+ // Generated by scripts/vendor-declared-data.mjs; edit the platform source.
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // worker/containers/compile/declared-data-contract.cjs
34
+ var require_declared_data_contract = __commonJS({
35
+ "worker/containers/compile/declared-data-contract.cjs"(exports2, module2) {
36
+ "use strict";
37
+ var identifier = /^[a-z_][a-z0-9_]*$/;
38
+ var types = /* @__PURE__ */ new Set(["integer", "number", "text", "boolean", "timestamp", "json", "blob"]);
39
+ function fail(detail) {
40
+ throw new Error(`Invalid declared data contract: ${detail}`);
41
+ }
42
+ function record(value, name) {
43
+ if (!value || typeof value !== "object" || Array.isArray(value)) fail(name);
44
+ }
45
+ function fields(value, columns, name, empty = false) {
46
+ if (!Array.isArray(value) || !empty && !value.length) fail(name);
47
+ const result = [...value];
48
+ if (result.some((field) => typeof field !== "string" || !columns.has(field)) || new Set(result).size !== result.length) fail(name);
49
+ return result.sort();
50
+ }
51
+ function canonicalize2(schema, intents, scopes) {
52
+ record(schema, "schema");
53
+ record(intents, "intents");
54
+ record(scopes, "scopes");
55
+ const tables = [];
56
+ const membershipTables = new Set(Object.values(schema).filter((table) => table && table.member).map((table) => table.member.m));
57
+ for (const name of Object.keys(schema).sort()) {
58
+ const table = schema[name];
59
+ record(table, `table ${name}`);
60
+ if (table.client === void 0) {
61
+ if (table.clientPrimaryKey !== void 0) fail(`${name} has a primary key without client permissions`);
62
+ continue;
63
+ }
64
+ if (!identifier.test(name) || name.startsWith("_") || name.startsWith("sqlite_")) fail(`table ${name}`);
65
+ record(table.client, `${name}.client`);
66
+ if (!Array.isArray(table.columns) || !table.columns.length) fail(`${name}.columns`);
67
+ const seen = /* @__PURE__ */ new Set();
68
+ const columns = table.columns.map((column) => {
69
+ record(column, `${name}.column`);
70
+ if (typeof column.n !== "string" || !identifier.test(column.n) || seen.has(column.n) || !types.has(column.t)) fail(`${name}.column`);
71
+ if (column.nul !== void 0 && column.nul !== 1 || column.d !== void 0 && column.d !== 1) fail(`${name}.${column.n} flags`);
72
+ seen.add(column.n);
73
+ return { n: column.n, t: column.t, ...column.nul ? { nul: 1 } : {}, ...column.d ? { d: 1 } : {} };
74
+ }).sort((a, b) => a.n < b.n ? -1 : a.n > b.n ? 1 : 0);
75
+ const pk = table.clientPrimaryKey;
76
+ if (!seen.has(pk) || !["integer", "text"].includes(columns.find((column) => column.n === pk).t)) fail(`${name} primary key`);
77
+ const input = table.client;
78
+ if (Object.keys(input).some((key) => !["read", "publicRead", "create", "update", "delete", "identity"].includes(key))) fail(`${name} permissions`);
79
+ const read = typeof input.read === "boolean" ? input.read : fields(input.read, seen, `${name}.read`);
80
+ if (Array.isArray(read) && !read.includes(pk)) fail(`${name}.read must include the primary key`);
81
+ if (typeof input.publicRead !== "boolean" || typeof input.delete !== "boolean" || !["authenticated", "visitor"].includes(input.identity)) fail(`${name} permissions`);
82
+ const create = input.create === null ? null : fields(input.create, seen, `${name}.create`, true);
83
+ const update = input.update === null ? null : fields(input.update, seen, `${name}.update`);
84
+ if (membershipTables.has(name) && (create !== null || update !== null || input.delete)) fail(`${name} membership authority writes`);
85
+ if ([...create || [], ...update || []].some((field) => columns.find((column) => column.n === field).t === "blob")) fail(`${name} blob browser writes require a server function`);
86
+ const intent = intents[name];
87
+ if (!["scoped", "shared", "member"].includes(intent)) fail(`${name} intent`);
88
+ const owner = intent === "scoped" ? scopes[name] : null;
89
+ if (intent === "scoped" && (typeof owner !== "string" || !identifier.test(owner))) fail(`${name} owner`);
90
+ if (table.visitors !== void 0 && table.visitors !== true) fail(`${name} visitors`);
91
+ const visitors = table.visitors === true;
92
+ if (visitors && intent !== "scoped") fail(`${name} visitors require ownership`);
93
+ if (input.identity !== (visitors ? "visitor" : "authenticated")) fail(`${name} identity must match visitor declaration`);
94
+ const author = intent === "shared" ? table.author : null;
95
+ if (intent === "shared" && (author !== "_sw_author_id" || !columns.some((column) => column.n === author && column.t === "text" && column.nul === 1))) fail(`${name} author`);
96
+ if (Array.isArray(read) && read.includes(author)) fail(`${name} internal author projection`);
97
+ if (input.identity === "visitor" && intent !== "scoped") fail(`${name} visitor identity`);
98
+ if (input.publicRead && !Array.isArray(read)) fail(`${name} public read`);
99
+ if ([...create || [], ...update || []].some((field) => field === pk || field === owner || field === author)) fail(`${name} identity writes`);
100
+ let member = null;
101
+ if (intent === "member") {
102
+ record(table.member, `${name}.member`);
103
+ const { g, m, u, mg } = table.member;
104
+ if (typeof m !== "string" || typeof u !== "string" || !identifier.test(m) || !identifier.test(u) || !Array.isArray(mg) || mg.some((field) => typeof field !== "string" || !identifier.test(field))) fail(`${name}.member`);
105
+ fields(g, seen, `${name}.member.g`);
106
+ if (g.length !== mg.length || new Set(mg).size !== mg.length) fail(`${name}.member`);
107
+ if ((update || []).some((field) => g.includes(field))) fail(`${name} membership writes`);
108
+ member = { g: [...g], m, u, mg: [...mg] };
109
+ }
110
+ tables.push({ name, columns, client: { identity: input.identity, read, publicRead: input.publicRead, create, update, delete: input.delete }, primaryKey: pk, intent, owner, author, visitors, member });
111
+ }
112
+ return JSON.stringify({ version: 1, tables });
113
+ }
114
+ function columnType(column) {
115
+ const type = { integer: "number | string", number: "number", text: "string", timestamp: "string", boolean: "boolean", json: "Json", blob: "number[]" }[column.t];
116
+ return column.nul ? `${type} | null` : type;
117
+ }
118
+ function shape(columns, names, mode) {
119
+ const values = names.map((name) => {
120
+ const column = columns.find((candidate) => candidate.n === name);
121
+ const optional = mode === "update" || mode === "create" && (column.d || column.nul);
122
+ return `${JSON.stringify(name)}${optional ? "?" : ""}: ${columnType(column)}`;
123
+ });
124
+ return values.length ? `{ ${values.join("; ")} }` : "Record<string, never>";
125
+ }
126
+ function describeDataClient2(authority, contract_digest) {
127
+ const canonical = authority ? canonicalize2(authority.schema, authority.intents, authority.scopes) : canonicalize2({}, {}, {});
128
+ const { tables } = JSON.parse(canonical);
129
+ const declarations = [];
130
+ const runtimeTables = [];
131
+ for (const table of tables) {
132
+ const { name, columns, client, primaryKey } = table;
133
+ const readable = client.read === true ? columns.filter((column) => column.n !== table.author).map((column) => column.n) : client.read || [];
134
+ const row = shape(columns, readable, "read");
135
+ const id = columnType(columns.find((column) => column.n === primaryKey));
136
+ const operations = [];
137
+ const methods = [];
138
+ if (client.read !== false) {
139
+ const equality = readable.filter((field) => !["json", "blob"].includes(columns.find((column) => column.n === field).t));
140
+ operations.push(`list(options?: { limit?: number; after?: ${id}; where?: ${shape(columns, equality, "update")} }): Promise<{ data: ${row}[]; next: ${id} | null; has_more: boolean }>`);
141
+ operations.push(`get(id: ${id}): Promise<{ data: ${row} | null }>`);
142
+ methods.push("list", "get");
143
+ }
144
+ const mutation = client.read === false ? "{ count: number; changes: number }" : `{ data: ${row} | null; count: number; changes: number }`;
145
+ if (client.create !== null) {
146
+ operations.push(`create(values: ${shape(columns, client.create, "create")}): Promise<${mutation}>`);
147
+ methods.push("create");
148
+ }
149
+ if (client.update !== null) {
150
+ operations.push(`update(id: ${id}, values: ${shape(columns, client.update, "update")}): Promise<${mutation}>`);
151
+ methods.push("update");
152
+ }
153
+ if (client.delete) {
154
+ operations.push(`delete(id: ${id}): Promise<${mutation}>`);
155
+ methods.push("delete");
156
+ }
157
+ declarations.push(`${JSON.stringify(name)}: { ${operations.join("; ")} }`);
158
+ runtimeTables.push([name, methods]);
159
+ }
160
+ const declaration = `declare module ${JSON.stringify("somewhere:data")} {
161
+ export type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
162
+ export class DataError extends Error { readonly status: number; readonly code: string | null; constructor(status: number, payload: unknown); }
163
+ export const data: { ${declarations.join(";\n ")} };
164
+ }
165
+ `;
166
+ const runtime = `const contract=${JSON.stringify(contract_digest)};
167
+ class DataError extends Error{constructor(status,payload){super(payload&&typeof payload.message==="string"?payload.message:payload&&typeof payload.error==="string"?payload.error:"Data operation failed");this.name="DataError";this.status=status;this.code=payload&&typeof payload.error==="string"?payload.error:null}}
168
+ const invoke=async(table,operation,input)=>{const response=await fetch("/__sw/data",{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({...input,contract,table,operation})});const payload=await response.json().catch(()=>{throw new DataError(response.status,{error:"INVALID_DATA_RESPONSE",message:"Data operation returned an invalid response"})});if(!response.ok)throw new DataError(response.status,payload);return payload};
169
+ const data=Object.freeze(Object.fromEntries(${JSON.stringify(runtimeTables)}.map(([table,operations])=>[table,Object.freeze(Object.fromEntries(operations.map(operation=>[operation,operation==="list"?(options={})=>invoke(table,operation,options):operation==="create"?values=>invoke(table,operation,{values}):operation==="update"?(id,values)=>invoke(table,operation,{id,values}):id=>invoke(table,operation,{id})])))])));
170
+ export{data,DataError};
171
+ `;
172
+ return { contract_digest, declaration, runtime, manifest: { version: 1, contract_digest, tables, declaration } };
173
+ }
174
+ module2.exports = { canonicalize: canonicalize2, describeDataClient: describeDataClient2 };
175
+ }
176
+ });
177
+
178
+ // worker/containers/compile/typed-data.cjs
179
+ var require_typed_data = __commonJS({
180
+ "worker/containers/compile/typed-data.cjs"(exports2, module2) {
181
+ "use strict";
182
+ var crypto2 = require("node:crypto");
183
+ var { canonicalize: canonicalize2, describeDataClient: describeDataClient2 } = require_declared_data_contract();
184
+ var MODULE_NAME = "somewhere:data";
185
+ var CLIENT_FILE = "__somewhere_data.d.ts";
186
+ var MANIFEST_PATH = "_internal/declared-data.json";
187
+ function generateDataClient2(authority) {
188
+ const canonical = authority ? canonicalize2(authority.schema, authority.intents, authority.scopes) : canonicalize2({}, {}, {});
189
+ return describeDataClient2(authority, crypto2.createHash("sha256").update(canonical).digest("hex"));
190
+ }
191
+ function virtualDataPlugin(analysis) {
192
+ return {
193
+ name: "somewhere-declared-data",
194
+ setup(build) {
195
+ build.onResolve({ filter: /^somewhere:data$/ }, () => ({ path: MODULE_NAME, namespace: "somewhere-declared-data" }));
196
+ build.onLoad({ filter: /.*/, namespace: "somewhere-declared-data" }, () => {
197
+ if (!analysis || !analysis.manifest.tables.length) return { errors: [{ text: "somewhere:data requires db/schema.ts with explicit client permissions." }] };
198
+ return { contents: analysis.runtime, loader: "js" };
199
+ });
200
+ }
201
+ };
202
+ }
203
+ module2.exports = { MODULE_NAME, CLIENT_FILE, MANIFEST_PATH, generateDataClient: generateDataClient2, virtualDataPlugin };
204
+ }
205
+ });
206
+
207
+ // worker/containers/compile/schema-types.cjs
208
+ var require_schema_types = __commonJS({
209
+ "worker/containers/compile/schema-types.cjs"(exports2, module2) {
210
+ "use strict";
211
+ var types = `declare namespace SomewhereSchemaDeclaration {
212
+ interface Column { readonly __somewhereColumn: unique symbol }
213
+ interface Table { readonly __somewhereTable: unique symbol }
214
+ interface TableMarker { readonly __somewhereTableMarker: unique symbol }
215
+ interface ColumnMarker { readonly __somewhereColumnMarker: unique symbol }
216
+ interface Relation { readonly __somewhereRelation: unique symbol }
217
+ interface Scope { readonly __somewhereScope: unique symbol }
218
+ interface OwnerScope extends Scope { readonly __somewhereOwner: unique symbol }
219
+ interface ColumnOptions<Default> {
220
+ nullable?: boolean; default?: Default | null; unique?: boolean;
221
+ references?: string; onDelete?: 'cascade' | 'restrict'; renamedFrom?: string;
222
+ }
223
+ interface ClientPermissions<Field extends string> {
224
+ identity?: 'authenticated' | 'visitor'; read?: boolean | Field[]; publicRead?: boolean;
225
+ create?: Field[] | false | null; update?: Field[] | false | null; delete?: boolean;
226
+ }
227
+ interface TableOptions<Field extends string> {
228
+ scope?: Scope; client?: ClientPermissions<Field>; indexes?: string[][];
229
+ unique?: string[][]; relations?: Record<string, Relation>;
230
+ }
231
+ }
232
+ `;
233
+ var signatures = `function schema(tables: Record<string, SomewhereSchemaDeclaration.Table | SomewhereSchemaDeclaration.TableMarker>, options?: { search: SomewhereSchemaDeclaration.OwnerScope }): unknown;
234
+ function table<Columns extends Record<string, SomewhereSchemaDeclaration.Column | SomewhereSchemaDeclaration.ColumnMarker>>(columns: Columns, options?: SomewhereSchemaDeclaration.TableOptions<Extract<keyof Columns, string>>): SomewhereSchemaDeclaration.Table;
235
+ function id(options?: { uuid?: boolean }): SomewhereSchemaDeclaration.Column;
236
+ function text(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
237
+ function number(options?: SomewhereSchemaDeclaration.ColumnOptions<number>): SomewhereSchemaDeclaration.Column;
238
+ function integer(options?: SomewhereSchemaDeclaration.ColumnOptions<number>): SomewhereSchemaDeclaration.Column;
239
+ function boolean(options?: SomewhereSchemaDeclaration.ColumnOptions<boolean>): SomewhereSchemaDeclaration.Column;
240
+ function timestamp(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
241
+ function json(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
242
+ function blob(options?: SomewhereSchemaDeclaration.ColumnOptions<never>): SomewhereSchemaDeclaration.Column;
243
+ function owner(options?: { column?: string; visitors?: boolean }): SomewhereSchemaDeclaration.OwnerScope;
244
+ function shared(): SomewhereSchemaDeclaration.Scope;
245
+ function serverOnly(): SomewhereSchemaDeclaration.Scope;
246
+ function member(options: { group: string | string[]; membership: string; member_user: string; member_group: string | string[] }): SomewhereSchemaDeclaration.Scope;
247
+ function hasMany(table: string, foreignKey: string): SomewhereSchemaDeclaration.Relation;
248
+ function removed(): SomewhereSchemaDeclaration.ColumnMarker;
249
+ function removedTable(): SomewhereSchemaDeclaration.TableMarker;
250
+ function exported(): SomewhereSchemaDeclaration.TableMarker;
251
+ `;
252
+ var SCHEMA_DECLARATION2 = types + signatures.replace(/^function /gm, "declare function ") + `declare module 'somewhere/db' {
253
+ ${signatures.replace(/^function /gm, " export function ")}}
254
+ `;
255
+ module2.exports = { SCHEMA_DECLARATION: SCHEMA_DECLARATION2 };
256
+ }
257
+ });
258
+
259
+ // worker/containers/compile/runtime-types.cjs
260
+ var require_runtime_types = __commonJS({
261
+ "worker/containers/compile/runtime-types.cjs"(exports2, module2) {
262
+ "use strict";
263
+ var RUNTIME_CONTEXT_DECLARATION2 = `
264
+ type __SomewhereJsonPrimitive = string | number | boolean | null;
265
+ type __SomewhereJson = __SomewhereJsonPrimitive | { [key: string]: __SomewhereJson } | __SomewhereJson[];
266
+ type SomewhereDbScalar = string | number | boolean | null;
267
+ type __SomewhereDbOperators = {
268
+ eq: SomewhereDbScalar; ne: SomewhereDbScalar;
269
+ lt: SomewhereDbScalar; lte: SomewhereDbScalar;
270
+ gt: SomewhereDbScalar; gte: SomewhereDbScalar;
271
+ like: SomewhereDbScalar; in: readonly SomewhereDbScalar[];
272
+ not: null; contains: string; startsWith: string; endsWith: string;
273
+ };
274
+ type __SomewhereDbOperator = {
275
+ [K in keyof __SomewhereDbOperators]: Pick<__SomewhereDbOperators, K>
276
+ & Partial<Record<Exclude<keyof __SomewhereDbOperators, K>, never>>
277
+ }[keyof __SomewhereDbOperators];
278
+ type SomewhereDbCondition = SomewhereDbScalar | __SomewhereDbOperator;
279
+ type SomewhereDbWhere = Readonly<Record<string, SomewhereDbCondition | readonly SomewhereDbCondition[]>>;
280
+ type SomewhereDbOrder = string | readonly [string, ('asc' | 'desc')?]
281
+ | readonly (readonly [string, ('asc' | 'desc')?])[];
282
+ interface SomewhereDbReadOptions {
283
+ where?: SomewhereDbWhere | null;
284
+ order?: SomewhereDbOrder | null;
285
+ limit?: number | null;
286
+ offset?: number | null;
287
+ columns?: readonly string[] | null;
288
+ include?: readonly string[] | null;
289
+ has?: Readonly<Record<string, SomewhereDbWhere>> | null;
290
+ }
291
+ interface SomewhereDbCountOptions { where?: SomewhereDbWhere | null }
292
+ interface SomewhereDbInsertOptions { onConflict?: 'ignore' | 'update' | null }
293
+ type SomewhereDbValues = Readonly<Record<string, __SomewhereJson>>;
294
+ interface SomewhereDbUpdate {
295
+ set: SomewhereDbValues;
296
+ where?: SomewhereDbWhere | null;
297
+ }
298
+ interface SomewhereDbRemove { where?: SomewhereDbWhere | null }
299
+ type SomewhereDbWriteIntent =
300
+ | { op: 'insert'; table: string; values: SomewhereDbValues; options?: SomewhereDbInsertOptions | null }
301
+ | { op: 'update'; table: string; set: SomewhereDbValues; where?: SomewhereDbWhere | null }
302
+ | { op: 'remove'; table: string; where?: SomewhereDbWhere | null };
303
+ interface SomewhereDbResult {
304
+ // Composed results preserve the database representation. They do not use
305
+ // the browser client's boolean/JSON normalization or its field projection.
306
+ data: Record<string, unknown>[];
307
+ error: null;
308
+ count: number;
309
+ changes: number;
310
+ last_row_id: number | string | null;
311
+ live_delivery?: { delivery: 'invalidated' }
312
+ | { delivery: 'resync_required'; reason: string };
313
+ }
314
+ interface SomewhereServerDb {
315
+ from(table: string, options?: SomewhereDbReadOptions | null): Promise<SomewhereDbResult>;
316
+ count(table: string, options?: SomewhereDbCountOptions | null): Promise<{ data: number; error: null }>;
317
+ insert(table: string, values: SomewhereDbValues, options?: SomewhereDbInsertOptions | null): Promise<SomewhereDbResult>;
318
+ update(table: string, spec: SomewhereDbUpdate): Promise<SomewhereDbResult>;
319
+ remove(table: string, spec?: SomewhereDbRemove | null): Promise<SomewhereDbResult>;
320
+ // Closed atomic batch, 1-100 intents checked before execution. No callback,
321
+ // reads, SQL, or caller-supplied authority. A lost acknowledgement can have
322
+ // an unknown outcome; a rejection never promises rollback or safe replay.
323
+ tx(intents: readonly SomewhereDbWriteIntent[]): Promise<SomewhereDbResult[]>;
324
+ }
325
+ interface SomewhereCallerDb extends Omit<SomewhereServerDb, 'from' | 'count'> {
326
+ from(table: string, options?: (SomewhereDbReadOptions & { asServer?: true }) | null): Promise<SomewhereDbResult>;
327
+ count(table: string, options?: (SomewhereDbCountOptions & { asServer?: true }) | null): Promise<{ data: number; error: null }>;
328
+ delete(table: string, spec?: SomewhereDbRemove | null): Promise<SomewhereDbResult>;
329
+ readonly server: SomewhereServerDb;
330
+ }
331
+ // Deliberately describes the composed database subset. Other sw namespaces
332
+ // are not inferred from code or copied from the unrelated developer SDK.
333
+ interface SomewhereRuntimeContext { readonly db: SomewhereCallerDb }
334
+ interface __SomewhereTypedRequest<Input> extends Request { json(): Promise<Input> }
335
+ type ServerFunction<Contract extends { input: unknown; output: unknown }> =
336
+ (req: __SomewhereTypedRequest<Contract["input"]>, sw: SomewhereRuntimeContext) =>
337
+ Contract["output"] | Promise<Contract["output"]>;
338
+ `;
339
+ module2.exports = { RUNTIME_CONTEXT_DECLARATION: RUNTIME_CONTEXT_DECLARATION2 };
340
+ }
341
+ });
342
+
343
+ // declared-data-vendor-entry.js
344
+ var declared_data_vendor_entry_exports = {};
345
+ __export(declared_data_vendor_entry_exports, {
346
+ RUNTIME_CONTEXT_DECLARATION: () => import_runtime_types.RUNTIME_CONTEXT_DECLARATION,
347
+ SCHEMA_DECLARATION: () => import_schema_types.SCHEMA_DECLARATION,
348
+ generateFromFiles: () => generateFromFiles
349
+ });
350
+ module.exports = __toCommonJS(declared_data_vendor_entry_exports);
351
+
352
+ // worker/src/utils/db-schema-deploy/schema-ownership.ts
353
+ var SHARED_AUTHOR_COLUMN = "_sw_author_id";
354
+
355
+ // worker/src/utils/db-schema-deploy/client-permissions.ts
356
+ function parseClientPermissions(value, columns, scope) {
357
+ const fail = (message) => ({ ok: false, message });
358
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
359
+ return fail("client must be an object declaring read, publicRead, create, update, delete, or identity.");
360
+ }
361
+ const input = value;
362
+ for (const key of Object.keys(input)) {
363
+ if (!["identity", "read", "publicRead", "create", "update", "delete"].includes(key)) {
364
+ return fail(`Unknown client permission "${key}".`);
365
+ }
366
+ }
367
+ if (!["owner", "member", "shared"].includes(scope.kind)) {
368
+ return fail("serverOnly() tables cannot declare client access. Use a server function for business logic.");
369
+ }
370
+ const identity = scope.kind === "owner" && scope.visitors === true ? "visitor" : "authenticated";
371
+ if (input.identity !== void 0 && input.identity !== "authenticated" && input.identity !== "visitor") {
372
+ return fail('client.identity must be "authenticated" or "visitor".');
373
+ }
374
+ if (input.identity !== void 0 && input.identity !== identity) {
375
+ return fail("client.identity must match the owner() declaration. Visitor access requires owner({ visitors: true }); owner() and other scopes require authenticated identity.");
376
+ }
377
+ let read = input.read ?? false;
378
+ const publicRead = input.publicRead ?? false;
379
+ if (typeof publicRead !== "boolean") return fail("client.publicRead must be true or false.");
380
+ if (read !== true && read !== false && !Array.isArray(read)) return fail("client.read must list readable fields, or be true or false.");
381
+ if (publicRead && (!Array.isArray(read) || read.length === 0)) {
382
+ return fail("client.publicRead requires an explicit non-empty client.read field list.");
383
+ }
384
+ const remove = input.delete ?? false;
385
+ if (typeof remove !== "boolean") return fail("client.delete must be true or false.");
386
+ const fields = new Map(columns.map((column) => [column.name, column]));
387
+ if (Array.isArray(read)) {
388
+ const names = [];
389
+ for (const entry of read) {
390
+ if (typeof entry !== "string" || !fields.has(entry.toLowerCase())) return fail("client.read must contain only declared column names.");
391
+ const name = entry.toLowerCase();
392
+ if (names.includes(name)) return fail(`client.read declares field "${entry}" twice.`);
393
+ names.push(name);
394
+ }
395
+ if (names.length === 0) return fail("client.read must name at least one field, or be false.");
396
+ read = names.sort();
397
+ }
398
+ const writes = { create: null, update: null };
399
+ for (const operation of ["create", "update"]) {
400
+ const declared = input[operation];
401
+ if (declared === void 0 || declared === false || declared === null) continue;
402
+ if (!Array.isArray(declared)) return fail(`client.${operation} must list the writable fields, or be false.`);
403
+ const allowed = [];
404
+ for (const entry of declared) {
405
+ if (typeof entry !== "string") return fail(`client.${operation} fields must be column names.`);
406
+ const name = entry.toLowerCase();
407
+ const column = fields.get(name);
408
+ if (!column) return fail(`client.${operation} names undeclared or platform-managed field "${entry}".`);
409
+ if (column.helper === "id" || scope.kind === "owner" && name === scope.column) {
410
+ return fail(`client.${operation} cannot write the platform-assigned identity field "${entry}".`);
411
+ }
412
+ if (column.helper === "blob") return fail(`client.${operation} cannot write binary field "${entry}". Use project files for uploads.`);
413
+ if (operation === "update" && scope.kind === "member" && scope.group?.includes(name)) {
414
+ return fail(`client.update cannot move a row between membership groups by changing "${entry}".`);
415
+ }
416
+ if (allowed.includes(name)) return fail(`client.${operation} declares field "${entry}" twice.`);
417
+ allowed.push(name);
418
+ }
419
+ if (operation === "update" && allowed.length === 0) return fail("client.update must name at least one writable field, or be false.");
420
+ writes[operation] = allowed.sort();
421
+ }
422
+ if (writes.create !== null) {
423
+ for (const column of columns) {
424
+ if (column.helper !== "id" && !column.nullable && !column.hasDefault && !column.autoNow && !writes.create.includes(column.name)) {
425
+ return fail(`client.create must include required field "${column.name}", or that field needs a declared default.`);
426
+ }
427
+ }
428
+ }
429
+ return { ok: true, permissions: { identity, read, publicRead, ...writes, delete: remove } };
430
+ }
431
+
432
+ // worker/src/utils/authority/scope-enforcement.ts
433
+ var SAFE_SCOPE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
434
+ function bakedTableSchemaFromDeclared(declaredJson) {
435
+ let shape;
436
+ try {
437
+ shape = JSON.parse(declaredJson);
438
+ } catch {
439
+ return null;
440
+ }
441
+ if (shape === null || typeof shape !== "object") return null;
442
+ const columns = shape.columns;
443
+ if (!Array.isArray(columns) || columns.length === 0) return null;
444
+ const out = [];
445
+ const permissionColumns = [];
446
+ const primaryKeys = [];
447
+ for (const c of columns) {
448
+ if (c === null || typeof c !== "object") return null;
449
+ const col = c;
450
+ if (typeof col.name !== "string" || !SAFE_SCOPE_IDENTIFIER.test(col.name) || col.name.toLowerCase() === SHARED_AUTHOR_COLUMN) return null;
451
+ if (typeof col.helper !== "string") return null;
452
+ let t;
453
+ let implicitDefault = false;
454
+ if (col.helper === "id") {
455
+ t = col.uuid === true ? "text" : "integer";
456
+ implicitDefault = true;
457
+ } else if (col.helper === "integer" || col.helper === "number" || col.helper === "text" || col.helper === "boolean" || col.helper === "timestamp" || col.helper === "json" || col.helper === "blob") {
458
+ t = col.helper;
459
+ } else {
460
+ return null;
461
+ }
462
+ const entry = { n: col.name.toLowerCase(), t };
463
+ if (col.nullable === true) entry.nul = 1;
464
+ if (implicitDefault || col.hasDefault === true || col.autoNow === true) entry.d = 1;
465
+ out.push(entry);
466
+ permissionColumns.push({ name: entry.n, helper: col.helper, nullable: col.nullable === true, hasDefault: col.hasDefault === true, autoNow: col.autoNow === true });
467
+ if (col.helper === "id") primaryKeys.push(entry.n);
468
+ }
469
+ const relations = bakedRelationsFromDeclared(shape.relations);
470
+ const scope = shape.scope;
471
+ let visitors;
472
+ if (scope !== null && typeof scope === "object" && Object.prototype.hasOwnProperty.call(scope, "visitors")) {
473
+ const declaredScope = scope;
474
+ if (declaredScope.kind !== "owner" || typeof declaredScope.visitors !== "boolean") return null;
475
+ if (declaredScope.visitors) visitors = true;
476
+ }
477
+ let browser = {};
478
+ if (Object.prototype.hasOwnProperty.call(shape, "client")) {
479
+ if (!scope || typeof scope !== "object" || primaryKeys.length !== 1) return null;
480
+ const sc = scope;
481
+ if (typeof sc.kind !== "string") return null;
482
+ const parsed = parseClientPermissions(shape.client, permissionColumns, {
483
+ kind: sc.kind,
484
+ ...visitors ? { visitors: true } : {},
485
+ ...typeof sc.column === "string" ? { column: sc.column } : {},
486
+ ...Array.isArray(sc.group) && sc.group.every((value) => typeof value === "string") ? { group: sc.group } : {}
487
+ });
488
+ if (!parsed.ok) return null;
489
+ if (Array.isArray(parsed.permissions.read) && !parsed.permissions.read.includes(primaryKeys[0])) return null;
490
+ browser = { client: parsed.permissions, clientPrimaryKey: primaryKeys[0] };
491
+ }
492
+ if (scope !== null && typeof scope === "object" && scope.kind === "member") {
493
+ const member = bakedMemberFromDeclared(scope);
494
+ if (!member) return null;
495
+ return relations ? { columns: out, member, relations, ...browser } : { columns: out, member, ...browser };
496
+ }
497
+ const author = scope !== null && typeof scope === "object" && scope.kind === "shared" ? SHARED_AUTHOR_COLUMN : void 0;
498
+ if (author) out.push({ n: author, t: "text", nul: 1 });
499
+ return { columns: out, ...relations ? { relations } : {}, ...author ? { author } : {}, ...visitors ? { visitors } : {}, ...browser };
500
+ }
501
+ function bakedRelationsFromDeclared(value) {
502
+ if (!Array.isArray(value) || value.length === 0) return void 0;
503
+ const out = [];
504
+ const seen = /* @__PURE__ */ new Set();
505
+ for (const entry of value) {
506
+ if (entry === null || typeof entry !== "object") continue;
507
+ const rel = entry;
508
+ if (typeof rel.name !== "string" || !SAFE_SCOPE_IDENTIFIER.test(rel.name)) continue;
509
+ if (typeof rel.table !== "string" || !SAFE_SCOPE_IDENTIFIER.test(rel.table)) continue;
510
+ if (typeof rel.fk !== "string" || !SAFE_SCOPE_IDENTIFIER.test(rel.fk)) continue;
511
+ if (typeof rel.parentKey !== "string" || !SAFE_SCOPE_IDENTIFIER.test(rel.parentKey)) continue;
512
+ const name = rel.name.toLowerCase();
513
+ if (seen.has(name)) continue;
514
+ seen.add(name);
515
+ out.push({ name, table: rel.table.toLowerCase(), fk: rel.fk.toLowerCase(), parentKey: rel.parentKey.toLowerCase() });
516
+ }
517
+ return out.length > 0 ? out : void 0;
518
+ }
519
+ function bakedMemberFromDeclared(scope) {
520
+ const s = scope;
521
+ const cols = (v) => {
522
+ if (!Array.isArray(v) || v.length === 0) return null;
523
+ const out = [];
524
+ for (const c of v) {
525
+ if (typeof c !== "string" || !SAFE_SCOPE_IDENTIFIER.test(c)) return null;
526
+ out.push(c.toLowerCase());
527
+ }
528
+ return out;
529
+ };
530
+ const g = cols(s.group);
531
+ const mg = cols(s.memberGroup);
532
+ if (!g || !mg || g.length !== mg.length) return null;
533
+ if (typeof s.membership !== "string" || !SAFE_SCOPE_IDENTIFIER.test(s.membership)) return null;
534
+ if (typeof s.memberUser !== "string" || !SAFE_SCOPE_IDENTIFIER.test(s.memberUser)) return null;
535
+ return { g, m: s.membership.toLowerCase(), u: s.memberUser.toLowerCase(), mg };
536
+ }
537
+
538
+ // worker/src/utils/db-schema-deploy/extract-schema-ts.ts
539
+ var COLUMN_HELPERS = /* @__PURE__ */ new Set([
540
+ "id",
541
+ "text",
542
+ "number",
543
+ "integer",
544
+ "boolean",
545
+ "timestamp",
546
+ "json",
547
+ "blob"
548
+ ]);
549
+ var SCOPE_HELPERS = /* @__PURE__ */ new Set(["owner", "shared", "serverOnly", "member"]);
550
+ var TABLE_MARKER_HELPERS = /* @__PURE__ */ new Set(["removedTable", "exported"]);
551
+ var SAFE_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
552
+ var RESERVED_PREFIXES = ["app_", "sqlite_", "d1_", "__sw_"];
553
+ var MAX_TABLES = 100;
554
+ var MAX_COLUMNS = 100;
555
+ var MAX_INDEXES = 40;
556
+ var MAX_INDEX_COLUMNS = 8;
557
+ var MAX_NAME_LENGTH = 64;
558
+ var MAX_SOURCE_BYTES = 256 * 1024;
559
+ var DEFAULT_OWNER_COLUMN = "user_id";
560
+ var SchemaTsError = class extends Error {
561
+ };
562
+ function tokenize(source) {
563
+ const tokens = [];
564
+ let i = 0;
565
+ let line = 1;
566
+ const n = source.length;
567
+ while (i < n) {
568
+ const ch = source[i];
569
+ if (ch === "\n") {
570
+ line++;
571
+ i++;
572
+ continue;
573
+ }
574
+ if (ch === " " || ch === " " || ch === "\r") {
575
+ i++;
576
+ continue;
577
+ }
578
+ if (ch === "/" && source[i + 1] === "/") {
579
+ while (i < n && source[i] !== "\n") i++;
580
+ continue;
581
+ }
582
+ if (ch === "/" && source[i + 1] === "*") {
583
+ i += 2;
584
+ while (i < n && !(source[i] === "*" && source[i + 1] === "/")) {
585
+ if (source[i] === "\n") line++;
586
+ i++;
587
+ }
588
+ i += 2;
589
+ continue;
590
+ }
591
+ if (ch === "`") {
592
+ throw new SchemaTsError(`line ${line}: template literals are not allowed in db/schema.ts \u2014 use plain 'single-quoted' strings.`);
593
+ }
594
+ if (ch === "'" || ch === '"') {
595
+ const startLine = line;
596
+ let value = "";
597
+ i++;
598
+ let closed = false;
599
+ while (i < n) {
600
+ const c = source[i];
601
+ if (c === "\\") {
602
+ const next = source[i + 1];
603
+ if (next === "'" || next === '"' || next === "\\") {
604
+ value += next;
605
+ i += 2;
606
+ continue;
607
+ }
608
+ throw new SchemaTsError(`line ${line}: unsupported escape "\\${next ?? ""}" in a string \u2014 db/schema.ts strings allow only \\', \\" and \\\\.`);
609
+ }
610
+ if (c === "\n") {
611
+ throw new SchemaTsError(`line ${startLine}: unterminated string.`);
612
+ }
613
+ if (c === ch) {
614
+ closed = true;
615
+ i++;
616
+ break;
617
+ }
618
+ value += c;
619
+ i++;
620
+ }
621
+ if (!closed) throw new SchemaTsError(`line ${startLine}: unterminated string.`);
622
+ tokens.push({ kind: "string", value, line: startLine });
623
+ continue;
624
+ }
625
+ if (/[0-9]/.test(ch) || ch === "-" && /[0-9]/.test(source[i + 1] ?? "")) {
626
+ let j = i + (ch === "-" ? 1 : 0);
627
+ while (j < n && /[0-9]/.test(source[j])) j++;
628
+ if (source[j] === ".") {
629
+ j++;
630
+ while (j < n && /[0-9]/.test(source[j])) j++;
631
+ }
632
+ const text = source.slice(i, j);
633
+ const num = Number(text);
634
+ if (!Number.isFinite(num)) throw new SchemaTsError(`line ${line}: invalid number "${text}".`);
635
+ tokens.push({ kind: "number", value: text, line });
636
+ i = j;
637
+ continue;
638
+ }
639
+ if (/[A-Za-z_$]/.test(ch)) {
640
+ let j = i;
641
+ while (j < n && /[A-Za-z0-9_$]/.test(source[j])) j++;
642
+ tokens.push({ kind: "ident", value: source.slice(i, j), line });
643
+ i = j;
644
+ continue;
645
+ }
646
+ if ("(){}[]:,;.".includes(ch)) {
647
+ tokens.push({ kind: "punct", value: ch, line });
648
+ i++;
649
+ continue;
650
+ }
651
+ throw new SchemaTsError(`line ${line}: unexpected character "${ch}". db/schema.ts holds only the schema declaration \u2014 no expressions, operators, or type annotations.`);
652
+ }
653
+ tokens.push({ kind: "eof", value: "", line });
654
+ return tokens;
655
+ }
656
+ var Reader = class {
657
+ constructor(tokens) {
658
+ this.tokens = tokens;
659
+ }
660
+ pos = 0;
661
+ peek() {
662
+ return this.tokens[this.pos];
663
+ }
664
+ next() {
665
+ return this.tokens[this.pos++];
666
+ }
667
+ fail(msg) {
668
+ throw new SchemaTsError(`line ${this.peek().line}: ${msg}`);
669
+ }
670
+ expectPunct(p, context) {
671
+ const t = this.next();
672
+ if (t.kind !== "punct" || t.value !== p) {
673
+ throw new SchemaTsError(`line ${t.line}: expected "${p}" ${context}, found ${describeToken(t)}.`);
674
+ }
675
+ }
676
+ tryPunct(p) {
677
+ const t = this.peek();
678
+ if (t.kind === "punct" && t.value === p) {
679
+ this.pos++;
680
+ return true;
681
+ }
682
+ return false;
683
+ }
684
+ expectIdent(context) {
685
+ const t = this.next();
686
+ if (t.kind !== "ident") {
687
+ throw new SchemaTsError(`line ${t.line}: expected a name ${context}, found ${describeToken(t)}.`);
688
+ }
689
+ return t;
690
+ }
691
+ /** An object key: identifier or quoted string. */
692
+ readKey(context) {
693
+ const t = this.next();
694
+ if (t.kind === "ident" || t.kind === "string") return { name: t.value, line: t.line };
695
+ throw new SchemaTsError(`line ${t.line}: expected a name ${context}, found ${describeToken(t)}.`);
696
+ }
697
+ readLiteral(context) {
698
+ const t = this.next();
699
+ if (t.kind === "string") return t.value;
700
+ if (t.kind === "number") return Number(t.value);
701
+ if (t.kind === "ident" && t.value === "true") return true;
702
+ if (t.kind === "ident" && t.value === "false") return false;
703
+ if (t.kind === "ident" && t.value === "null") return null;
704
+ throw new SchemaTsError(
705
+ `line ${t.line}: expected a literal value (string, number, true/false, or null) ${context}, found ${describeToken(t)}. Computed values are not allowed \u2014 the declaration must be readable without running it.`
706
+ );
707
+ }
708
+ /** { key: literal, … } with trailing commas allowed. */
709
+ readLiteralObject(context) {
710
+ this.expectPunct("{", context);
711
+ const out = /* @__PURE__ */ new Map();
712
+ while (!this.tryPunct("}")) {
713
+ const key = this.readKey(`for an option key ${context}`);
714
+ this.expectPunct(":", `after "${key.name}" ${context}`);
715
+ const value = this.readLiteral(`for "${key.name}" ${context}`);
716
+ if (out.has(key.name)) throw new SchemaTsError(`line ${key.line}: option "${key.name}" appears twice ${context}.`);
717
+ out.set(key.name, { value, line: key.line });
718
+ if (!this.tryPunct(",")) {
719
+ this.expectPunct("}", context);
720
+ break;
721
+ }
722
+ }
723
+ return out;
724
+ }
725
+ atEof() {
726
+ return this.peek().kind === "eof";
727
+ }
728
+ };
729
+ function describeToken(t) {
730
+ if (t.kind === "eof") return "the end of the file";
731
+ if (t.kind === "string") return `the string '${t.value.slice(0, 40)}'`;
732
+ return `"${t.value}"`;
733
+ }
734
+ function readClientPermissions(r) {
735
+ r.expectPunct("{", "opening client permissions");
736
+ const out = /* @__PURE__ */ Object.create(null);
737
+ while (!r.tryPunct("}")) {
738
+ const key = r.readKey("for a client permission");
739
+ if (Object.prototype.hasOwnProperty.call(out, key.name)) r.fail(`client declares "${key.name}" twice.`);
740
+ r.expectPunct(":", `after client permission "${key.name}"`);
741
+ if (r.tryPunct("[")) {
742
+ const fields = [];
743
+ while (!r.tryPunct("]")) {
744
+ fields.push(r.readLiteral(`in client.${key.name}`));
745
+ if (!r.tryPunct(",")) {
746
+ r.expectPunct("]", `closing client.${key.name}`);
747
+ break;
748
+ }
749
+ }
750
+ out[key.name] = fields;
751
+ } else {
752
+ out[key.name] = r.readLiteral(`for client.${key.name}`);
753
+ }
754
+ if (!r.tryPunct(",")) {
755
+ r.expectPunct("}", "closing client permissions");
756
+ break;
757
+ }
758
+ }
759
+ return out;
760
+ }
761
+ function skipImport(r) {
762
+ r.next();
763
+ r.expectPunct("{", 'after "import"');
764
+ while (!r.tryPunct("}")) {
765
+ r.expectIdent("inside the import list");
766
+ if (!r.tryPunct(",")) {
767
+ r.expectPunct("}", "closing the import list");
768
+ break;
769
+ }
770
+ }
771
+ const from = r.expectIdent("after the import list");
772
+ if (from.value !== "from") throw new SchemaTsError(`line ${from.line}: expected "from" after the import list.`);
773
+ const src = r.next();
774
+ if (src.kind !== "string") throw new SchemaTsError(`line ${src.line}: expected a module name string after "from".`);
775
+ r.tryPunct(";");
776
+ }
777
+ function checkDeclaredName(kind, raw, line) {
778
+ if (raw.length > MAX_NAME_LENGTH) {
779
+ throw new SchemaTsError(`line ${line}: ${kind} name "${raw.slice(0, 40)}\u2026" is longer than ${MAX_NAME_LENGTH} characters.`);
780
+ }
781
+ if (!SAFE_IDENT.test(raw)) {
782
+ throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" is invalid. Use letters, digits, and underscores, starting with a letter or underscore.`);
783
+ }
784
+ const name = raw.toLowerCase();
785
+ if (name.startsWith("_") || RESERVED_PREFIXES.some((p) => name.startsWith(p))) {
786
+ throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" uses a reserved platform prefix and cannot be declared. Pick a different name \u2014 in particular, names starting with "app_" or "_" belong to the platform.`);
787
+ }
788
+ return name;
789
+ }
790
+ function readColumn(r, tableName, rawName, line) {
791
+ const helperTok = r.expectIdent(`for column "${tableName}"."${rawName}"`);
792
+ const helper = helperTok.value;
793
+ if (helper === "removed") {
794
+ r.expectPunct("(", 'after "removed"');
795
+ r.expectPunct(")", `closing removed() on "${tableName}"."${rawName}" \u2014 the removal marker takes no options`);
796
+ return { kind: "removed", name: checkDeclaredName("Column", rawName, line) };
797
+ }
798
+ if (TABLE_MARKER_HELPERS.has(helper)) {
799
+ throw new SchemaTsError(
800
+ `line ${helperTok.line}: "${tableName}"."${rawName}" uses ${helper}(), which marks a whole table. To remove this column, mark it ${rawName}: removed().`
801
+ );
802
+ }
803
+ if (!COLUMN_HELPERS.has(helper)) {
804
+ throw new SchemaTsError(
805
+ `line ${helperTok.line}: unknown column type "${helper}" on "${tableName}"."${rawName}". Use one of: id, text, number, integer, boolean, timestamp, json, blob.`
806
+ );
807
+ }
808
+ r.expectPunct("(", `after "${helper}"`);
809
+ let opts = /* @__PURE__ */ new Map();
810
+ if (!r.tryPunct(")")) {
811
+ opts = r.readLiteralObject(`in ${helper}() options for "${tableName}"."${rawName}"`);
812
+ r.tryPunct(",");
813
+ r.expectPunct(")", `closing ${helper}()`);
814
+ }
815
+ const name = checkDeclaredName("Column", rawName, line);
816
+ const col = {
817
+ name,
818
+ helper,
819
+ uuid: false,
820
+ nullable: false,
821
+ hasDefault: false,
822
+ default: null,
823
+ autoNow: false,
824
+ unique: false,
825
+ references: null,
826
+ onDelete: "restrict",
827
+ renamedFrom: null
828
+ };
829
+ const allowed = helper === "id" ? /* @__PURE__ */ new Set(["uuid"]) : /* @__PURE__ */ new Set(["nullable", "default", "unique", "references", "onDelete", "renamedFrom"]);
830
+ for (const [key, { value, line: optLine }] of opts) {
831
+ if (!allowed.has(key)) {
832
+ throw new SchemaTsError(
833
+ `line ${optLine}: ${helper}() on "${tableName}"."${rawName}" has an unknown option "${key}". Allowed: ${[...allowed].join(", ")}.`
834
+ );
835
+ }
836
+ switch (key) {
837
+ case "uuid":
838
+ if (typeof value !== "boolean") throw new SchemaTsError(`line ${optLine}: "uuid" must be true or false.`);
839
+ col.uuid = value;
840
+ break;
841
+ case "nullable":
842
+ if (typeof value !== "boolean") throw new SchemaTsError(`line ${optLine}: "nullable" must be true or false.`);
843
+ col.nullable = value;
844
+ break;
845
+ case "unique":
846
+ if (typeof value !== "boolean") throw new SchemaTsError(`line ${optLine}: "unique" must be true or false.`);
847
+ col.unique = value;
848
+ break;
849
+ case "default": {
850
+ const t = col.helper;
851
+ if (t === "timestamp" && value === "now") {
852
+ col.autoNow = true;
853
+ break;
854
+ }
855
+ const okType = value === null || t === "boolean" && typeof value === "boolean" || (t === "number" || t === "integer") && typeof value === "number" || (t === "text" || t === "timestamp" || t === "json") && typeof value === "string";
856
+ if (!okType) {
857
+ throw new SchemaTsError(
858
+ `line ${optLine}: the default for "${tableName}"."${rawName}" (${t}) must be ${t === "boolean" ? "true or false" : t === "number" || t === "integer" ? "a number" : t === "blob" ? "omitted \u2014 blob columns cannot declare a default" : "a string"} or null.`
859
+ );
860
+ }
861
+ if (t === "integer" && typeof value === "number" && !Number.isInteger(value)) {
862
+ throw new SchemaTsError(`line ${optLine}: the default for integer column "${tableName}"."${rawName}" must be a whole number.`);
863
+ }
864
+ col.hasDefault = true;
865
+ col.default = value;
866
+ break;
867
+ }
868
+ case "references":
869
+ if (typeof value !== "string" || !SAFE_IDENT.test(value)) {
870
+ throw new SchemaTsError(`line ${optLine}: "references" must be the name of a table declared in this file.`);
871
+ }
872
+ col.references = value.toLowerCase();
873
+ break;
874
+ case "onDelete":
875
+ if (value !== "cascade" && value !== "restrict") {
876
+ throw new SchemaTsError(`line ${optLine}: "onDelete" must be 'cascade' or 'restrict'.`);
877
+ }
878
+ col.onDelete = value;
879
+ break;
880
+ case "renamedFrom":
881
+ if (typeof value !== "string" || !SAFE_IDENT.test(value) || value.length > MAX_NAME_LENGTH) {
882
+ throw new SchemaTsError(`line ${optLine}: "renamedFrom" on "${tableName}"."${rawName}" must be the previous column name.`);
883
+ }
884
+ col.renamedFrom = value.toLowerCase();
885
+ break;
886
+ }
887
+ }
888
+ if (col.renamedFrom === col.name) {
889
+ throw new SchemaTsError(
890
+ `line ${line}: "${tableName}"."${rawName}" declares renamedFrom: '${col.renamedFrom}' \u2014 that is its own name. A rename needs the OLD name there.`
891
+ );
892
+ }
893
+ if (col.onDelete === "cascade" && !col.references) {
894
+ throw new SchemaTsError(`line ${line}: "onDelete" on "${tableName}"."${rawName}" needs "references" on the same column.`);
895
+ }
896
+ if (col.hasDefault && col.default === null) {
897
+ if (!col.nullable) {
898
+ throw new SchemaTsError(
899
+ `line ${line}: "${tableName}"."${rawName}" is required but declares { default: null } \u2014 a required column can never hold null. Give it a real default, or mark it { nullable: true }.`
900
+ );
901
+ }
902
+ col.hasDefault = false;
903
+ }
904
+ return { kind: "column", column: col };
905
+ }
906
+ function readColumnOrColumnList(r, ctx) {
907
+ if (r.tryPunct("[")) {
908
+ const out = [];
909
+ while (!r.tryPunct("]")) {
910
+ const t2 = r.next();
911
+ if (t2.kind !== "string") {
912
+ throw new SchemaTsError(`line ${t2.line}: ${ctx} must be quoted column names.`);
913
+ }
914
+ out.push(t2.value.toLowerCase());
915
+ if (!r.tryPunct(",")) {
916
+ r.expectPunct("]", `closing ${ctx}`);
917
+ break;
918
+ }
919
+ }
920
+ return out;
921
+ }
922
+ const t = r.next();
923
+ if (t.kind !== "string") {
924
+ throw new SchemaTsError(`line ${t.line}: ${ctx} must be a quoted column name, or a list of them for a composite key.`);
925
+ }
926
+ return [t.value.toLowerCase()];
927
+ }
928
+ function readSingleColumn(r, ctx) {
929
+ const t = r.next();
930
+ if (t.kind !== "string") {
931
+ throw new SchemaTsError(`line ${t.line}: ${ctx} must be a single quoted column name.`);
932
+ }
933
+ return t.value.toLowerCase();
934
+ }
935
+ function validateMemberColumns(names, optionLabel, tableName, line) {
936
+ const seen = /* @__PURE__ */ new Set();
937
+ for (const name of names) {
938
+ if (!SAFE_IDENT.test(name) || name.length > MAX_NAME_LENGTH) {
939
+ throw new SchemaTsError(`line ${line}: member(${optionLabel}) on table "${tableName}" names an invalid column "${name}".`);
940
+ }
941
+ if (seen.has(name)) {
942
+ throw new SchemaTsError(`line ${line}: member(${optionLabel}) on table "${tableName}" lists "${name}" twice.`);
943
+ }
944
+ seen.add(name);
945
+ }
946
+ }
947
+ function readMemberScope(r, tableName, scopeLine) {
948
+ r.expectPunct("{", `in member() options for table "${tableName}" \u2014 member() needs { group, membership, member_user, member_group }`);
949
+ let group = null;
950
+ let membership = null;
951
+ let memberUser = null;
952
+ let memberGroup = null;
953
+ while (!r.tryPunct("}")) {
954
+ const key = r.readKey(`for a member() option of table "${tableName}"`);
955
+ r.expectPunct(":", `after "${key.name}" in member() for table "${tableName}"`);
956
+ if (key.name === "group") {
957
+ group = readColumnOrColumnList(r, `member({ group }) on table "${tableName}"`);
958
+ } else if (key.name === "membership") {
959
+ membership = readSingleColumn(r, `member({ membership }) on table "${tableName}"`);
960
+ } else if (key.name === "member_user") {
961
+ memberUser = readSingleColumn(r, `member({ member_user }) on table "${tableName}"`);
962
+ } else if (key.name === "member_group") {
963
+ memberGroup = readColumnOrColumnList(r, `member({ member_group }) on table "${tableName}"`);
964
+ } else {
965
+ throw new SchemaTsError(
966
+ `line ${key.line}: member() on table "${tableName}" has an unknown option "${key.name}". Allowed: group, membership, member_user, member_group.`
967
+ );
968
+ }
969
+ if (!r.tryPunct(",")) {
970
+ r.expectPunct("}", `closing member() options for table "${tableName}"`);
971
+ break;
972
+ }
973
+ }
974
+ const missing = [];
975
+ if (!group) missing.push("group");
976
+ if (!membership) missing.push("membership");
977
+ if (!memberUser) missing.push("member_user");
978
+ if (!memberGroup) missing.push("member_group");
979
+ if (missing.length > 0 || !group || !membership || !memberUser || !memberGroup) {
980
+ throw new SchemaTsError(
981
+ `line ${scopeLine}: member() on table "${tableName}" is missing ${missing.join(", ")}. It needs member({ group, membership, member_user, member_group }) \u2014 group and member_group name the group-key columns on this table and on the membership table.`
982
+ );
983
+ }
984
+ validateMemberColumns(group, "group", tableName, scopeLine);
985
+ validateMemberColumns([membership], "membership", tableName, scopeLine);
986
+ validateMemberColumns([memberUser], "member_user", tableName, scopeLine);
987
+ validateMemberColumns(memberGroup, "member_group", tableName, scopeLine);
988
+ if (group.length > MAX_INDEX_COLUMNS) {
989
+ throw new SchemaTsError(`line ${scopeLine}: member({ group }) on table "${tableName}" lists ${group.length} columns (max ${MAX_INDEX_COLUMNS}).`);
990
+ }
991
+ if (group.length !== memberGroup.length) {
992
+ throw new SchemaTsError(
993
+ `line ${scopeLine}: member() on table "${tableName}" has a ${group.length}-column group but a ${memberGroup.length}-column member_group. The group key must have the same number of columns on both sides.`
994
+ );
995
+ }
996
+ return { kind: "member", group, membership, memberUser, memberGroup };
997
+ }
998
+ function readRelations(r, tableName) {
999
+ r.expectPunct("{", `for "relations" of table "${tableName}"`);
1000
+ const out = [];
1001
+ const seen = /* @__PURE__ */ new Set();
1002
+ while (!r.tryPunct("}")) {
1003
+ const key = r.readKey(`for a relation name of table "${tableName}"`);
1004
+ if (!SAFE_IDENT.test(key.name) || key.name.length > MAX_NAME_LENGTH) {
1005
+ throw new SchemaTsError(`line ${key.line}: relation name "${key.name}" on table "${tableName}" is invalid. Use letters, digits, and underscores, starting with a letter or underscore.`);
1006
+ }
1007
+ const name = key.name.toLowerCase();
1008
+ if (seen.has(name)) throw new SchemaTsError(`line ${key.line}: relation "${key.name}" on table "${tableName}" is declared twice.`);
1009
+ seen.add(name);
1010
+ r.expectPunct(":", `after relation "${key.name}" on table "${tableName}"`);
1011
+ const callee = r.expectIdent(`for relation "${key.name}" on table "${tableName}"`);
1012
+ if (callee.value !== "hasMany") {
1013
+ throw new SchemaTsError(`line ${callee.line}: relation "${key.name}" on table "${tableName}" uses "${callee.value}()", which is not supported. Only hasMany('child_table', 'foreign_key') is available.`);
1014
+ }
1015
+ r.expectPunct("(", 'after "hasMany"');
1016
+ const childTok = r.next();
1017
+ if (childTok.kind !== "string") throw new SchemaTsError(`line ${childTok.line}: hasMany() for relation "${key.name}" on table "${tableName}" needs the child table name as a quoted string first.`);
1018
+ r.expectPunct(",", `after the child table in hasMany() for relation "${key.name}" on table "${tableName}" \u2014 hasMany('child_table', 'foreign_key')`);
1019
+ const fkTok = r.next();
1020
+ if (fkTok.kind !== "string") throw new SchemaTsError(`line ${fkTok.line}: hasMany() for relation "${key.name}" on table "${tableName}" needs the child foreign-key column as a quoted string second.`);
1021
+ r.tryPunct(",");
1022
+ r.expectPunct(")", `closing hasMany() for relation "${key.name}" on table "${tableName}"`);
1023
+ const childTable = childTok.value.toLowerCase();
1024
+ const fk = fkTok.value.toLowerCase();
1025
+ if (!SAFE_IDENT.test(childTable) || childTable.length > MAX_NAME_LENGTH) {
1026
+ throw new SchemaTsError(`line ${childTok.line}: hasMany() child table "${childTok.value}" for relation "${key.name}" on table "${tableName}" is not a valid table name.`);
1027
+ }
1028
+ if (!SAFE_IDENT.test(fk) || fk.length > MAX_NAME_LENGTH) {
1029
+ throw new SchemaTsError(`line ${fkTok.line}: hasMany() foreign key "${fkTok.value}" for relation "${key.name}" on table "${tableName}" is not a valid column name.`);
1030
+ }
1031
+ out.push({ name, table: childTable, fk, parentKey: "" });
1032
+ if (!r.tryPunct(",")) {
1033
+ r.expectPunct("}", `closing "relations" of table "${tableName}"`);
1034
+ break;
1035
+ }
1036
+ }
1037
+ out.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
1038
+ return out;
1039
+ }
1040
+ function readScope(r, tableName) {
1041
+ const tok = r.expectIdent(`for the scope of table "${tableName}"`);
1042
+ if (!SCOPE_HELPERS.has(tok.value)) {
1043
+ throw new SchemaTsError(
1044
+ `line ${tok.line}: unknown scope "${tok.value}" on table "${tableName}". Use owner(), member(), shared(), or serverOnly().`
1045
+ );
1046
+ }
1047
+ if (tok.value === "member") {
1048
+ r.expectPunct("(", 'after "member"');
1049
+ const scope = readMemberScope(r, tableName, tok.line);
1050
+ r.tryPunct(",");
1051
+ r.expectPunct(")", "closing member()");
1052
+ return scope;
1053
+ }
1054
+ r.expectPunct("(", `after "${tok.value}"`);
1055
+ let opts = /* @__PURE__ */ new Map();
1056
+ if (!r.tryPunct(")")) {
1057
+ opts = r.readLiteralObject(`in ${tok.value}() options for table "${tableName}"`);
1058
+ r.tryPunct(",");
1059
+ r.expectPunct(")", `closing ${tok.value}()`);
1060
+ }
1061
+ if (tok.value === "owner") {
1062
+ let column = DEFAULT_OWNER_COLUMN;
1063
+ let visitors = false;
1064
+ for (const [key, { value, line }] of opts) {
1065
+ if (key === "visitors") {
1066
+ if (typeof value !== "boolean") {
1067
+ throw new SchemaTsError(`line ${line}: owner({ visitors }) on table "${tableName}" must be true or false.`);
1068
+ }
1069
+ visitors = value;
1070
+ continue;
1071
+ }
1072
+ if (key !== "column") {
1073
+ throw new SchemaTsError(`line ${line}: owner() on table "${tableName}" has an unknown option "${key}". Allowed: column, visitors.`);
1074
+ }
1075
+ if (typeof value !== "string" || !SAFE_IDENT.test(value) || value.length > MAX_NAME_LENGTH) {
1076
+ throw new SchemaTsError(`line ${line}: owner({ column }) on table "${tableName}" must be a valid column name.`);
1077
+ }
1078
+ column = value.toLowerCase();
1079
+ }
1080
+ return { kind: "owner", column, ...visitors ? { visitors: true } : {} };
1081
+ }
1082
+ if (opts.size > 0) {
1083
+ throw new SchemaTsError(`line ${tok.line}: ${tok.value}() on table "${tableName}" takes no options.`);
1084
+ }
1085
+ return tok.value === "shared" ? { kind: "shared" } : { kind: "server_only" };
1086
+ }
1087
+ function readColumnLists(r, tableName, optionName) {
1088
+ const startLine = r.peek().line;
1089
+ r.expectPunct("[", `for "${optionName}" of table "${tableName}"`);
1090
+ const entries = [];
1091
+ while (!r.tryPunct("]")) {
1092
+ const entryLine = r.peek().line;
1093
+ r.expectPunct("[", `for a "${optionName}" entry of table "${tableName}" (it is a list of column lists, e.g. [['a'], ['b', 'c']])`);
1094
+ const entry = [];
1095
+ while (!r.tryPunct("]")) {
1096
+ const t = r.next();
1097
+ if (t.kind !== "string") {
1098
+ throw new SchemaTsError(`line ${t.line}: "${optionName}" columns for table "${tableName}" must be quoted column names.`);
1099
+ }
1100
+ const colName = t.value.toLowerCase();
1101
+ if (entry.includes(colName)) {
1102
+ throw new SchemaTsError(`line ${t.line}: a "${optionName}" entry on table "${tableName}" lists "${t.value}" twice.`);
1103
+ }
1104
+ entry.push(colName);
1105
+ if (!r.tryPunct(",")) {
1106
+ r.expectPunct("]", `closing a "${optionName}" entry of table "${tableName}"`);
1107
+ break;
1108
+ }
1109
+ }
1110
+ if (entry.length === 0) {
1111
+ throw new SchemaTsError(`line ${entryLine}: a "${optionName}" entry on table "${tableName}" is empty.`);
1112
+ }
1113
+ if (entry.length > MAX_INDEX_COLUMNS) {
1114
+ throw new SchemaTsError(`line ${entryLine}: a "${optionName}" entry on table "${tableName}" lists ${entry.length} columns (max ${MAX_INDEX_COLUMNS}).`);
1115
+ }
1116
+ entries.push({ columns: entry, line: entryLine });
1117
+ if (!r.tryPunct(",")) {
1118
+ r.expectPunct("]", `closing "${optionName}" of table "${tableName}"`);
1119
+ break;
1120
+ }
1121
+ }
1122
+ if (entries.length > MAX_INDEXES) {
1123
+ throw new SchemaTsError(`line ${startLine}: table "${tableName}" declares ${entries.length} "${optionName}" entries (max ${MAX_INDEXES}).`);
1124
+ }
1125
+ return entries;
1126
+ }
1127
+ function readTable(r, rawName, nameLine) {
1128
+ const callee = r.expectIdent(`for table "${rawName}"`);
1129
+ if (callee.value === "removed") {
1130
+ throw new SchemaTsError(
1131
+ `line ${callee.line}: table "${rawName}" uses removed(), which marks a single column. To remove this table, mark it ${rawName}: removedTable().`
1132
+ );
1133
+ }
1134
+ if (TABLE_MARKER_HELPERS.has(callee.value)) {
1135
+ r.expectPunct("(", `after "${callee.value}"`);
1136
+ r.expectPunct(")", `closing ${callee.value}() on table "${rawName}" \u2014 the marker takes no options`);
1137
+ return {
1138
+ kind: callee.value,
1139
+ name: checkDeclaredName("Table", rawName, nameLine)
1140
+ };
1141
+ }
1142
+ if (callee.value !== "table") {
1143
+ throw new SchemaTsError(`line ${callee.line}: expected table(\u2026) for "${rawName}", found "${callee.value}(".`);
1144
+ }
1145
+ const name = checkDeclaredName("Table", rawName, nameLine);
1146
+ r.expectPunct("(", 'after "table"');
1147
+ r.expectPunct("{", `opening the columns of table "${rawName}"`);
1148
+ const columns = [];
1149
+ const removedColumns = [];
1150
+ const seen = /* @__PURE__ */ new Set();
1151
+ while (!r.tryPunct("}")) {
1152
+ const key = r.readKey(`for a column of table "${rawName}"`);
1153
+ r.expectPunct(":", `after column "${key.name}"`);
1154
+ const entry = readColumn(r, rawName, key.name, key.line);
1155
+ const entryName = entry.kind === "column" ? entry.column.name : entry.name;
1156
+ if (seen.has(entryName)) {
1157
+ throw new SchemaTsError(`line ${key.line}: column "${rawName}"."${key.name}" is declared twice (names are case-insensitive).`);
1158
+ }
1159
+ seen.add(entryName);
1160
+ if (entry.kind === "column") columns.push(entry.column);
1161
+ else removedColumns.push(entry.name);
1162
+ if (!r.tryPunct(",")) {
1163
+ r.expectPunct("}", `closing the columns of table "${rawName}"`);
1164
+ break;
1165
+ }
1166
+ }
1167
+ if (columns.length === 0) {
1168
+ throw new SchemaTsError(`line ${nameLine}: table "${rawName}" declares no columns${removedColumns.length > 0 ? " (removal markers do not count \u2014 a table needs at least one kept column, or mark the whole table removedTable())" : ""}.`);
1169
+ }
1170
+ if (columns.length > MAX_COLUMNS) {
1171
+ throw new SchemaTsError(`line ${nameLine}: table "${rawName}" declares ${columns.length} columns (max ${MAX_COLUMNS}).`);
1172
+ }
1173
+ const idCols = columns.filter((c) => c.helper === "id");
1174
+ if (idCols.length > 1) {
1175
+ throw new SchemaTsError(`line ${nameLine}: table "${rawName}" declares ${idCols.length} id() columns \u2014 a table has at most one.`);
1176
+ }
1177
+ let scope = null;
1178
+ let indexEntries = null;
1179
+ let uniqueEntries = null;
1180
+ let relations = [];
1181
+ let relationsDeclared = false;
1182
+ let clientInput;
1183
+ if (r.tryPunct(",")) {
1184
+ r.expectPunct("{", `opening the options of table "${rawName}"`);
1185
+ while (!r.tryPunct("}")) {
1186
+ const key = r.readKey(`for an option of table "${rawName}"`);
1187
+ r.expectPunct(":", `after "${key.name}"`);
1188
+ if (key.name === "scope") {
1189
+ if (scope) throw new SchemaTsError(`line ${key.line}: table "${rawName}" declares "scope" twice.`);
1190
+ scope = readScope(r, rawName);
1191
+ } else if (key.name === "indexes") {
1192
+ if (indexEntries) throw new SchemaTsError(`line ${key.line}: table "${rawName}" declares "indexes" twice.`);
1193
+ indexEntries = readColumnLists(r, rawName, "indexes");
1194
+ } else if (key.name === "unique") {
1195
+ if (uniqueEntries) throw new SchemaTsError(`line ${key.line}: table "${rawName}" declares "unique" twice.`);
1196
+ uniqueEntries = readColumnLists(r, rawName, "unique");
1197
+ } else if (key.name === "relations") {
1198
+ if (relationsDeclared) throw new SchemaTsError(`line ${key.line}: table "${rawName}" declares "relations" twice.`);
1199
+ relationsDeclared = true;
1200
+ relations = readRelations(r, rawName);
1201
+ } else if (key.name === "client") {
1202
+ if (clientInput) throw new SchemaTsError(`line ${key.line}: table "${rawName}" declares "client" twice.`);
1203
+ clientInput = readClientPermissions(r);
1204
+ } else {
1205
+ throw new SchemaTsError(
1206
+ `line ${key.line}: table "${rawName}" has an unknown option "${key.name}". Allowed: scope, indexes, unique, relations, client.`
1207
+ );
1208
+ }
1209
+ if (!r.tryPunct(",")) {
1210
+ r.expectPunct("}", `closing the options of table "${rawName}"`);
1211
+ break;
1212
+ }
1213
+ }
1214
+ }
1215
+ r.tryPunct(",");
1216
+ r.expectPunct(")", `closing table("${rawName}")`);
1217
+ scope ??= { kind: "owner", column: DEFAULT_OWNER_COLUMN };
1218
+ if (columns.some((column) => column.name === SHARED_AUTHOR_COLUMN || column.renamedFrom === SHARED_AUTHOR_COLUMN) || removedColumns.includes(SHARED_AUTHOR_COLUMN) || scope.kind === "owner" && scope.column === SHARED_AUTHOR_COLUMN) {
1219
+ throw new SchemaTsError(`line ${nameLine}: "${SHARED_AUTHOR_COLUMN}" is platform-maintained row authorship and cannot be declared, removed, renamed, or used as a custom owner column.`);
1220
+ }
1221
+ if (scope.kind === "owner") {
1222
+ const clash = columns.find((c) => c.name === scope.column);
1223
+ if (clash) {
1224
+ throw new SchemaTsError(
1225
+ `line ${nameLine}: table "${rawName}" declares the column "${scope.column}", but that is its owner column, which the platform creates and manages. Remove it from the columns \u2014 or pick a different owner column with owner({ column: '\u2026' }).`
1226
+ );
1227
+ }
1228
+ if (removedColumns.includes(scope.column)) {
1229
+ throw new SchemaTsError(
1230
+ `line ${nameLine}: table "${rawName}" marks its owner column "${scope.column}" as removed(). The owner column carries the table's per-user scope and cannot be removed while the scope is owner().`
1231
+ );
1232
+ }
1233
+ }
1234
+ const declaredNow = new Set(columns.map((c) => c.name));
1235
+ const renameSources = /* @__PURE__ */ new Set();
1236
+ for (const c of columns) {
1237
+ if (!c.renamedFrom) continue;
1238
+ if (declaredNow.has(c.renamedFrom)) {
1239
+ throw new SchemaTsError(
1240
+ `line ${nameLine}: "${rawName}"."${c.name}" declares renamedFrom: '${c.renamedFrom}', but "${c.renamedFrom}" is still declared as a column. A rename replaces the old column \u2014 remove its declaration.`
1241
+ );
1242
+ }
1243
+ if (scope.kind === "owner" && c.renamedFrom === scope.column) {
1244
+ throw new SchemaTsError(
1245
+ `line ${nameLine}: "${rawName}"."${c.name}" declares renamedFrom: '${c.renamedFrom}', but that is the table's owner column, which the platform creates and manages \u2014 it cannot be renamed into an ordinary column.`
1246
+ );
1247
+ }
1248
+ if (removedColumns.includes(c.renamedFrom)) {
1249
+ throw new SchemaTsError(
1250
+ `line ${nameLine}: "${rawName}"."${c.renamedFrom}" is marked removed() AND named by renamedFrom on "${c.name}" \u2014 it cannot be both removed and renamed. Keep one.`
1251
+ );
1252
+ }
1253
+ if (renameSources.has(c.renamedFrom)) {
1254
+ throw new SchemaTsError(
1255
+ `line ${nameLine}: two columns of "${rawName}" declare renamedFrom: '${c.renamedFrom}' \u2014 a column can only be renamed to one new name.`
1256
+ );
1257
+ }
1258
+ renameSources.add(c.renamedFrom);
1259
+ }
1260
+ const known = new Set(columns.map((c) => c.name));
1261
+ if (scope.kind === "owner") known.add(scope.column);
1262
+ const indexes = [];
1263
+ for (const entry of indexEntries ?? []) {
1264
+ for (const colName of entry.columns) {
1265
+ if (!known.has(colName)) {
1266
+ throw new SchemaTsError(
1267
+ `line ${entry.line}: index on table "${rawName}" names "${colName}", which is not one of its declared columns.`
1268
+ );
1269
+ }
1270
+ }
1271
+ indexes.push(entry.columns);
1272
+ }
1273
+ const uniques = [];
1274
+ for (const entry of uniqueEntries ?? []) {
1275
+ if (entry.columns.length < 2) {
1276
+ throw new SchemaTsError(
1277
+ `line ${entry.line}: the "unique" option on table "${rawName}" is for MULTI-column uniqueness. For a single column, put { unique: true } on the column itself.`
1278
+ );
1279
+ }
1280
+ for (const colName of entry.columns) {
1281
+ if (!known.has(colName)) {
1282
+ throw new SchemaTsError(
1283
+ `line ${entry.line}: a "unique" entry on table "${rawName}" names "${colName}", which is not one of its declared columns.`
1284
+ );
1285
+ }
1286
+ }
1287
+ uniques.push(entry.columns);
1288
+ }
1289
+ let client;
1290
+ if (clientInput) {
1291
+ if (columns.filter((column) => column.helper === "id").length !== 1) {
1292
+ throw new SchemaTsError(`line ${nameLine}: table "${rawName}": client operations require exactly one id() column.`);
1293
+ }
1294
+ const parsed = parseClientPermissions(clientInput, columns, scope);
1295
+ if (!parsed.ok) throw new SchemaTsError(`line ${nameLine}: table "${rawName}": ${parsed.message}`);
1296
+ const key = columns.find((column) => column.helper === "id").name;
1297
+ if (Array.isArray(parsed.permissions.read) && !parsed.permissions.read.includes(key)) {
1298
+ throw new SchemaTsError(`line ${nameLine}: table "${rawName}": client.read must include primary key "${key}" for stable pagination.`);
1299
+ }
1300
+ client = parsed.permissions;
1301
+ }
1302
+ return { kind: "table", table: { name, scope, relations, columns, indexes, uniques, removedColumns: [...removedColumns].sort(), ...client ? { client } : {} } };
1303
+ }
1304
+ function extractSchemaTs(source) {
1305
+ if (typeof source !== "string" || source.trim().length === 0) {
1306
+ return { ok: false, errors: ["db/schema.ts is empty. Declare at least one table, or remove the file to leave the database unmanaged."] };
1307
+ }
1308
+ if (source.length > MAX_SOURCE_BYTES) {
1309
+ return { ok: false, errors: [`db/schema.ts is larger than ${Math.floor(MAX_SOURCE_BYTES / 1024)}KB \u2014 that is not a schema declaration.`] };
1310
+ }
1311
+ try {
1312
+ const r = new Reader(tokenize(source));
1313
+ while (r.peek().kind === "ident" && r.peek().value === "import") skipImport(r);
1314
+ const exp = r.expectIdent("to start the declaration");
1315
+ if (exp.value !== "export") {
1316
+ throw new SchemaTsError(
1317
+ `line ${exp.line}: db/schema.ts must contain only the schema declaration: optional imports, then "export default schema({ \u2026 })". Found "${exp.value}".`
1318
+ );
1319
+ }
1320
+ const def = r.expectIdent('after "export"');
1321
+ if (def.value !== "default") throw new SchemaTsError(`line ${def.line}: expected "default" after "export".`);
1322
+ const schemaTok = r.expectIdent('after "export default"');
1323
+ if (schemaTok.value !== "schema") {
1324
+ throw new SchemaTsError(`line ${schemaTok.line}: expected schema({ \u2026 }) after "export default", found "${schemaTok.value}".`);
1325
+ }
1326
+ r.expectPunct("(", 'after "schema"');
1327
+ r.expectPunct("{", "opening the schema tables");
1328
+ const tables = [];
1329
+ const removedTables = [];
1330
+ const exportedTables = [];
1331
+ const seen = /* @__PURE__ */ new Set();
1332
+ const reader = r;
1333
+ while (!reader.tryPunct("}")) {
1334
+ const key = reader.readKey("for a table name");
1335
+ reader.expectPunct(":", `after table "${key.name}"`);
1336
+ const entry = readTable(reader, key.name, key.line);
1337
+ const entryName = entry.kind === "table" ? entry.table.name : entry.name;
1338
+ if (seen.has(entryName)) {
1339
+ throw new SchemaTsError(`line ${key.line}: table "${key.name}" is declared twice (names are case-insensitive).`);
1340
+ }
1341
+ seen.add(entryName);
1342
+ if (entry.kind === "table") tables.push(entry.table);
1343
+ else if (entry.kind === "removedTable") removedTables.push(entryName);
1344
+ else exportedTables.push(entryName);
1345
+ if (!reader.tryPunct(",")) {
1346
+ reader.expectPunct("}", "closing the schema tables");
1347
+ break;
1348
+ }
1349
+ }
1350
+ let managedSearch;
1351
+ if (reader.tryPunct(",") && !(reader.peek().kind === "punct" && reader.peek().value === ")")) {
1352
+ reader.expectPunct("{", "opening schema options");
1353
+ const option = reader.readKey("for a schema option");
1354
+ if (option.name !== "search") {
1355
+ throw new SchemaTsError(`line ${option.line}: unknown schema option "${option.name}". The only schema-level option is search: owner().`);
1356
+ }
1357
+ reader.expectPunct(":", 'after schema option "search"');
1358
+ const helper = reader.expectIdent('for schema option "search"');
1359
+ if (helper.value !== "owner") {
1360
+ throw new SchemaTsError(`line ${helper.line}: search ownership must be declared with owner().`);
1361
+ }
1362
+ reader.expectPunct("(", "after search: owner");
1363
+ reader.expectPunct(")", "closing search: owner()");
1364
+ managedSearch = { kind: "owner" };
1365
+ reader.tryPunct(",");
1366
+ reader.expectPunct("}", "closing schema options");
1367
+ reader.tryPunct(",");
1368
+ }
1369
+ reader.expectPunct(")", "closing schema(\u2026)");
1370
+ reader.tryPunct(";");
1371
+ if (!reader.atEof()) {
1372
+ reader.fail(
1373
+ `db/schema.ts must end after "export default schema({ \u2026 })" \u2014 found ${describeToken(reader.peek())}. The file holds only the schema declaration.`
1374
+ );
1375
+ }
1376
+ if (tables.length === 0 && removedTables.length === 0 && exportedTables.length === 0 && !managedSearch) {
1377
+ return { ok: false, errors: ["db/schema.ts declares no tables. Declare at least one table, or remove the file to leave the database unmanaged."] };
1378
+ }
1379
+ if (tables.length > MAX_TABLES) {
1380
+ return { ok: false, errors: [`db/schema.ts declares ${tables.length} tables (max ${MAX_TABLES}).`] };
1381
+ }
1382
+ const errors = [];
1383
+ const names = new Set(tables.map((t) => t.name));
1384
+ for (const t of tables) {
1385
+ for (const c of t.columns) {
1386
+ if (c.references && !names.has(c.references)) {
1387
+ const marked = removedTables.includes(c.references) || exportedTables.includes(c.references);
1388
+ errors.push(
1389
+ marked ? `Column "${t.name}"."${c.name}" references "${c.references}", which this file marks ${removedTables.includes(c.references) ? "removedTable()" : "exported()"}. Remove the reference (mark the column removed(), or drop "references" from it) before the table can go.` : `Column "${t.name}"."${c.name}" references "${c.references}", which is not declared in db/schema.ts. Foreign keys can only target tables in the same declaration.`
1390
+ );
1391
+ }
1392
+ }
1393
+ }
1394
+ const tableByName = new Map(tables.map((t) => [t.name, t]));
1395
+ const knownColumns = (t) => {
1396
+ const s = new Set(t.columns.map((c) => c.name));
1397
+ if (t.scope.kind === "owner") s.add(t.scope.column);
1398
+ return s;
1399
+ };
1400
+ for (const t of tables) {
1401
+ if (t.scope.kind !== "member") continue;
1402
+ const sc = t.scope;
1403
+ const own = knownColumns(t);
1404
+ for (const g of sc.group) {
1405
+ if (!own.has(g)) {
1406
+ errors.push(
1407
+ `Table "${t.name}" is member-scoped on "${g}", but "${g}" is not one of its declared columns. The group-key columns must be real columns on the table.`
1408
+ );
1409
+ }
1410
+ }
1411
+ const membershipTable = tableByName.get(sc.membership);
1412
+ if (!membershipTable) {
1413
+ errors.push(
1414
+ `Table "${t.name}" is member-scoped through the membership table "${sc.membership}", which is not declared in db/schema.ts. The membership table must be a managed table in the same schema.`
1415
+ );
1416
+ continue;
1417
+ }
1418
+ const membershipCols = knownColumns(membershipTable);
1419
+ if (membershipTable.client && (membershipTable.client.create !== null || membershipTable.client.update !== null || membershipTable.client.delete)) {
1420
+ errors.push(`Table "${sc.membership}" grants membership access to "${t.name}" and cannot allow client writes. Change membership through a server function.`);
1421
+ }
1422
+ if (!membershipCols.has(sc.memberUser)) {
1423
+ errors.push(
1424
+ `The membership table "${sc.membership}" (for member-scoped "${t.name}") has no "${sc.memberUser}" column named in member({ member_user }).`
1425
+ );
1426
+ }
1427
+ for (const mg of sc.memberGroup) {
1428
+ if (!membershipCols.has(mg)) {
1429
+ errors.push(
1430
+ `The membership table "${sc.membership}" (for member-scoped "${t.name}") has no "${mg}" column named in member({ member_group }).`
1431
+ );
1432
+ }
1433
+ }
1434
+ }
1435
+ const columnByName = (t, col) => t.columns.find((c) => c.name === col);
1436
+ for (const t of tables) {
1437
+ if (t.relations.length === 0) continue;
1438
+ const idCol = t.columns.find((c) => c.helper === "id");
1439
+ const rootNames = knownColumns(t);
1440
+ for (const rel of t.relations) {
1441
+ if (rel.name === "__proto__" || rel.name === "prototype" || rel.name === "constructor") {
1442
+ errors.push(
1443
+ `Relation "${t.name}"."${rel.name}" uses a reserved object name. Choose a relation name that can be returned as an ordinary row field.`
1444
+ );
1445
+ } else if (rootNames.has(rel.name)) {
1446
+ errors.push(
1447
+ `Relation "${t.name}"."${rel.name}" has the same name as a column on "${t.name}". Choose a different relation name so nesting child rows cannot overwrite the root column.`
1448
+ );
1449
+ }
1450
+ if (!idCol) {
1451
+ errors.push(
1452
+ `Table "${t.name}" declares relation "${rel.name}" but has no id() column to join on. A hasMany relation joins the child's foreign key to this table's primary key \u2014 declare an id() column.`
1453
+ );
1454
+ } else {
1455
+ rel.parentKey = idCol.name;
1456
+ }
1457
+ const child = tableByName.get(rel.table);
1458
+ if (!child) {
1459
+ const marked = removedTables.includes(rel.table) || exportedTables.includes(rel.table);
1460
+ errors.push(
1461
+ marked ? `Relation "${t.name}"."${rel.name}" targets "${rel.table}", which this file marks ${removedTables.includes(rel.table) ? "removedTable()" : "exported()"}. Remove the relation before the table can go.` : `Relation "${t.name}"."${rel.name}" targets child table "${rel.table}", which is not declared in db/schema.ts. A relation can only target a managed table in the same declaration.`
1462
+ );
1463
+ continue;
1464
+ }
1465
+ const fkCol = columnByName(child, rel.fk);
1466
+ if (!fkCol) {
1467
+ errors.push(
1468
+ `Relation "${t.name}"."${rel.name}" joins on "${rel.table}"."${rel.fk}", but "${rel.table}" has no column "${rel.fk}". The foreign key must be a real column on the child table.`
1469
+ );
1470
+ continue;
1471
+ }
1472
+ if (fkCol.references !== t.name) {
1473
+ errors.push(
1474
+ fkCol.references === null ? `Relation "${t.name}"."${rel.name}" joins on "${rel.table}"."${rel.fk}", but that column declares no foreign key. Declare it as ${rel.fk}: <type>({ references: '${t.name}' }) so the join key is a proven foreign key, not a free-form join.` : `Relation "${t.name}"."${rel.name}" joins on "${rel.table}"."${rel.fk}", but that column references "${fkCol.references}", not "${t.name}". The foreign key must reference the table that declares the relation.`
1475
+ );
1476
+ } else if (idCol) {
1477
+ const expectedHelper = idCol.uuid ? "text" : "integer";
1478
+ if (fkCol.helper !== expectedHelper) {
1479
+ errors.push(
1480
+ `Relation "${t.name}"."${rel.name}" joins "${rel.table}"."${rel.fk}" to "${t.name}"."${idCol.name}", but their key types do not match. Use ${expectedHelper}() for the foreign key so fetched child rows can be stitched to the ${idCol.uuid ? "text" : "whole-number"} parent id exactly.`
1481
+ );
1482
+ }
1483
+ }
1484
+ }
1485
+ }
1486
+ if (errors.length > 0) return { ok: false, errors };
1487
+ return {
1488
+ ok: true,
1489
+ declaration: {
1490
+ tables,
1491
+ removedTables: removedTables.sort(),
1492
+ exportedTables: exportedTables.sort(),
1493
+ ...managedSearch ? { search: managedSearch } : {}
1494
+ }
1495
+ };
1496
+ } catch (err) {
1497
+ if (err instanceof SchemaTsError) {
1498
+ return { ok: false, errors: [`db/schema.ts could not be read: ${err.message}`] };
1499
+ }
1500
+ throw err;
1501
+ }
1502
+ }
1503
+ function canonicalTableShape(t) {
1504
+ return {
1505
+ name: t.name,
1506
+ scope: t.scope,
1507
+ ...t.client ? { client: t.client } : {},
1508
+ columns: t.columns.map((c) => ({
1509
+ name: c.name,
1510
+ helper: c.helper,
1511
+ uuid: c.uuid,
1512
+ nullable: c.nullable,
1513
+ hasDefault: c.hasDefault,
1514
+ default: c.default,
1515
+ autoNow: c.autoNow,
1516
+ unique: c.unique,
1517
+ references: c.references,
1518
+ onDelete: c.onDelete,
1519
+ // conditional: a marker-free declaration keeps its slice-1 generation id
1520
+ ...c.renamedFrom ? { renamedFrom: c.renamedFrom } : {}
1521
+ })),
1522
+ indexes: t.indexes,
1523
+ uniques: t.uniques,
1524
+ ...t.removedColumns.length > 0 ? { removedColumns: t.removedColumns } : {},
1525
+ // conditional: a relation-free table keeps its slice-1 generation id. Ordered
1526
+ // by name (readRelations sorts) so the shape is deterministic.
1527
+ ...t.relations.length > 0 ? { relations: t.relations.map((rel) => ({ name: rel.name, table: rel.table, fk: rel.fk, parentKey: rel.parentKey })) } : {}
1528
+ };
1529
+ }
1530
+
1531
+ // worker/src/utils/db-schema-deploy/client-contract-source.ts
1532
+ var import_declared_data_contract = __toESM(require_declared_data_contract());
1533
+ function declareEntry(dictionary, name, value) {
1534
+ Object.defineProperty(dictionary, name, { value, writable: true, enumerable: true, configurable: true });
1535
+ }
1536
+ function clientAuthorityFromSource(files) {
1537
+ const authority = schemaAuthorityFromSource(files);
1538
+ return Object.values(authority?.schema ?? {}).some((table) => table.client !== void 0) ? authority : void 0;
1539
+ }
1540
+ function schemaAuthorityFromSource(files) {
1541
+ const source = files["db/schema.ts"];
1542
+ if (source === void 0) return void 0;
1543
+ const parsed = extractSchemaTs(source);
1544
+ if (!parsed.ok) throw new Error(`Invalid db/schema.ts: ${parsed.errors.join(" ")}`);
1545
+ const schema = {};
1546
+ const intents = {};
1547
+ const scopes = {};
1548
+ for (const table of parsed.declaration.tables) {
1549
+ const baked = bakedTableSchemaFromDeclared(JSON.stringify(canonicalTableShape(table)));
1550
+ if (!baked) throw new Error(`Could not derive the client contract for declared table "${table.name}".`);
1551
+ declareEntry(schema, table.name, baked);
1552
+ declareEntry(intents, table.name, table.scope.kind === "owner" ? "scoped" : table.scope.kind);
1553
+ if (table.scope.kind === "owner") declareEntry(scopes, table.name, table.scope.column);
1554
+ }
1555
+ return { schema, intents, scopes };
1556
+ }
1557
+
1558
+ // declared-data-vendor-entry.js
1559
+ var import_typed_data = __toESM(require_typed_data());
1560
+ var import_schema_types = __toESM(require_schema_types());
1561
+ var import_runtime_types = __toESM(require_runtime_types());
1562
+ function generateFromFiles(files) {
1563
+ const authority = clientAuthorityFromSource(files);
1564
+ return authority ? (0, import_typed_data.generateDataClient)(authority) : void 0;
1565
+ }
1566
+ // Annotate the CommonJS export names for ESM import in node:
1567
+ 0 && (module.exports = {
1568
+ RUNTIME_CONTEXT_DECLARATION,
1569
+ SCHEMA_DECLARATION,
1570
+ generateFromFiles
1571
+ });