@somewhere-tech/cli 0.34.2 → 0.34.4

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.
@@ -309,7 +309,7 @@ var require_typed_data = __commonJS({
309
309
  var crypto2 = require("node:crypto");
310
310
  var { canonicalize: canonicalize2, describeDataClient: describeDataClient2 } = require_declared_data_contract();
311
311
  var MODULE_NAME = "somewhere:data";
312
- var CLIENT_FILE = "__somewhere_data.d.ts";
312
+ var CLIENT_FILE2 = "__somewhere_data.d.ts";
313
313
  var MANIFEST_PATH = "_internal/declared-data.json";
314
314
  function generateDataClient2(authority) {
315
315
  const canonical = authority ? canonicalize2(authority.schema, authority.intents, authority.scopes) : canonicalize2({}, {}, {});
@@ -327,65 +327,7 @@ var require_typed_data = __commonJS({
327
327
  }
328
328
  };
329
329
  }
330
- module2.exports = { MODULE_NAME, CLIENT_FILE, MANIFEST_PATH, generateDataClient: generateDataClient2, virtualDataPlugin };
331
- }
332
- });
333
-
334
- // worker/containers/compile/schema-types.cjs
335
- var require_schema_types = __commonJS({
336
- "worker/containers/compile/schema-types.cjs"(exports2, module2) {
337
- "use strict";
338
- var types = `declare namespace SomewhereSchemaDeclaration {
339
- interface Column { readonly __somewhereColumn: unique symbol }
340
- interface Table { readonly __somewhereTable: unique symbol }
341
- interface TableMarker { readonly __somewhereTableMarker: unique symbol }
342
- interface ColumnMarker { readonly __somewhereColumnMarker: unique symbol }
343
- interface Relation { readonly __somewhereRelation: unique symbol }
344
- interface Scope { readonly __somewhereScope: unique symbol }
345
- interface OwnerScope extends Scope { readonly __somewhereOwner: unique symbol }
346
- interface MemberScope extends Scope { readonly __somewhereMember: unique symbol }
347
- interface ColumnOptions<Default> {
348
- nullable?: boolean; default?: Default | null; unique?: boolean;
349
- references?: string; onDelete?: 'cascade' | 'restrict'; renamedFrom?: string;
350
- }
351
- interface ClientPermissions<Field extends string> {
352
- identity?: 'authenticated' | 'visitor'; read?: boolean | Field[]; publicRead?: boolean | { where: Partial<Record<Field, string | number | boolean | null>> };
353
- create?: Field[] | false | null; update?: Field[] | false | null; delete?: boolean;
354
- }
355
- interface TableOptions<Field extends string> {
356
- scope?: Scope; client?: ClientPermissions<Field>; indexes?: string[][];
357
- unique?: string[][]; relations?: Record<string, Relation>;
358
- }
359
- }
360
- `;
361
- var signatures = `function schema(tables: Record<string, SomewhereSchemaDeclaration.Table | SomewhereSchemaDeclaration.TableMarker>, options?: { search: SomewhereSchemaDeclaration.OwnerScope }): unknown;
362
- function table<Columns extends Record<string, SomewhereSchemaDeclaration.Column | SomewhereSchemaDeclaration.ColumnMarker>>(columns: Columns, options?: SomewhereSchemaDeclaration.TableOptions<Extract<keyof Columns, string>>): SomewhereSchemaDeclaration.Table;
363
- function id(options?: { uuid?: boolean }): SomewhereSchemaDeclaration.Column;
364
- function text(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
365
- function number(options?: SomewhereSchemaDeclaration.ColumnOptions<number>): SomewhereSchemaDeclaration.Column;
366
- function integer(options?: SomewhereSchemaDeclaration.ColumnOptions<number>): SomewhereSchemaDeclaration.Column;
367
- function boolean(options?: SomewhereSchemaDeclaration.ColumnOptions<boolean>): SomewhereSchemaDeclaration.Column;
368
- function timestamp(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
369
- function json(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
370
- function blob(options?: SomewhereSchemaDeclaration.ColumnOptions<never>): SomewhereSchemaDeclaration.Column;
371
- function owner(options?: { column?: string; visitors?: boolean }): SomewhereSchemaDeclaration.OwnerScope;
372
- function shared(): SomewhereSchemaDeclaration.Scope;
373
- function serverOnly(): SomewhereSchemaDeclaration.Scope;
374
- function member(options: { group: string | string[]; membership: string; member_user: string; member_group: string | string[]; operations?: Array<'read' | 'create' | 'update' | 'delete'> }): SomewhereSchemaDeclaration.MemberScope;
375
- function hasMany(table: string, foreignKey: string): SomewhereSchemaDeclaration.Relation;
376
- function belongsTo(table: string, foreignKey: string): SomewhereSchemaDeclaration.Relation;
377
- function removed(): SomewhereSchemaDeclaration.ColumnMarker;
378
- function removedTable(): SomewhereSchemaDeclaration.TableMarker;
379
- function exported(): SomewhereSchemaDeclaration.TableMarker;
380
- `;
381
- var policySignatures = `function anyOf(owner: SomewhereSchemaDeclaration.OwnerScope, member: SomewhereSchemaDeclaration.MemberScope): SomewhereSchemaDeclaration.Scope;
382
- function anyOf(member: SomewhereSchemaDeclaration.MemberScope, owner: SomewhereSchemaDeclaration.OwnerScope): SomewhereSchemaDeclaration.Scope;
383
- function parent(options: { via: string }): SomewhereSchemaDeclaration.Scope;
384
- `;
385
- var SCHEMA_DECLARATION2 = types + signatures.replace(/^function /gm, "declare function ") + `declare module 'somewhere/db' {
386
- ${(signatures + policySignatures).replace(/^function /gm, " export function ")}}
387
- `;
388
- module2.exports = { SCHEMA_DECLARATION: SCHEMA_DECLARATION2 };
330
+ module2.exports = { MODULE_NAME, CLIENT_FILE: CLIENT_FILE2, MANIFEST_PATH, generateDataClient: generateDataClient2, virtualDataPlugin };
389
331
  }
390
332
  });
391
333
 
@@ -439,11 +381,45 @@ type SomewhereDbWriteIntent =
439
381
  | { op: 'insert'; table: string; values: SomewhereDbValues; options?: SomewhereDbInsertOptions | null }
440
382
  | ({ op: 'update'; table: string } & SomewhereDbUpdate)
441
383
  | { op: 'remove'; table: string; where?: SomewhereDbWhere | null };
442
- interface SomewhereDbResult {
384
+ // db/schema.ts's tables, one entry each, generated beside this declaration
385
+ // (declaredTablesDeclaration). With no declared tables it stays empty: any
386
+ // table name is accepted and rows are Record<string, unknown>. Once tables are
387
+ // declared, structured calls accept only their names and rows carry the
388
+ // declared types. Raw SQL rows are never typed from it.
389
+ interface SomewhereDeclaredTables {}
390
+ type SomewhereDbTable = [keyof SomewhereDeclaredTables] extends [never] ? string : Extract<keyof SomewhereDeclaredTables, string>;
391
+ type __SomewhereDeclared<T extends string, K extends string, Fallback> = T extends keyof SomewhereDeclaredTables
392
+ ? K extends keyof SomewhereDeclaredTables[T] ? SomewhereDeclaredTables[T][K] : Fallback
393
+ : Fallback;
394
+ type SomewhereDbRow<T extends string> = __SomewhereDeclared<T, 'row', Record<string, unknown>>;
395
+ type SomewhereDbColumn<T extends string> = Extract<keyof SomewhereDbRow<T>, string>;
396
+ type SomewhereDbRelations<T extends string> = __SomewhereDeclared<T, 'relations', Record<string, unknown>>;
397
+ type SomewhereDbRelationName<T extends string> = Extract<keyof SomewhereDbRelations<T>, string>;
398
+ // A column list narrows the rows to those columns; each included relation adds
399
+ // its related rows under its name.
400
+ type SomewhereDbReadRow<T extends string, C, I> = T extends keyof SomewhereDeclaredTables
401
+ ? Pick<SomewhereDbRow<T>, Extract<C extends readonly (infer K)[] ? K : never, keyof SomewhereDbRow<T>>>
402
+ & Pick<SomewhereDbRelations<T>, Extract<I extends readonly (infer K)[] ? K : never, keyof SomewhereDbRelations<T>>>
403
+ : Record<string, unknown>;
404
+ type SomewhereDbReadOptionsFor<T extends string, C, I> = Omit<SomewhereDbReadOptions, 'columns' | 'include'> & {
405
+ columns?: C | null;
406
+ include?: I | null;
407
+ };
408
+ type SomewhereDbIncrementFor<T extends string> = T extends keyof SomewhereDeclaredTables
409
+ ? { readonly [K in __SomewhereDeclared<T, 'numeric', never>]?: number }
410
+ : SomewhereDbIncrement;
411
+ // A caller write never names row identity; server authority may ('serverSet').
412
+ type SomewhereDbUpdateFor<T extends string, Set extends 'set' | 'serverSet'> = {
413
+ where?: SomewhereDbWhere | null;
414
+ } & (
415
+ | { set: __SomewhereDeclared<T, Set, SomewhereDbValues>; increment?: SomewhereDbIncrementFor<T> | null }
416
+ | { set?: never; increment: SomewhereDbIncrementFor<T> }
417
+ );
418
+ interface SomewhereDbResult<Row = Record<string, unknown>> {
443
419
  // Composed results return db/schema.ts's declared types, as the browser data
444
420
  // client does: boolean columns as true/false, json columns as their value,
445
421
  // blob columns as byte arrays. Raw query/batch rows are exactly as stored.
446
- data: Record<string, unknown>[];
422
+ data: Row[];
447
423
  error: null;
448
424
  count: number;
449
425
  changes: number;
@@ -463,20 +439,26 @@ interface SomewhereRawBatchResult {
463
439
  interface SomewhereServerDb {
464
440
  query(sql: string, params?: readonly unknown[]): Promise<SomewhereDbResult>;
465
441
  batch(statements: readonly SomewhereRawDbStatement[]): Promise<SomewhereRawBatchResult[]>;
466
- from(table: string, options?: SomewhereDbReadOptions | null): Promise<SomewhereDbResult>;
467
- count(table: string, options?: SomewhereDbCountOptions | null): Promise<{ data: number; error: null }>;
468
- insert(table: string, values: SomewhereDbValues, options?: SomewhereDbInsertOptions | null): Promise<SomewhereDbResult>;
469
- update(table: string, spec: SomewhereDbUpdate): Promise<SomewhereDbResult>;
470
- remove(table: string, spec?: SomewhereDbRemove | null): Promise<SomewhereDbResult>;
442
+ from<T extends SomewhereDbTable, const C extends readonly SomewhereDbColumn<T>[] = readonly SomewhereDbColumn<T>[], const I extends readonly SomewhereDbRelationName<T>[] = []>(
443
+ table: T, options?: SomewhereDbReadOptionsFor<T, C, I> | null): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I>>>;
444
+ count(table: SomewhereDbTable, options?: SomewhereDbCountOptions | null): Promise<{ data: number; error: null }>;
445
+ // Server authority names row identity (owner column or shared authorship) explicitly on insert.
446
+ insert<T extends SomewhereDbTable>(table: T, values: __SomewhereDeclared<T, 'serverInsert', SomewhereDbValues>, options?: SomewhereDbInsertOptions | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
447
+ update<T extends SomewhereDbTable>(table: T, spec: SomewhereDbUpdateFor<T, 'serverSet'>): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
448
+ remove<T extends SomewhereDbTable>(table: T, spec?: SomewhereDbRemove | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
471
449
  // Closed atomic batch, 1-100 intents checked before execution. No callback,
472
450
  // reads, SQL, or caller-supplied authority. A lost acknowledgement can have
473
451
  // an unknown outcome; a rejection never promises rollback or safe replay.
474
452
  tx(intents: readonly SomewhereDbWriteIntent[]): Promise<SomewhereDbResult[]>;
475
453
  }
476
- interface SomewhereCallerDb extends Omit<SomewhereServerDb, 'from' | 'count'> {
477
- from(table: string, options?: (SomewhereDbReadOptions & { asServer?: true }) | null): Promise<SomewhereDbResult>;
478
- count(table: string, options?: (SomewhereDbCountOptions & { asServer?: true }) | null): Promise<{ data: number; error: null }>;
479
- delete(table: string, spec?: SomewhereDbRemove | null): Promise<SomewhereDbResult>;
454
+ interface SomewhereCallerDb extends Omit<SomewhereServerDb, 'from' | 'count' | 'insert' | 'update'> {
455
+ // The platform composes row identity from the verified user.
456
+ insert<T extends SomewhereDbTable>(table: T, values: __SomewhereDeclared<T, 'insert', SomewhereDbValues>, options?: SomewhereDbInsertOptions | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
457
+ update<T extends SomewhereDbTable>(table: T, spec: SomewhereDbUpdateFor<T, 'set'>): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
458
+ from<T extends SomewhereDbTable, const C extends readonly SomewhereDbColumn<T>[] = readonly SomewhereDbColumn<T>[], const I extends readonly SomewhereDbRelationName<T>[] = []>(
459
+ table: T, options?: (SomewhereDbReadOptionsFor<T, C, I> & { asServer?: true }) | null): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I>>>;
460
+ count(table: SomewhereDbTable, options?: (SomewhereDbCountOptions & { asServer?: true }) | null): Promise<{ data: number; error: null }>;
461
+ delete<T extends SomewhereDbTable>(table: T, spec?: SomewhereDbRemove | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
480
462
  readonly server: SomewhereServerDb;
481
463
  }
482
464
  interface SomewhereAuthUser {
@@ -546,9 +528,22 @@ type SomewhereDbAggregateOptions = SomewhereDbAggregateMeasures & {
546
528
  order?: SomewhereDbOrder | null;
547
529
  limit?: number | null;
548
530
  };
549
- interface SomewhereDbAggregateResult {
531
+ // A declared table names only its own columns: sum/avg its numeric ones,
532
+ // min/max/groupBy any but json/blob, and its rows hold scalars, as the browser
533
+ // data client's aggregate does.
534
+ type SomewhereDbAggregateOptionsFor<T extends string> = SomewhereDbAggregateOptions & {
535
+ sum?: __SomewhereDeclared<T, 'numeric', string> | null;
536
+ avg?: __SomewhereDeclared<T, 'numeric', string> | null;
537
+ min?: __SomewhereDeclared<T, 'scalar', string> | null;
538
+ max?: __SomewhereDeclared<T, 'scalar', string> | null;
539
+ groupBy?: readonly __SomewhereDeclared<T, 'scalar', string>[] | null;
540
+ };
541
+ type SomewhereDbAggregateRow<T extends string> = T extends keyof SomewhereDeclaredTables
542
+ ? Record<string, number | string | boolean | null>
543
+ : Record<string, unknown>;
544
+ interface SomewhereDbAggregateResult<Row = Record<string, unknown>> {
550
545
  // Always an array of group rows (exactly one when ungrouped): grouped columns plus composed result keys.
551
- data: Record<string, unknown>[];
546
+ data: Row[];
552
547
  error: null;
553
548
  // Number of rows (groups) returned; the COUNT(*) value lives in each row under "count".
554
549
  count: number;
@@ -558,7 +553,7 @@ interface SomewhereDbRawCallerOptions { unscoped?: true | null; asServer?: true
558
553
  type SomewhereDbLiveState =
559
554
  | { name: string; state: 'ready'; release_id?: string; fingerprint: string; subscribe_url: string; expires_at: number }
560
555
  | { name: string; state: 'resync_required'; reason: string };
561
- type SomewhereDbLiveResult = SomewhereDbResult & { live: SomewhereDbLiveState };
556
+ type SomewhereDbLiveResult<Row = Record<string, unknown>> = SomewhereDbResult<Row> & { live: SomewhereDbLiveState };
562
557
  type SomewhereDbScopeDeclaration =
563
558
  | { intent?: 'scoped' | null; owner_column: string; sensitive_columns?: readonly string[] | null }
564
559
  | { intent: 'shared' | 'server_only'; owner_column?: string | null; sensitive_columns?: readonly string[] | null };
@@ -594,14 +589,14 @@ interface SomewhereDbScopeApi {
594
589
  list(): Promise<SomewhereDbScopeEntry[]>;
595
590
  }
596
591
  interface SomewhereServerDb {
597
- aggregate(table: string, options: SomewhereDbAggregateOptions): Promise<SomewhereDbAggregateResult>;
592
+ aggregate<T extends SomewhereDbTable>(table: T, options: SomewhereDbAggregateOptionsFor<T>): Promise<SomewhereDbAggregateResult<SomewhereDbAggregateRow<T>>>;
598
593
  }
599
594
  interface SomewhereCallerDb {
600
595
  query(sql: string, params?: readonly unknown[] | null, options?: SomewhereDbRawCallerOptions | null): Promise<SomewhereDbResult>;
601
596
  batch(statements: readonly SomewhereRawDbStatement[], options?: SomewhereDbRawCallerOptions | null): Promise<SomewhereRawBatchResult[]>;
602
- aggregate(table: string, options: SomewhereDbAggregateOptions & { asServer?: true }): Promise<SomewhereDbAggregateResult>;
597
+ aggregate<T extends SomewhereDbTable>(table: T, options: SomewhereDbAggregateOptionsFor<T> & { asServer?: true }): Promise<SomewhereDbAggregateResult<SomewhereDbAggregateRow<T>>>;
603
598
  // read must be the exact sw.db.from(...) result (or its promise) this release declares under name.
604
- live(name: string, read: SomewhereDbResult | PromiseLike<SomewhereDbResult>): Promise<SomewhereDbLiveResult>;
599
+ live<Row>(name: string, read: SomewhereDbResult<Row> | PromiseLike<SomewhereDbResult<Row>>): Promise<SomewhereDbLiveResult<Row>>;
605
600
  tables(): Promise<string[]>;
606
601
  readonly scope: SomewhereDbScopeApi;
607
602
  }
@@ -1452,8 +1447,9 @@ interface SomewhereFsView {
1452
1447
  signedUrl(path: string, options?: SomewhereFsSignedUrlOptions | null): Promise<SomewhereFsSignedUrlResult>;
1453
1448
  public_url(path: string, options?: SomewhereFsPublicUrlOptions | null): Promise<SomewhereFsPublicUrlResult>;
1454
1449
  publicUrl(path: string, options?: SomewhereFsPublicUrlOptions | null): Promise<SomewhereFsPublicUrlResult>;
1455
- // null resets ownership to the project.
1456
- setOwner(path: string, user: string | { readonly id: string } | null): Promise<SomewhereFsSetOwnerResult>;
1450
+ // null resets ownership to the project. On sw.fs.server, { collection, group? }
1451
+ // adopts an existing file under that collection's folder into the collection.
1452
+ setOwner(path: string, user: string | { readonly id: string } | null, options?: { collection: string; group?: string | number } | null): Promise<SomewhereFsSetOwnerResult>;
1457
1453
  uploadFromRequest(req: Request, options: SomewhereFsUploadFromRequestOptions): Promise<SomewhereFsUploadFromRequestResult>;
1458
1454
  }
1459
1455
  interface SomewhereRuntimeFs extends SomewhereFsView { readonly server: SomewhereFsView }
@@ -2751,15 +2747,314 @@ type ServerFunction<Contract extends { input: unknown; output: unknown }> =
2751
2747
  (req: __SomewhereTypedRequest<Contract["input"]>, sw: SomewhereRuntimeContext) =>
2752
2748
  Contract["output"] | Promise<Contract["output"]>;
2753
2749
  `;
2754
- module2.exports = { RUNTIME_CONTEXT_DECLARATION: RUNTIME_CONTEXT_DECLARATION2 };
2750
+ var ENDPOINT_DECLARATION2 = `
2751
+ interface SomewhereEndpointUser {
2752
+ id: string;
2753
+ email?: string | null;
2754
+ display_name?: string | null;
2755
+ role?: string;
2756
+ [key: string]: unknown;
2757
+ }
2758
+ type __SomewhereEndpointLeaf = 'string' | 'email' | 'number' | 'boolean' | 'array' | 'object';
2759
+ interface SomewhereEndpointBodySchema {
2760
+ readonly [field: string]: __SomewhereEndpointLeaf | \`\${__SomewhereEndpointLeaf}?\` | SomewhereEndpointBodySchema;
2761
+ }
2762
+ type __SomewhereEndpointValue<T> =
2763
+ T extends 'string' | 'email' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean
2764
+ : T extends 'array' ? unknown[] : T extends 'object' ? Record<string, unknown> : never;
2765
+ type __SomewhereEndpointBody<S> = {
2766
+ -readonly [K in keyof S as S[K] extends \`\${string}?\` ? never : K]:
2767
+ S[K] extends string ? __SomewhereEndpointValue<S[K]> : __SomewhereEndpointBody<S[K]>;
2768
+ } & {
2769
+ -readonly [K in keyof S as S[K] extends \`\${string}?\` ? K : never]?:
2770
+ S[K] extends \`\${infer T}?\` ? __SomewhereEndpointValue<T> | null : never;
2771
+ };
2772
+ type SomewhereEndpointAuth = 'none' | 'optional' | 'required';
2773
+ interface SomewhereEndpointInput<Auth extends SomewhereEndpointAuth, Body> {
2774
+ body: Body;
2775
+ user: Auth extends 'required' ? SomewhereEndpointUser : SomewhereEndpointUser | null;
2776
+ headers: Headers;
2777
+ params: Record<string, string>;
2778
+ request: Request;
2779
+ }
2780
+ interface SomewhereEndpointConfig<Auth extends SomewhereEndpointAuth, Schema extends SomewhereEndpointBodySchema | undefined> {
2781
+ auth?: Auth;
2782
+ body?: Schema;
2783
+ rateLimit?: \`\${number}/\${'second' | 'minute' | 'hour' | 'day'}\${'' | 's'}\`;
2784
+ cors?: 'same-origin' | '*' | readonly string[];
2785
+ handler(
2786
+ input: SomewhereEndpointInput<Auth, Schema extends SomewhereEndpointBodySchema ? __SomewhereEndpointBody<Schema> : null>,
2787
+ sw: SomewhereRuntimeContext,
2788
+ ): unknown;
2789
+ }
2790
+ `;
2791
+ var DECLARED_COLUMN_TYPES = { integer: "number | string", number: "number", text: "string", timestamp: "string", boolean: "boolean", json: "__SomewhereJson", blob: "number[]" };
2792
+ function declaredOwnerColumn2(name, table, scopes) {
2793
+ if (Object.prototype.hasOwnProperty.call(scopes, name)) return scopes[name];
2794
+ const find = (policy) => !policy ? null : policy.k === "o" ? policy.c : policy.k === "a" ? find(policy.p[0]) || find(policy.p[1]) : null;
2795
+ return find(table.policy);
2796
+ }
2797
+ function declaredTablesDeclaration2(authority) {
2798
+ const schema = authority && authority.schema || {};
2799
+ const scopes = authority && authority.scopes || {};
2800
+ const names = Object.keys(schema).sort();
2801
+ if (!names.length) return "";
2802
+ const key = JSON.stringify;
2803
+ const shape = (fields) => fields.length ? "{ " + fields.join("; ") + " }" : "Record<string, never>";
2804
+ const union = (columns) => columns.length ? columns.map((column) => key(column.n)).join(" | ") : "never";
2805
+ const field = (column, optional) => key(column.n) + (optional ? "?" : "") + ": " + column.type;
2806
+ const entries = names.map((name) => {
2807
+ const table = schema[name];
2808
+ const columns = table.columns.map((column) => ({
2809
+ n: column.n,
2810
+ t: column.t,
2811
+ optional: column.d === 1 || column.nul === 1,
2812
+ identity: column.n === table.author,
2813
+ type: DECLARED_COLUMN_TYPES[column.t] + (column.nul === 1 ? " | null" : "")
2814
+ }));
2815
+ const owner = declaredOwnerColumn2(name, table, scopes);
2816
+ if (owner && !columns.some((column) => column.n === owner)) columns.push({ n: owner, t: "text", optional: false, identity: true, type: "string" });
2817
+ const declared = columns.filter((column) => !column.identity);
2818
+ const relations = (table.relations || []).filter((relation) => Object.prototype.hasOwnProperty.call(schema, relation.table)).map((relation) => key(relation.name) + ": SomewhereDeclaredTables[" + key(relation.table) + "]['row']" + (relation.kind === "belongsTo" ? " | null" : "[]"));
2819
+ return " " + key(name) + ": {\n row: " + shape(columns.map((column) => field(column, false))) + ";\n insert: " + shape(declared.map((column) => field(column, column.optional))) + ";\n serverInsert: " + shape(columns.map((column) => field(column, column.optional && !column.identity))) + ";\n set: " + shape(declared.map((column) => field(column, true))) + ";\n serverSet: " + shape(columns.map((column) => field(column, true))) + ";\n numeric: " + union(declared.filter((column) => column.t === "integer" || column.t === "number")) + ";\n scalar: " + union(declared.filter((column) => column.t !== "json" && column.t !== "blob")) + ";\n relations: " + (relations.length ? shape(relations) : "{}") + ";\n };\n";
2820
+ });
2821
+ return "interface SomewhereDeclaredTables {\n" + entries.join("") + "}\n";
2822
+ }
2823
+ module2.exports = { RUNTIME_CONTEXT_DECLARATION: RUNTIME_CONTEXT_DECLARATION2, ENDPOINT_DECLARATION: ENDPOINT_DECLARATION2, declaredTablesDeclaration: declaredTablesDeclaration2 };
2824
+ }
2825
+ });
2826
+
2827
+ // worker/containers/compile/typed-files.cjs
2828
+ var require_typed_files = __commonJS({
2829
+ "worker/containers/compile/typed-files.cjs"(exports2, module2) {
2830
+ "use strict";
2831
+ var crypto2 = require("node:crypto");
2832
+ var MODULE_NAME = "somewhere:files";
2833
+ var CLIENT_FILE2 = "__somewhere_files.d.ts";
2834
+ var SINGLE_REQUEST_MAX_BYTES = 90 * 1024 * 1024;
2835
+ function parseDeclaration(canonical) {
2836
+ const parsed = JSON.parse(canonical);
2837
+ if (!parsed || parsed.v !== 1 || !Array.isArray(parsed.collections)) throw new Error("declared_files is not a file collection declaration");
2838
+ return parsed.collections;
2839
+ }
2840
+ function memberOf(collection) {
2841
+ const scope = collection.scope || {};
2842
+ return scope.kind === "member" || scope.kind === "owner_or_member" ? scope.member : null;
2843
+ }
2844
+ function describeCollection(collection) {
2845
+ const member = memberOf(collection);
2846
+ const groupKey = member ? JSON.stringify(member.group) : null;
2847
+ const meta = member ? `FileMeta & { ${groupKey}: string }` : "FileMeta";
2848
+ const groupOption = member ? `${groupKey}: string | number; ` : "";
2849
+ const ops = [];
2850
+ if (collection.client.read) {
2851
+ ops.push(`list(options?: { ${groupOption.replace(": string | number; ", "?: string | number; ")}limit?: number; cursor?: string | null }): Promise<FileResult<{ items: ${meta}[]; next: string | null; has_more: boolean }>>`);
2852
+ ops.push(`get(id: string): Promise<FileResult<${meta}>>`);
2853
+ ops.push("url(id: string): string");
2854
+ ops.push("shareLink(id: string, options?: { expiresIn?: number }): Promise<FileResult<{ url: string; expires_at: string; expires_in: number }>>");
2855
+ }
2856
+ if (collection.client.upload) ops.push(`upload(file: Blob, options${member ? "" : "?"}: { ${groupOption}name?: string; contentType?: string; onProgress?: (progress: { loaded: number; total: number }) => void }): Promise<FileResult<UploadedFile>>`);
2857
+ if (collection.client.replace) ops.push("replace(id: string, file: Blob, options?: { contentType?: string; onProgress?: (progress: { loaded: number; total: number }) => void }): Promise<FileResult<UploadedFile>>");
2858
+ if (collection.client.delete) ops.push("delete(id: string): Promise<FileResult<{ id: string; deleted: true }>>");
2859
+ if (collection.public) {
2860
+ ops.push(collection.scope.kind === "server_only" ? "publicUrl(path: string): string" : "publicUrl(id: string, name: string): string");
2861
+ }
2862
+ return `${JSON.stringify(collection.name)}: { ${ops.join("; ")} }`;
2863
+ }
2864
+ function describeFilesClient2(canonical, singleRequestMaxBytes = SINGLE_REQUEST_MAX_BYTES) {
2865
+ const collections = parseDeclaration(canonical);
2866
+ const contract = crypto2.createHash("sha256").update(canonical).digest("hex");
2867
+ const declaration = `declare module ${JSON.stringify(MODULE_NAME)} {
2868
+ /** Why an operation did not happen. Expected refusals resolve here; nothing throws for them. */
2869
+ export interface FileError { code: string; message: string; status: number }
2870
+ export type FileResult<T> = { data: T; error: null } | { data: null; error: FileError };
2871
+ export interface FileMeta { id: string; name: string; size: number; content_type: string | null; version: number; created_at: string; updated_at: string; mine: boolean }
2872
+ export interface UploadedFile { id: string; name: string; size: number; content_type: string; version: number; url: string }
2873
+ export const files: { ${collections.map(describeCollection).join(";\n ")} };
2874
+ }
2875
+ `;
2876
+ const shapes = collections.map((c) => {
2877
+ const member = memberOf(c);
2878
+ return {
2879
+ name: c.name,
2880
+ root: c.path,
2881
+ ops: Object.keys(c.client).filter((op) => c.client[op]),
2882
+ public: c.public ? c.scope.kind === "server_only" ? "path" : "id" : null,
2883
+ group: member ? member.group : null,
2884
+ maxSize: c.limits.maxSize,
2885
+ types: c.limits.types
2886
+ };
2887
+ });
2888
+ const runtime = `const contract=${JSON.stringify(contract)};
2889
+ const SINGLE=${singleRequestMaxBytes};
2890
+ const shapes=${JSON.stringify(shapes)};
2891
+ const fail=(status,code,message)=>({data:null,error:{code,message,status}});
2892
+ const encodePath=p=>String(p).split("/").map(encodeURIComponent).join("/");
2893
+ async function readJson(response){try{return await response.json()}catch(_){return null}}
2894
+ async function call(collection,operation,input){
2895
+ let response;
2896
+ try{response=await fetch("/__sw/files",{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify(Object.assign({},input,{contract,collection,operation}))})}
2897
+ catch(e){return fail(0,"NETWORK_ERROR","The file request did not reach the app. Check the connection and try again.")}
2898
+ const payload=await readJson(response);
2899
+ if(!response.ok||!payload||payload.ok===false)return fail(response.status,payload&&payload.error||"FILES_ERROR",payload&&payload.message||"The file operation failed.");
2900
+ return{data:payload.data,error:null};
2901
+ }
2902
+ function send(method,url,body,type,onProgress){
2903
+ if(onProgress&&typeof XMLHttpRequest!=="undefined")return new Promise(resolve=>{
2904
+ const xhr=new XMLHttpRequest();xhr.open(method,url);if(type)xhr.setRequestHeader("Content-Type",type);
2905
+ xhr.upload.onprogress=e=>{if(e.lengthComputable)onProgress({loaded:e.loaded,total:e.total})};
2906
+ xhr.onload=()=>{let p=null;try{p=JSON.parse(xhr.responseText)}catch(_){}resolve({status:xhr.status,ok:xhr.status>=200&&xhr.status<300,payload:p})};
2907
+ xhr.onerror=()=>resolve({status:0,ok:false,payload:null});xhr.send(body);
2908
+ });
2909
+ return fetch(url,{method,headers:type?{"Content-Type":type}:{},body}).then(async r=>({status:r.status,ok:r.ok,payload:await readJson(r)}),()=>({status:0,ok:false,payload:null}));
2910
+ }
2911
+ const refused=r=>fail(r.status,r.payload&&r.payload.error||(r.status?"UPLOAD_FAILED":"NETWORK_ERROR"),r.payload&&r.payload.message||"The upload did not finish.");
2912
+ async function transfer(link,file,type,onProgress){
2913
+ const size=file.size;
2914
+ if(size<=SINGLE){const r=await send("PUT",link.url,file,type,onProgress);return r.ok?{data:r.payload.data,error:null}:refused(r)}
2915
+ const start=await send("POST",link.multipart_url,JSON.stringify({size_bytes:size,content_type:type}),"application/json");
2916
+ if(!start.ok)return refused(start);
2917
+ const s=start.payload.data;
2918
+ for(let n=1;n<=s.part_count;n++){
2919
+ const from=(n-1)*s.part_size,part=file.slice(from,Math.min(size,from+s.part_size));
2920
+ const r=await send("PUT",s.parts_url+"/"+n,part,null,onProgress?p=>onProgress({loaded:from+p.loaded,total:size}):null);
2921
+ if(!r.ok){await send("DELETE",s.abort_url,null,null);return refused(r)}
2922
+ }
2923
+ const done=await send("POST",s.complete_url,null,null);
2924
+ return done.ok?{data:done.payload.data,error:null}:refused(done);
2925
+ }
2926
+ function typeAllowed(types,type){if(!types.length)return true;const t=type.split(";")[0].trim().toLowerCase();return types.some(p=>p===t||(p.endsWith("/*")&&t.startsWith(p.slice(0,-1))))}
2927
+ function makeCollection(shape){
2928
+ const c={};const has=op=>shape.ops.indexOf(op)!==-1;
2929
+ const doorUrl=id=>"/__sw/files/"+encodeURIComponent(shape.name)+"/"+encodeURIComponent(id);
2930
+ const publicUrl=(id,name)=>shape.public==="path"?"/storage"+shape.root+"/"+encodePath(id):"/storage"+shape.root+"/"+encodeURIComponent(id)+"/"+encodeURIComponent(name);
2931
+ async function put(operation,id,file,options){
2932
+ options=options||{};
2933
+ if(!file||typeof file.size!=="number")return fail(400,"FILES_INPUT_INVALID","Pass a File or Blob.");
2934
+ const type=String(options.contentType||file.type||"application/octet-stream").split(";")[0].trim().toLowerCase();
2935
+ if(shape.maxSize!==null&&file.size>shape.maxSize)return fail(413,"PAYLOAD_TOO_LARGE","This file is larger than this collection accepts.");
2936
+ if(!typeAllowed(shape.types,type))return fail(415,"FILE_TYPE_NOT_ALLOWED","This collection accepts "+shape.types.join(", ")+".");
2937
+ const input={size:file.size,content_type:type};
2938
+ if(operation==="upload"){input.name=options.name||file.name||"file";if(shape.group)input[shape.group]=options[shape.group]}
2939
+ else input.id=id;
2940
+ const minted=await call(shape.name,operation,input);
2941
+ if(minted.error)return minted;
2942
+ const sent=await transfer(minted.data,file,type,options.onProgress);
2943
+ if(sent.error)return sent;
2944
+ const name=minted.data.name||input.name||"file";
2945
+ return{data:{id:minted.data.id,name,size:sent.data.size_bytes,content_type:sent.data.content_type,version:sent.data.version,url:shape.public==="id"?publicUrl(minted.data.id,name):doorUrl(minted.data.id)},error:null};
2946
+ }
2947
+ if(has("read")){
2948
+ c.list=(options={})=>{const input={};if(options.limit!==undefined)input.limit=options.limit;if(options.cursor!==undefined&&options.cursor!==null)input.cursor=options.cursor;if(shape.group&&options[shape.group]!==undefined)input[shape.group]=options[shape.group];return call(shape.name,"list",input)};
2949
+ c.get=id=>call(shape.name,"get",{id});
2950
+ c.url=doorUrl;
2951
+ c.shareLink=(id,options={})=>call(shape.name,"link",options.expiresIn===undefined?{id}:{id,expires_in:options.expiresIn});
2952
+ }
2953
+ if(has("upload"))c.upload=(file,options)=>put("upload",null,file,options);
2954
+ if(has("replace"))c.replace=(id,file,options)=>put("replace",id,file,options);
2955
+ if(has("delete"))c.delete=id=>call(shape.name,"delete",{id});
2956
+ if(shape.public)c.publicUrl=publicUrl;
2957
+ return Object.freeze(c);
2958
+ }
2959
+ const files=Object.freeze(Object.fromEntries(shapes.map(shape=>[shape.name,makeCollection(shape)])));
2960
+ export{files};
2961
+ `;
2962
+ return { contract_digest: contract, declaration, runtime };
2963
+ }
2964
+ function generateFilesClient(declaredFiles) {
2965
+ return typeof declaredFiles === "string" ? describeFilesClient2(declaredFiles) : null;
2966
+ }
2967
+ function virtualFilesPlugin(analysis) {
2968
+ return {
2969
+ name: "somewhere-declared-files",
2970
+ setup(build) {
2971
+ build.onResolve({ filter: /^somewhere:files$/ }, () => ({ path: MODULE_NAME, namespace: "somewhere-declared-files" }));
2972
+ build.onLoad({ filter: /.*/, namespace: "somewhere-declared-files" }, () => {
2973
+ if (!analysis) return { errors: [{ text: "somewhere:files requires db/schema.ts with a files: { \u2026 } declaration." }] };
2974
+ return { contents: analysis.runtime, loader: "js" };
2975
+ });
2976
+ }
2977
+ };
2978
+ }
2979
+ module2.exports = { MODULE_NAME, CLIENT_FILE: CLIENT_FILE2, SINGLE_REQUEST_MAX_BYTES, describeFilesClient: describeFilesClient2, generateFilesClient, virtualFilesPlugin };
2980
+ }
2981
+ });
2982
+
2983
+ // worker/containers/compile/schema-types.cjs
2984
+ var require_schema_types = __commonJS({
2985
+ "worker/containers/compile/schema-types.cjs"(exports2, module2) {
2986
+ "use strict";
2987
+ var types = `declare namespace SomewhereSchemaDeclaration {
2988
+ interface Column { readonly __somewhereColumn: unique symbol }
2989
+ interface Table { readonly __somewhereTable: unique symbol }
2990
+ interface TableMarker { readonly __somewhereTableMarker: unique symbol }
2991
+ interface ColumnMarker { readonly __somewhereColumnMarker: unique symbol }
2992
+ interface Relation { readonly __somewhereRelation: unique symbol }
2993
+ interface Scope { readonly __somewhereScope: unique symbol }
2994
+ interface OwnerScope extends Scope { readonly __somewhereOwner: unique symbol }
2995
+ interface MemberScope extends Scope { readonly __somewhereMember: unique symbol }
2996
+ interface ColumnOptions<Default> {
2997
+ nullable?: boolean; default?: Default | null; unique?: boolean;
2998
+ references?: string; onDelete?: 'cascade' | 'restrict'; renamedFrom?: string;
2999
+ }
3000
+ interface ClientPermissions<Field extends string> {
3001
+ identity?: 'authenticated' | 'visitor'; read?: boolean | Field[]; publicRead?: boolean | { where: Partial<Record<Field, string | number | boolean | null>> };
3002
+ create?: Field[] | false | null; update?: Field[] | false | null; delete?: boolean;
3003
+ }
3004
+ interface FileCollection { readonly __somewhereFileCollection: unique symbol }
3005
+ type FileOperation = 'read' | 'upload' | 'replace' | 'delete';
3006
+ interface FileCollectionOptions {
3007
+ path: string; scope: Scope; public?: boolean;
3008
+ client?: Partial<Record<FileOperation, boolean>>;
3009
+ limits?: { maxSize?: string | number; types?: string[] };
3010
+ }
3011
+ interface TableOptions<Field extends string> {
3012
+ scope?: Scope; client?: ClientPermissions<Field>; indexes?: string[][];
3013
+ unique?: string[][]; relations?: Record<string, Relation>;
3014
+ }
3015
+ }
3016
+ `;
3017
+ var signatures = `function schema(tables: Record<string, SomewhereSchemaDeclaration.Table | SomewhereSchemaDeclaration.TableMarker>, options?: { search?: SomewhereSchemaDeclaration.OwnerScope; files?: Record<string, SomewhereSchemaDeclaration.FileCollection> }): unknown;
3018
+ function table<Columns extends Record<string, SomewhereSchemaDeclaration.Column | SomewhereSchemaDeclaration.ColumnMarker>>(columns: Columns, options?: SomewhereSchemaDeclaration.TableOptions<Extract<keyof Columns, string>>): SomewhereSchemaDeclaration.Table;
3019
+ function id(options?: { uuid?: boolean }): SomewhereSchemaDeclaration.Column;
3020
+ function text(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
3021
+ function number(options?: SomewhereSchemaDeclaration.ColumnOptions<number>): SomewhereSchemaDeclaration.Column;
3022
+ function integer(options?: SomewhereSchemaDeclaration.ColumnOptions<number>): SomewhereSchemaDeclaration.Column;
3023
+ function boolean(options?: SomewhereSchemaDeclaration.ColumnOptions<boolean>): SomewhereSchemaDeclaration.Column;
3024
+ function timestamp(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
3025
+ function json(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
3026
+ function blob(options?: SomewhereSchemaDeclaration.ColumnOptions<never>): SomewhereSchemaDeclaration.Column;
3027
+ function owner(options?: { column?: string; visitors?: boolean }): SomewhereSchemaDeclaration.OwnerScope;
3028
+ function shared(): SomewhereSchemaDeclaration.Scope;
3029
+ function serverOnly(): SomewhereSchemaDeclaration.Scope;
3030
+ function member(options: { group: string | string[]; membership: string; member_user: string; member_group: string | string[]; operations?: Array<'read' | 'create' | 'update' | 'delete' | 'upload' | 'replace'> }): SomewhereSchemaDeclaration.MemberScope;
3031
+ function files(options: SomewhereSchemaDeclaration.FileCollectionOptions): SomewhereSchemaDeclaration.FileCollection;
3032
+ function hasMany(table: string, foreignKey: string): SomewhereSchemaDeclaration.Relation;
3033
+ function belongsTo(table: string, foreignKey: string): SomewhereSchemaDeclaration.Relation;
3034
+ function removed(): SomewhereSchemaDeclaration.ColumnMarker;
3035
+ function removedTable(): SomewhereSchemaDeclaration.TableMarker;
3036
+ function exported(): SomewhereSchemaDeclaration.TableMarker;
3037
+ `;
3038
+ var policySignatures = `function anyOf(owner: SomewhereSchemaDeclaration.OwnerScope, member: SomewhereSchemaDeclaration.MemberScope): SomewhereSchemaDeclaration.Scope;
3039
+ function anyOf(member: SomewhereSchemaDeclaration.MemberScope, owner: SomewhereSchemaDeclaration.OwnerScope): SomewhereSchemaDeclaration.Scope;
3040
+ function parent(options: { via: string }): SomewhereSchemaDeclaration.Scope;
3041
+ `;
3042
+ var SCHEMA_DECLARATION2 = types + signatures.replace(/^function /gm, "declare function ") + `declare module 'somewhere/db' {
3043
+ ${(signatures + policySignatures).replace(/^function /gm, " export function ")}}
3044
+ `;
3045
+ module2.exports = { SCHEMA_DECLARATION: SCHEMA_DECLARATION2 };
2755
3046
  }
2756
3047
  });
2757
3048
 
2758
3049
  // declared-data-vendor-entry.js
2759
3050
  var declared_data_vendor_entry_exports = {};
2760
3051
  __export(declared_data_vendor_entry_exports, {
2761
- RUNTIME_CONTEXT_DECLARATION: () => import_runtime_types.RUNTIME_CONTEXT_DECLARATION,
3052
+ ENDPOINT_DECLARATION: () => import_runtime_types2.ENDPOINT_DECLARATION,
3053
+ FILES_DECLARATION_FILE: () => import_typed_files2.CLIENT_FILE,
3054
+ RUNTIME_CONTEXT_DECLARATION: () => import_runtime_types2.RUNTIME_CONTEXT_DECLARATION,
2762
3055
  SCHEMA_DECLARATION: () => import_schema_types.SCHEMA_DECLARATION,
3056
+ declaredTablesFromFiles: () => declaredTablesFromFiles,
3057
+ filesDeclarationFromFiles: () => filesDeclarationFromFiles,
2763
3058
  generateFromFiles: () => generateFromFiles
2764
3059
  });
2765
3060
  module.exports = __toCommonJS(declared_data_vendor_entry_exports);
@@ -3109,6 +3404,295 @@ function validateDeclaredRelations(tables, tableByName, knownColumns, markedGone
3109
3404
  }
3110
3405
  }
3111
3406
 
3407
+ // worker/src/utils/db-schema-deploy/extract-schema-files.ts
3408
+ var FILE_OPERATIONS = ["read", "upload", "replace", "delete"];
3409
+ var MAX_FILE_COLLECTIONS = 20;
3410
+ var COLLECTION_NAME = /^[a-z][a-z0-9_]{0,63}$/;
3411
+ var COLUMN_NAME = /^[a-zA-Z_][a-zA-Z0-9_]{0,63}$/;
3412
+ var MIME_PATTERN = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/(\*|[a-z0-9][a-z0-9!#$&^_.+-]{0,126})$/i;
3413
+ var FILES_OPTIONS = ["path", "scope", "public", "client", "limits"];
3414
+ var LIMIT_KEYS = ["maxSize", "types"];
3415
+ var RESERVED_ROOT = /^\/(__sw|\.)/;
3416
+ var SIZE_UNITS = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3 };
3417
+ function fileCollectionRootError(name, path) {
3418
+ const bad = (why) => `File collection "${name}" has an invalid path: ${why}`;
3419
+ if (typeof path !== "string") return bad("path must be a quoted absolute folder such as '/uploads'.");
3420
+ if (!path.startsWith("/")) return bad(`"${path}" must start with "/".`);
3421
+ if (path === "/") return bad('a collection cannot cover the whole project ("/").');
3422
+ if (path.endsWith("/")) return bad(`"${path}" must not end with "/".`);
3423
+ if (/[*{}]/.test(path)) return bad(`"${path}" is a literal folder; wildcards and {placeholders} are not allowed.`);
3424
+ const segments = path.slice(1).split("/");
3425
+ if (segments.some((s) => s === "" || s === "." || s === "..")) return bad(`"${path}" must not contain empty, "." or ".." segments.`);
3426
+ if (segments.some((s) => s.length > 255 || /[\x00-\x1f\\]/.test(s))) return bad(`"${path}" contains an invalid folder name.`);
3427
+ if (RESERVED_ROOT.test(path)) return bad(`"${path}" is under a folder the platform reserves (names starting with "__sw" or ".").`);
3428
+ return null;
3429
+ }
3430
+ function parseFileSize(value) {
3431
+ if (typeof value === "number") return Number.isSafeInteger(value) && value > 0 ? value : null;
3432
+ if (typeof value !== "string") return null;
3433
+ const m = /^\s*(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)\s*$/i.exec(value);
3434
+ if (!m) return null;
3435
+ const bytes = Math.floor(Number(m[1]) * SIZE_UNITS[m[2].toLowerCase()]);
3436
+ return Number.isSafeInteger(bytes) && bytes > 0 ? bytes : null;
3437
+ }
3438
+ function readStringList(r, fail, ctx) {
3439
+ r.expectPunct("[", ctx);
3440
+ const out = [];
3441
+ while (!r.tryPunct("]")) {
3442
+ const t = r.next();
3443
+ if (t.kind !== "string") fail(t.line, `${ctx} must be a list of quoted strings.`);
3444
+ out.push(t.value);
3445
+ if (!r.tryPunct(",")) {
3446
+ r.expectPunct("]", `closing ${ctx}`);
3447
+ break;
3448
+ }
3449
+ }
3450
+ return out;
3451
+ }
3452
+ function readFileMember(r, fail, name, line) {
3453
+ r.expectPunct("(", 'after "member"');
3454
+ r.expectPunct("{", `in member() for file collection "${name}" \u2014 member() needs { group, membership, member_user, member_group }`);
3455
+ const seen = /* @__PURE__ */ new Map();
3456
+ let operations;
3457
+ while (!r.tryPunct("}")) {
3458
+ const key = r.readKey(`for a member() option of file collection "${name}"`);
3459
+ r.expectPunct(":", `after "${key.name}" in member() for file collection "${name}"`);
3460
+ if (key.name === "operations") {
3461
+ const verbs = readStringList(r, fail, `member({ operations }) on file collection "${name}"`);
3462
+ const bad = verbs.filter((v) => !FILE_OPERATIONS.includes(v));
3463
+ if (bad.length > 0) {
3464
+ fail(key.line, `member({ operations }) on file collection "${name}" names "${bad[0]}". File collections use the file verbs 'read', 'upload', 'replace' and 'delete'.`);
3465
+ }
3466
+ const set = new Set(verbs);
3467
+ if (!set.has("read") && (set.has("replace") || set.has("delete"))) {
3468
+ fail(key.line, `member({ operations }) on file collection "${name}" must include 'read' when it includes 'replace' or 'delete'.`);
3469
+ }
3470
+ operations = FILE_OPERATIONS.filter((op) => set.has(op));
3471
+ } else if (["group", "membership", "member_user", "member_group"].includes(key.name)) {
3472
+ const t = r.next();
3473
+ if (t.kind !== "string") {
3474
+ fail(t.line, `member({ ${key.name} }) on file collection "${name}" must be one quoted name. A file collection's group is a single key.`);
3475
+ }
3476
+ if (!COLUMN_NAME.test(t.value)) fail(t.line, `member({ ${key.name} }) on file collection "${name}" names an invalid column "${t.value}".`);
3477
+ if (seen.has(key.name)) fail(key.line, `member() on file collection "${name}" declares ${key.name} twice.`);
3478
+ seen.set(key.name, t.value.toLowerCase());
3479
+ } else {
3480
+ fail(key.line, `member() on file collection "${name}" has an unknown option "${key.name}". Allowed: group, membership, member_user, member_group, operations.`);
3481
+ }
3482
+ if (!r.tryPunct(",")) {
3483
+ r.expectPunct("}", `closing member() for file collection "${name}"`);
3484
+ break;
3485
+ }
3486
+ }
3487
+ r.tryPunct(",");
3488
+ r.expectPunct(")", "closing member()");
3489
+ const missing = ["group", "membership", "member_user", "member_group"].filter((k) => !seen.has(k));
3490
+ if (missing.length > 0) {
3491
+ fail(line, `member() on file collection "${name}" is missing ${missing.join(", ")}. It needs member({ group, membership, member_user, member_group }).`);
3492
+ }
3493
+ return {
3494
+ group: seen.get("group"),
3495
+ membership: seen.get("membership"),
3496
+ memberUser: seen.get("member_user"),
3497
+ memberGroup: seen.get("member_group"),
3498
+ ...operations && operations.length < FILE_OPERATIONS.length ? { operations } : {}
3499
+ };
3500
+ }
3501
+ function readFileOwner(r, fail, name) {
3502
+ r.expectPunct("(", 'after "owner"');
3503
+ if (r.tryPunct(")")) return;
3504
+ r.expectPunct("{", `in owner() for file collection "${name}"`);
3505
+ const key = r.readKey(`in owner() for file collection "${name}"`);
3506
+ if (key.name === "visitors") {
3507
+ fail(key.line, `owner({ visitors: true }) is not available for file collections yet: an upload needs a signed-in account. Use owner().`);
3508
+ }
3509
+ fail(key.line, `owner() on file collection "${name}" takes no options \u2014 the owner is always the signed-in uploader.`);
3510
+ }
3511
+ function readFileScope(r, fail, name) {
3512
+ const tok = r.expectIdent(`for the scope of file collection "${name}"`);
3513
+ if (tok.value === "parent") {
3514
+ fail(tok.line, `parent() is not available for file collections yet. Use owner(), member(), anyOf(owner(), member()), shared(), or serverOnly().`);
3515
+ }
3516
+ if (tok.value === "owner") {
3517
+ readFileOwner(r, fail, name);
3518
+ return { kind: "owner" };
3519
+ }
3520
+ if (tok.value === "member") return { kind: "member", member: readFileMember(r, fail, name, tok.line) };
3521
+ if (tok.value === "shared" || tok.value === "serverOnly") {
3522
+ r.expectPunct("(", `after "${tok.value}"`);
3523
+ if (!r.tryPunct(")")) fail(tok.line, `${tok.value}() on file collection "${name}" takes no options.`);
3524
+ return tok.value === "shared" ? { kind: "shared" } : { kind: "server_only" };
3525
+ }
3526
+ if (tok.value === "anyOf") {
3527
+ r.expectPunct("(", 'after "anyOf"');
3528
+ let member = null;
3529
+ let owners = 0;
3530
+ for (let i = 0; i < 2; i++) {
3531
+ const inner = r.expectIdent(`inside anyOf() for file collection "${name}"`);
3532
+ if (inner.value === "owner") {
3533
+ readFileOwner(r, fail, name);
3534
+ owners++;
3535
+ } else if (inner.value === "member" && !member) member = readFileMember(r, fail, name, inner.line);
3536
+ else fail(inner.line, `anyOf() on file collection "${name}" requires exactly one owner() and one member().`);
3537
+ if (i === 0) r.expectPunct(",", `between the two policies in anyOf() on file collection "${name}"`);
3538
+ }
3539
+ r.tryPunct(",");
3540
+ r.expectPunct(")", "closing anyOf()");
3541
+ if (owners !== 1 || !member) fail(tok.line, `anyOf() on file collection "${name}" requires exactly one owner() and one member().`);
3542
+ return { kind: "owner_or_member", member };
3543
+ }
3544
+ return fail(tok.line, `unknown scope "${tok.value}" on file collection "${name}". Use owner(), member(), anyOf(owner(), member()), shared(), or serverOnly().`);
3545
+ }
3546
+ function readCollection(r, fail, name, errors) {
3547
+ const callee = r.expectIdent(`for file collection "${name}"`);
3548
+ if (callee.value !== "files") fail(callee.line, `file collection "${name}" must be declared with files({ \u2026 }).`);
3549
+ r.expectPunct("(", 'after "files"');
3550
+ r.expectPunct("{", `opening files() options for "${name}"`);
3551
+ const seen = /* @__PURE__ */ new Set();
3552
+ let path;
3553
+ let scope = null;
3554
+ let isPublic = false;
3555
+ const client = { read: false, upload: false, replace: false, delete: false };
3556
+ const limits = { maxSize: null, types: [] };
3557
+ while (!r.tryPunct("}")) {
3558
+ const key = r.readKey(`for a files() option of "${name}"`);
3559
+ if (!FILES_OPTIONS.includes(key.name)) {
3560
+ fail(key.line, `files() for "${name}" has an unknown option "${key.name}". Allowed: ${FILES_OPTIONS.join(", ")}.`);
3561
+ }
3562
+ if (seen.has(key.name)) fail(key.line, `files() for "${name}" declares ${key.name} twice.`);
3563
+ seen.add(key.name);
3564
+ r.expectPunct(":", `after "${key.name}" in files() for "${name}"`);
3565
+ if (key.name === "path") path = r.readLiteral(`for files({ path }) of "${name}"`);
3566
+ else if (key.name === "scope") scope = readFileScope(r, fail, name);
3567
+ else if (key.name === "public") {
3568
+ const value = r.readLiteral(`for files({ public }) of "${name}"`);
3569
+ if (typeof value !== "boolean") fail(key.line, `files({ public }) for "${name}" must be true or false.`);
3570
+ isPublic = value;
3571
+ } else if (key.name === "client") {
3572
+ r.expectPunct("{", `opening client for file collection "${name}"`);
3573
+ while (!r.tryPunct("}")) {
3574
+ const op = r.readKey(`for a client operation of file collection "${name}"`);
3575
+ if (!FILE_OPERATIONS.includes(op.name)) {
3576
+ fail(op.line, `client for file collection "${name}" has an unknown operation "${op.name}". Allowed: ${FILE_OPERATIONS.join(", ")}.`);
3577
+ }
3578
+ r.expectPunct(":", `after client.${op.name}`);
3579
+ const value = r.readLiteral(`for client.${op.name} of "${name}"`);
3580
+ if (typeof value !== "boolean") fail(op.line, `client.${op.name} for file collection "${name}" must be true or false.`);
3581
+ client[op.name] = value;
3582
+ if (!r.tryPunct(",")) {
3583
+ r.expectPunct("}", `closing client for "${name}"`);
3584
+ break;
3585
+ }
3586
+ }
3587
+ } else {
3588
+ r.expectPunct("{", `opening limits for file collection "${name}"`);
3589
+ while (!r.tryPunct("}")) {
3590
+ const limit = r.readKey(`for a limit of file collection "${name}"`);
3591
+ if (!LIMIT_KEYS.includes(limit.name)) {
3592
+ fail(limit.line, `limits for file collection "${name}" has an unknown key "${limit.name}". Allowed: ${LIMIT_KEYS.join(", ")}.`);
3593
+ }
3594
+ r.expectPunct(":", `after limits.${limit.name}`);
3595
+ if (limit.name === "maxSize") {
3596
+ const raw = r.readLiteral(`for limits.maxSize of "${name}"`);
3597
+ limits.maxSize = parseFileSize(raw);
3598
+ if (limits.maxSize === null) errors.push(`File collection "${name}" has an invalid limits.maxSize: use a whole number of bytes or a size such as '25MB'.`);
3599
+ } else {
3600
+ limits.types = readStringList(r, fail, `limits.types of file collection "${name}"`).map((t) => t.toLowerCase());
3601
+ for (const type of limits.types) {
3602
+ if (!MIME_PATTERN.test(type)) errors.push(`File collection "${name}" has an invalid limits.types entry "${type}": use a content type such as 'image/png' or 'image/*'.`);
3603
+ }
3604
+ }
3605
+ if (!r.tryPunct(",")) {
3606
+ r.expectPunct("}", `closing limits for "${name}"`);
3607
+ break;
3608
+ }
3609
+ }
3610
+ }
3611
+ if (!r.tryPunct(",")) {
3612
+ r.expectPunct("}", `closing files() for "${name}"`);
3613
+ break;
3614
+ }
3615
+ }
3616
+ r.tryPunct(",");
3617
+ r.expectPunct(")", "closing files()");
3618
+ if (!scope) return fail(callee.line, `files() for "${name}" needs a scope: owner(), member(), anyOf(owner(), member()), shared(), or serverOnly().`);
3619
+ const rootError = fileCollectionRootError(name, path);
3620
+ if (rootError) errors.push(rootError);
3621
+ const declaresClient = FILE_OPERATIONS.some((op) => client[op]);
3622
+ if (scope.kind === "server_only" && declaresClient) {
3623
+ errors.push(`File collection "${name}" is serverOnly() and cannot declare client operations. Remove client, or choose a scope the browser may use.`);
3624
+ }
3625
+ if (isPublic && client.read) {
3626
+ errors.push(`File collection "${name}" is public: true and also declares client.read. Public already lets anyone read; remove client.read.`);
3627
+ }
3628
+ if (isPublic && (scope.kind === "member" || scope.kind === "owner_or_member")) {
3629
+ errors.push(`File collection "${name}" is public: true on a member() scope, which would let anyone read team files. Remove public, or use owner() or serverOnly().`);
3630
+ }
3631
+ return { name, path: typeof path === "string" ? path : "", scope, public: isPublic, client, limits };
3632
+ }
3633
+ function readFileCollections(r, fail, errors) {
3634
+ r.expectPunct("{", "opening schema option files");
3635
+ const out = [];
3636
+ while (!r.tryPunct("}")) {
3637
+ const key = r.readKey("for a file collection name");
3638
+ if (!COLLECTION_NAME.test(key.name)) {
3639
+ errors.push(`File collection name "${key.name}" is invalid. Use lowercase letters, digits and underscores, starting with a letter.`);
3640
+ }
3641
+ if (out.some((c) => c.name === key.name)) fail(key.line, `file collection "${key.name}" is declared twice.`);
3642
+ r.expectPunct(":", `after file collection "${key.name}"`);
3643
+ out.push(readCollection(r, fail, key.name, errors));
3644
+ if (!r.tryPunct(",")) {
3645
+ r.expectPunct("}", "closing schema option files");
3646
+ break;
3647
+ }
3648
+ }
3649
+ if (out.length > MAX_FILE_COLLECTIONS) errors.push(`db/schema.ts declares ${out.length} file collections (max ${MAX_FILE_COLLECTIONS}).`);
3650
+ return out.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
3651
+ }
3652
+ function validateFileCollections(collections, table, errors) {
3653
+ for (const a of collections) {
3654
+ for (const b of collections) {
3655
+ if (a === b || !a.path || !b.path) continue;
3656
+ if (a.path === b.path && a.name < b.name) errors.push(`File collections "${a.name}" and "${b.name}" both use the path "${a.path}". Each collection needs its own folder.`);
3657
+ else if (b.path.startsWith(a.path + "/")) errors.push(`File collection "${b.name}" (${b.path}) is nested inside "${a.name}" (${a.path}). Collection folders cannot overlap.`);
3658
+ }
3659
+ if (a.scope.kind !== "member" && a.scope.kind !== "owner_or_member") continue;
3660
+ const m = a.scope.member;
3661
+ const membership = table(m.membership);
3662
+ if (!membership) {
3663
+ errors.push(`File collection "${a.name}" is member-scoped through the membership table "${m.membership}", which is not declared in db/schema.ts. The membership table must be a managed table in the same schema.`);
3664
+ continue;
3665
+ }
3666
+ if (membership.clientWrites) {
3667
+ errors.push(`Table "${m.membership}" grants membership access to file collection "${a.name}" and cannot allow client writes. Change membership through a server function.`);
3668
+ }
3669
+ if (!membership.columns.has(m.memberUser)) errors.push(`The membership table "${m.membership}" (for file collection "${a.name}") has no "${m.memberUser}" column named in member({ member_user }).`);
3670
+ if (!membership.columns.has(m.memberGroup)) errors.push(`The membership table "${m.membership}" (for file collection "${a.name}") has no "${m.memberGroup}" column named in member({ member_group }).`);
3671
+ }
3672
+ }
3673
+ function canonicalFileCollectionsJson(collections) {
3674
+ return JSON.stringify({
3675
+ v: 1,
3676
+ collections: [...collections].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map((c) => ({
3677
+ name: c.name,
3678
+ path: c.path,
3679
+ scope: c.scope.kind === "member" || c.scope.kind === "owner_or_member" ? {
3680
+ kind: c.scope.kind,
3681
+ member: {
3682
+ group: c.scope.member.group,
3683
+ membership: c.scope.member.membership,
3684
+ memberUser: c.scope.member.memberUser,
3685
+ memberGroup: c.scope.member.memberGroup,
3686
+ ...c.scope.member.operations ? { operations: c.scope.member.operations } : {}
3687
+ }
3688
+ } : { kind: c.scope.kind },
3689
+ public: c.public,
3690
+ client: { read: c.client.read, upload: c.client.upload, replace: c.client.replace, delete: c.client.delete },
3691
+ limits: { maxSize: c.limits.maxSize, types: [...c.limits.types] }
3692
+ }))
3693
+ });
3694
+ }
3695
+
3112
3696
  // worker/src/utils/db-schema-deploy/extract-schema-ts.ts
3113
3697
  function policyOwner(policy) {
3114
3698
  if (policy.kind === "owner") return policy;
@@ -3394,7 +3978,7 @@ function readColumn(r, tableName, rawName, line) {
3394
3978
  }
3395
3979
  if (TABLE_MARKER_HELPERS.has(helper)) {
3396
3980
  throw new SchemaTsError(
3397
- `line ${helperTok.line}: "${tableName}"."${rawName}" uses ${helper}(), which marks a whole table. To remove this column, mark it ${rawName}: removed().`
3981
+ `line ${helperTok.line}: "${tableName}"."${rawName}" uses ${helper}(), which marks a whole table, not a column. Removing columns from the live database is not supported yet, so keep "${rawName}" declared with its column type.`
3398
3982
  );
3399
3983
  }
3400
3984
  if (!COLUMN_HELPERS.has(helper)) {
@@ -4039,22 +4623,34 @@ function extractSchemaTs(source) {
4039
4623
  }
4040
4624
  }
4041
4625
  let managedSearch;
4626
+ let fileCollections;
4627
+ const fileErrors = [];
4042
4628
  if (reader.tryPunct(",") && !(reader.peek().kind === "punct" && reader.peek().value === ")")) {
4043
4629
  reader.expectPunct("{", "opening schema options");
4044
- const option = reader.readKey("for a schema option");
4045
- if (option.name !== "search") {
4046
- throw new SchemaTsError(`line ${option.line}: unknown schema option "${option.name}". The only schema-level option is search: owner().`);
4047
- }
4048
- reader.expectPunct(":", 'after schema option "search"');
4049
- const helper = reader.expectIdent('for schema option "search"');
4050
- if (helper.value !== "owner") {
4051
- throw new SchemaTsError(`line ${helper.line}: search ownership must be declared with owner().`);
4630
+ while (!reader.tryPunct("}")) {
4631
+ const option = reader.readKey("for a schema option");
4632
+ if (option.name === "files" && !fileCollections) {
4633
+ reader.expectPunct(":", 'after schema option "files"');
4634
+ fileCollections = readFileCollections(reader, (line, message) => {
4635
+ throw new SchemaTsError(`line ${line}: ${message}`);
4636
+ }, fileErrors);
4637
+ } else if (option.name === "search" && !managedSearch) {
4638
+ reader.expectPunct(":", 'after schema option "search"');
4639
+ const helper = reader.expectIdent('for schema option "search"');
4640
+ if (helper.value !== "owner") {
4641
+ throw new SchemaTsError(`line ${helper.line}: search ownership must be declared with owner().`);
4642
+ }
4643
+ reader.expectPunct("(", "after search: owner");
4644
+ reader.expectPunct(")", "closing search: owner()");
4645
+ managedSearch = { kind: "owner" };
4646
+ } else {
4647
+ throw new SchemaTsError(`line ${option.line}: unknown schema option "${option.name}". The schema-level options are search: owner() and files: { \u2026 }, each at most once.`);
4648
+ }
4649
+ if (!reader.tryPunct(",")) {
4650
+ reader.expectPunct("}", "closing schema options");
4651
+ break;
4652
+ }
4052
4653
  }
4053
- reader.expectPunct("(", "after search: owner");
4054
- reader.expectPunct(")", "closing search: owner()");
4055
- managedSearch = { kind: "owner" };
4056
- reader.tryPunct(",");
4057
- reader.expectPunct("}", "closing schema options");
4058
4654
  reader.tryPunct(",");
4059
4655
  }
4060
4656
  reader.expectPunct(")", "closing schema(\u2026)");
@@ -4064,7 +4660,7 @@ function extractSchemaTs(source) {
4064
4660
  `db/schema.ts must end after "export default schema({ \u2026 })" \u2014 found ${describeToken(reader.peek())}. The file holds only the schema declaration.`
4065
4661
  );
4066
4662
  }
4067
- if (tables.length === 0 && removedTables.length === 0 && exportedTables.length === 0 && !managedSearch) {
4663
+ if (tables.length === 0 && removedTables.length === 0 && exportedTables.length === 0 && !managedSearch && !fileCollections?.length) {
4068
4664
  return { ok: false, errors: ["db/schema.ts declares no tables. Declare at least one table, or remove the file to leave the database unmanaged."] };
4069
4665
  }
4070
4666
  if (tables.length > MAX_TABLES) {
@@ -4166,6 +4762,13 @@ function extractSchemaTs(source) {
4166
4762
  }
4167
4763
  }
4168
4764
  validateDeclaredRelations(tables, tableByName, knownColumns, (table) => removedTables.includes(table) ? "removedTable()" : exportedTables.includes(table) ? "exported()" : null, errors);
4765
+ if (fileCollections) {
4766
+ errors.push(...fileErrors);
4767
+ validateFileCollections(fileCollections, (name) => {
4768
+ const t = tableByName.get(name);
4769
+ return t ? { columns: knownColumns(t), clientWrites: Boolean(t.client && (t.client.create !== null || t.client.update !== null || t.client.delete)) } : null;
4770
+ }, errors);
4771
+ }
4169
4772
  if (errors.length > 0) return { ok: false, errors };
4170
4773
  return {
4171
4774
  ok: true,
@@ -4173,7 +4776,8 @@ function extractSchemaTs(source) {
4173
4776
  tables,
4174
4777
  removedTables: removedTables.sort(),
4175
4778
  exportedTables: exportedTables.sort(),
4176
- ...managedSearch ? { search: managedSearch } : {}
4779
+ ...managedSearch ? { search: managedSearch } : {},
4780
+ ...fileCollections ? { files: fileCollections } : {}
4177
4781
  }
4178
4782
  };
4179
4783
  } catch (err) {
@@ -4248,18 +4852,38 @@ function schemaAuthorityFromSource(files) {
4248
4852
  }
4249
4853
  return { schema, intents, scopes };
4250
4854
  }
4855
+ function declaredFilesFromSource(files) {
4856
+ const source = files["db/schema.ts"];
4857
+ if (source === void 0) return void 0;
4858
+ const parsed = extractSchemaTs(source);
4859
+ return parsed.ok && parsed.declaration.files?.length ? canonicalFileCollectionsJson(parsed.declaration.files) : void 0;
4860
+ }
4251
4861
 
4252
4862
  // declared-data-vendor-entry.js
4253
4863
  var import_typed_data = __toESM(require_typed_data());
4254
- var import_schema_types = __toESM(require_schema_types());
4255
4864
  var import_runtime_types = __toESM(require_runtime_types());
4865
+ var import_typed_files = __toESM(require_typed_files());
4866
+ var import_schema_types = __toESM(require_schema_types());
4867
+ var import_runtime_types2 = __toESM(require_runtime_types());
4868
+ var import_typed_files2 = __toESM(require_typed_files());
4256
4869
  function generateFromFiles(files) {
4257
4870
  const authority = clientAuthorityFromSource(files);
4258
4871
  return authority ? (0, import_typed_data.generateDataClient)(authority) : void 0;
4259
4872
  }
4873
+ function declaredTablesFromFiles(files) {
4874
+ return (0, import_runtime_types.declaredTablesDeclaration)(schemaAuthorityFromSource(files));
4875
+ }
4876
+ function filesDeclarationFromFiles(files) {
4877
+ const canonical = declaredFilesFromSource(files);
4878
+ return canonical ? (0, import_typed_files.describeFilesClient)(canonical).declaration : void 0;
4879
+ }
4260
4880
  // Annotate the CommonJS export names for ESM import in node:
4261
4881
  0 && (module.exports = {
4882
+ ENDPOINT_DECLARATION,
4883
+ FILES_DECLARATION_FILE,
4262
4884
  RUNTIME_CONTEXT_DECLARATION,
4263
4885
  SCHEMA_DECLARATION,
4886
+ declaredTablesFromFiles,
4887
+ filesDeclarationFromFiles,
4264
4888
  generateFromFiles
4265
4889
  });