@somewhere-tech/cli 0.34.3 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/browser.js.map +1 -1
- package/dist/commands/init.js +135 -16
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/status.js +2 -13
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/verify.js +526 -4
- package/dist/commands/verify.js.map +1 -1
- package/dist/lib/browser-actions.js +11 -1
- package/dist/lib/browser-actions.js.map +1 -1
- package/dist/lib/browser-run.js +14 -0
- package/dist/lib/browser-run.js.map +1 -1
- package/dist/lib/declared-data.js +41 -51
- package/dist/lib/declared-data.js.map +1 -1
- package/dist/lib/files.js +3 -2
- package/dist/lib/files.js.map +1 -1
- package/dist/lib/init-agent-guide.js +51 -29
- package/dist/lib/init-agent-guide.js.map +1 -1
- package/dist/lib/init-feature-template.js +1397 -0
- package/dist/lib/init-feature-template.js.map +1 -0
- package/dist/lib/init-features.js +120 -0
- package/dist/lib/init-features.js.map +1 -0
- package/dist/lib/init-green-template.js +1 -1
- package/dist/lib/init-scaffold.js +13 -5
- package/dist/lib/init-scaffold.js.map +1 -1
- package/dist/lib/init-template.js +7 -7
- package/dist/lib/typecheck.js +7 -2
- package/dist/lib/typecheck.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/DECLARED-DATA-VENDOR.json +7 -5
- package/runtime/declared-data.cjs +722 -98
- package/dist/lib/init-auth-template.js +0 -230
- package/dist/lib/init-auth-template.js.map +0 -1
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
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
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
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
|
});
|