@somewhere-cloud/cli 0.38.19 → 0.38.21
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/CHANGELOG.md +28 -0
- package/README.md +1 -0
- package/dist/commands/auth.js +128 -1
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/lint.js +3 -0
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/typecheck.js +3 -0
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/lib/http.js +12 -0
- package/dist/lib/http.js.map +1 -1
- package/dist/lib/platform-contract.js +86 -0
- package/dist/lib/platform-contract.js.map +1 -0
- package/dist/lib/source-lint.js +6 -1
- package/dist/lib/source-lint.js.map +1 -1
- package/dist/lib/typecheck.js +11 -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 +12 -11
- package/runtime/PLATFORM-SOURCE.json +1 -1
- package/runtime/SOURCE-LINT-VENDOR.json +37 -36
- package/runtime/declared-data.cjs +284 -40
- package/runtime/source-lint.cjs +247 -166
|
@@ -67,11 +67,12 @@ var require_declared_data_contract = __commonJS({
|
|
|
67
67
|
const normalizedOperations = o?.length === 4 ? void 0 : o;
|
|
68
68
|
return { k: "m", m: { g: [...g], m, u, mg: [...mg], ...normalizedOperations !== void 0 ? { o: [...normalizedOperations] } : {}, ...og ? { og: true } : {} } };
|
|
69
69
|
}
|
|
70
|
-
if (value.k === "a" && keys === "k,p" && Array.isArray(value.p) && value.p.length === 2) {
|
|
70
|
+
if (value.k === "a" && (keys === "k,p" || keys === "k,p,reads" && edges === 0) && Array.isArray(value.p) && value.p.length === 2) {
|
|
71
71
|
const left = policy(value.p[0], label), right = policy(value.p[1], label);
|
|
72
72
|
if (left.k !== "o" || !["m", "g", "r"].includes(right.k)) fail(label);
|
|
73
73
|
if (right.k === "m" && right.m.og && left.c !== "user_id") fail(label);
|
|
74
|
-
|
|
74
|
+
const reads = value.reads === void 0 ? void 0 : branchReads(value.reads, label);
|
|
75
|
+
return { k: "a", p: [left, right], ...reads ? { reads } : {} };
|
|
75
76
|
}
|
|
76
77
|
if (value.k === "n" && keys === "g,k,w") {
|
|
77
78
|
const gate = policy(value.g, label), who = policy(value.w, label);
|
|
@@ -99,6 +100,23 @@ var require_declared_data_contract = __commonJS({
|
|
|
99
100
|
}
|
|
100
101
|
fail(label);
|
|
101
102
|
}
|
|
103
|
+
function branchReads(value, label) {
|
|
104
|
+
if (!Array.isArray(value) || value.length !== 2 || value[0] === null && value[1] === null) fail(label + ".reads");
|
|
105
|
+
return value.map((list) => {
|
|
106
|
+
if (list === null) return null;
|
|
107
|
+
if (!Array.isArray(list) || !list.length || list.some((name) => typeof name !== "string" || !identifier.test(name)) || new Set(list).size !== list.length) fail(label + ".reads");
|
|
108
|
+
return [...list].sort();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function hiddenByBranch(table) {
|
|
112
|
+
const reads = table.policy && table.policy.k === "a" ? table.policy.reads : void 0;
|
|
113
|
+
if (!reads) return /* @__PURE__ */ new Set();
|
|
114
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
115
|
+
for (const list of reads) if (list) {
|
|
116
|
+
for (const column of table.columns) if (!list.includes(column.n)) hidden.add(column.n);
|
|
117
|
+
}
|
|
118
|
+
return hidden;
|
|
119
|
+
}
|
|
102
120
|
function policyRoles(value, label) {
|
|
103
121
|
record2(value, label + ".roles");
|
|
104
122
|
if (Object.keys(value).sort().join(",") !== "create,delete,read,update") fail(label);
|
|
@@ -336,10 +354,10 @@ var require_declared_data_contract = __commonJS({
|
|
|
336
354
|
const type = { integer: "number", bigint: "string", number: "number", text: "string", timestamp: "string", boolean: "boolean", json: "Json", blob: "number[]" }[column.t];
|
|
337
355
|
return column.nul ? `${type} | null` : type;
|
|
338
356
|
}
|
|
339
|
-
function shape(columns, names, mode, required = /* @__PURE__ */ new Set()) {
|
|
357
|
+
function shape(columns, names, mode, required = /* @__PURE__ */ new Set(), hidden = /* @__PURE__ */ new Set()) {
|
|
340
358
|
const values = names.map((name) => {
|
|
341
359
|
const column = columns.find((candidate) => candidate.n === name);
|
|
342
|
-
const optional = mode === "update" || mode === "create" && !required.has(name) && (column.d || column.nul);
|
|
360
|
+
const optional = mode === "update" || mode === "create" && !required.has(name) && (column.d || column.nul) || mode === "read" && hidden.has(name);
|
|
343
361
|
return `${JSON.stringify(name)}${optional ? "?" : ""}: ${columnType(column)}`;
|
|
344
362
|
});
|
|
345
363
|
return values.length ? `{ ${values.join("; ")} }` : "Record<string, never>";
|
|
@@ -503,13 +521,14 @@ async function intake(table, reference, values, extra) {
|
|
|
503
521
|
export interface IntakeResult { readonly accepted: true; readonly idempotency: InsertReceipt; readonly request: InsertRequest<IntakeResult> }
|
|
504
522
|
`;
|
|
505
523
|
var ROW_CAN_DECLARATION = " /** Row capability hint (list/get with can: true): whether the signed-in caller may update or delete this row now, by the same rule the write enforces. A hint for UI; the write is still the final answer. */\n export interface RowCan { readonly update: boolean; readonly delete: boolean; }\n";
|
|
524
|
+
var ROW_HINTS_DECLARATION = ROW_CAN_DECLARATION + ' /** Row hints (list/get with can: true): can says what the signed-in caller may change; mine is true when this caller created the row (the owner or author the platform recorded). Show edit and delete controls, a "you" label or a "mine" filter from these; the identity column itself is never sent. */\n export interface RowHints { readonly can: RowCan; readonly mine: boolean; }\n';
|
|
506
525
|
var INSERT_CLIENT_DECLARATIONS = `
|
|
507
526
|
export interface InsertReceipt { readonly replayed: boolean; readonly created_at: number; readonly result_until: number; readonly refuse_until: number; }
|
|
508
527
|
export interface InsertRequest<T> { readonly key: string; readonly createdAt: number; retry(): Promise<T>; }
|
|
509
528
|
export type InsertResult<T> = T & { readonly idempotency: InsertReceipt; readonly request: InsertRequest<InsertResult<T>> };
|
|
510
529
|
`;
|
|
511
|
-
var CLIENT_GENERATION2 =
|
|
512
|
-
var ACCEPTED_CLIENT_GENERATIONS2 = Object.freeze([3, 2, 1]);
|
|
530
|
+
var CLIENT_GENERATION2 = 5;
|
|
531
|
+
var ACCEPTED_CLIENT_GENERATIONS2 = Object.freeze([5, 4, 3, 2, 1]);
|
|
513
532
|
var PUBLIC_BOOTSTRAP_CLIENT_RUNTIME = 'const swCanon=(v,d)=>{if(v===null||typeof v==="string"||typeof v==="boolean"||(typeof v==="number"&&Number.isFinite(v)))return JSON.stringify(v);if(d===0||typeof v!=="object"||Array.isArray(v))return null;const parts=[];for(const k of Object.keys(v).sort()){if(v[k]===undefined)continue;const e=swCanon(v[k],d-1);if(e===null)return null;parts.push(JSON.stringify(k)+":"+e)}return"{"+parts.join(",")+"}"};let swBoot;const swBootTake=body=>{try{if(swBoot===undefined){swBoot=null;const el=typeof document!=="undefined"&&document.getElementById?document.getElementById("__sw_public_data"):null;if(el&&el.type==="application/json"&&(el.parentNode===document.head||el.parentNode===document.body)){const p=JSON.parse(el.textContent||"null");if(p&&p.v===1&&p.contract===contract&&Number.isSafeInteger(p.validated_at)&&p.validated_at<=Date.now()&&Number.isInteger(p.max_age)&&p.max_age>0&&p.max_age<=120&&p.entries&&typeof p.entries==="object"&&!Array.isArray(p.entries)&&typeof p.path==="string"&&typeof location!=="undefined")swBoot={until:p.validated_at+p.max_age*1000,path:p.path,entries:new Map(Object.entries(p.entries))}}if(el&&el.parentNode)el.parentNode.removeChild(el)}if(!swBoot)return undefined;if(Date.now()>=swBoot.until||location.pathname!==swBoot.path){swBoot=null;return undefined}const key=swCanon(body,2);if(key===null||!swBoot.entries.has(key))return undefined;const value=swBoot.entries.get(key);swBoot.entries.delete(key);return value}catch(error){swBoot=null;return undefined}};';
|
|
514
533
|
var PUBLIC_BOOTSTRAP_INVOKE = 'if(operation==="list"||operation==="get"||operation==="aggregate"||operation==="relation_list"){const booted=swBootTake({...input,contract,table,operation});if(booted!==undefined)return booted}else swBoot=null;';
|
|
515
534
|
function describeDataClient2(authority, contract_digest, insertReceiptCapability, localTypes = false, generation = CLIENT_GENERATION2) {
|
|
@@ -529,17 +548,19 @@ async function intake(table, reference, values, extra) {
|
|
|
529
548
|
for (const table of tables) {
|
|
530
549
|
const { name, columns, client, primaryKey } = table;
|
|
531
550
|
const readable = client.read === true ? columns.filter((column) => column.n !== table.author).map((column) => column.n) : client.read || [];
|
|
532
|
-
const
|
|
551
|
+
const hidden = generation >= 5 ? hiddenByBranch(table) : /* @__PURE__ */ new Set();
|
|
552
|
+
const row = shape(columns, readable, "read", /* @__PURE__ */ new Set(), hidden);
|
|
533
553
|
const id = columnType(columns.find((column) => column.n === primaryKey));
|
|
534
554
|
const operations = [];
|
|
535
555
|
const methods = [];
|
|
536
556
|
if (client.read !== false) {
|
|
537
|
-
const equality = readable.filter((field) => !["json", "blob"].includes(columns.find((column) => column.n === field).t));
|
|
557
|
+
const equality = readable.filter((field) => !["json", "blob"].includes(columns.find((column) => column.n === field).t) && !hidden.has(field));
|
|
538
558
|
if (generation >= 2) {
|
|
539
559
|
const listOptions = `limit?: L & (number extends L ? unknown : [L] extends [PageSize] ? unknown : never); after?: ${id}; where?: ${shape(columns, equality, "update")}`;
|
|
540
|
-
|
|
560
|
+
const hinted = generation >= 4 ? "RowHints" : "{ can: RowCan }";
|
|
561
|
+
operations.push(`list<const L extends number = number>(options: { ${listOptions}; can: true }): Promise<{ data: (${row} & ${hinted})[]; next: ${id} | null; has_more: boolean }>`);
|
|
541
562
|
operations.push(`list<const L extends number = number>(options?: { ${listOptions}; can?: false }): Promise<{ data: ${row}[]; next: ${id} | null; has_more: boolean }>`);
|
|
542
|
-
operations.push(`get(id: ${id}, options: { can: true }): Promise<{ data: (${row} & {
|
|
563
|
+
operations.push(`get(id: ${id}, options: { can: true }): Promise<{ data: (${row} & ${hinted}) | null }>`);
|
|
543
564
|
operations.push(`get(id: ${id}, options?: { can?: false }): Promise<{ data: ${row} | null }>`);
|
|
544
565
|
} else {
|
|
545
566
|
operations.push(`list<const L extends number = number>(options?: { limit?: L & (number extends L ? unknown : [L] extends [PageSize] ? unknown : never); after?: ${id}; where?: ${shape(columns, equality, "update")} }): Promise<{ data: ${row}[]; next: ${id} | null; has_more: boolean }>`);
|
|
@@ -676,7 +697,7 @@ async function intake(table, reference, values, extra) {
|
|
|
676
697
|
const declaration = `declare module ${JSON.stringify("somewhere:data")} {
|
|
677
698
|
type PageSize = ${Array.from({ length: 100 }, (_, i) => i + 1).join(" | ")};
|
|
678
699
|
export type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
|
|
679
|
-
${generation >= 2 ? ROW_CAN_DECLARATION : ""}${receiptCapable ? localTypes ? INSERT_CLIENT_DECLARATIONS.replace("readonly idempotency:", "readonly idempotency?:").replace("readonly request:", "readonly request?:") : INSERT_CLIENT_DECLARATIONS : ""}${anonymousTables.length ? ANONYMOUS_CLIENT_DECLARATIONS : ""}${intakeTables.length ? INTAKE_CLIENT_DECLARATIONS : ""} export class DataError extends Error { readonly status: number; readonly code: string | null;${receiptCapable ? " readonly request?: InsertRequest<unknown>; readonly outcome?: string; readonly write_committed?: boolean; readonly retry?: false; readonly retryable?: false; readonly idempotency?: InsertReceipt;" : ""} constructor(status: number, payload: unknown); }
|
|
700
|
+
${generation >= 4 ? ROW_HINTS_DECLARATION : generation >= 2 ? ROW_CAN_DECLARATION : ""}${receiptCapable ? localTypes ? INSERT_CLIENT_DECLARATIONS.replace("readonly idempotency:", "readonly idempotency?:").replace("readonly request:", "readonly request?:") : INSERT_CLIENT_DECLARATIONS : ""}${anonymousTables.length ? ANONYMOUS_CLIENT_DECLARATIONS : ""}${intakeTables.length ? INTAKE_CLIENT_DECLARATIONS : ""} export class DataError extends Error { readonly status: number; readonly code: string | null;${receiptCapable ? " readonly request?: InsertRequest<unknown>; readonly outcome?: string; readonly write_committed?: boolean; readonly retry?: false; readonly retryable?: false; readonly idempotency?: InsertReceipt;" : ""} constructor(status: number, payload: unknown); }
|
|
680
701
|
export const data: { ${declarations.join(";\n ")} };
|
|
681
702
|
}
|
|
682
703
|
`;
|
|
@@ -957,6 +978,12 @@ interface SomewhereDbReadOptions {
|
|
|
957
978
|
include?: readonly string[] | null;
|
|
958
979
|
has?: Readonly<Record<string, SomewhereDbWhere>> | null;
|
|
959
980
|
}
|
|
981
|
+
// Row hints on a caller read asked for { can: true }: what this signed-in caller may change now (the
|
|
982
|
+
// write still enforces) and whether this caller created the row. The owner column itself is never sent.
|
|
983
|
+
interface SomewhereDbRowHints {
|
|
984
|
+
readonly can: { readonly update: boolean; readonly delete: boolean };
|
|
985
|
+
readonly mine: boolean;
|
|
986
|
+
}
|
|
960
987
|
interface SomewhereDbCountOptions { where?: SomewhereDbWhere | null }
|
|
961
988
|
interface SomewhereDbPlainInsertOptions { onConflict?: 'ignore' | 'update' | null }
|
|
962
989
|
// idempotencyKey: an explicit key (16-128 of A-Z a-z 0-9 - _) for a single plain insert by a
|
|
@@ -1076,8 +1103,11 @@ interface SomewhereCallerDb extends Omit<SomewhereServerDb, 'from' | 'count' | '
|
|
|
1076
1103
|
insert<T extends SomewhereDbTable>(table: T, values: __SomewhereDeclared<T, 'insert', SomewhereDbValues>, options?: SomewhereDbCallerInsertOptions<T> | null): Promise<SomewhereDbInsertResult<SomewhereDbRow<T>>>;
|
|
1077
1104
|
update<T extends SomewhereDbTable>(table: T, spec: SomewhereDbUpdateFor<T, 'set'>): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
|
|
1078
1105
|
tx(intents: readonly SomewhereDbCallerWriteIntent[]): Promise<SomewhereDbResult[]>;
|
|
1106
|
+
// can: true (signed-in callers only; never with asServer): each row also carries the row hints.
|
|
1079
1107
|
from<T extends SomewhereDbTable, const C extends readonly SomewhereDbColumn<T>[] = readonly SomewhereDbColumn<T>[], const I extends readonly SomewhereDbRelationName<T>[] = []>(
|
|
1080
|
-
table: T, options
|
|
1108
|
+
table: T, options: SomewhereDbReadOptionsFor<T, C, I> & { can: true; asServer?: never }): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I> & SomewhereDbRowHints>>;
|
|
1109
|
+
from<T extends SomewhereDbTable, const C extends readonly SomewhereDbColumn<T>[] = readonly SomewhereDbColumn<T>[], const I extends readonly SomewhereDbRelationName<T>[] = []>(
|
|
1110
|
+
table: T, options?: (SomewhereDbReadOptionsFor<T, C, I> & { asServer?: true; can?: false }) | null): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I>>>;
|
|
1081
1111
|
count(table: SomewhereDbTable, options?: (SomewhereDbCountOptions & { asServer?: true }) | null): Promise<{ data: number; error: null }>;
|
|
1082
1112
|
delete<T extends SomewhereDbTable>(table: T, spec?: SomewhereDbRemove | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
|
|
1083
1113
|
readonly server: SomewhereServerDb;
|
|
@@ -4124,10 +4154,10 @@ function boolean(options?: SomewhereSchemaDeclaration.ColumnOptions<boolean>): S
|
|
|
4124
4154
|
function timestamp(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
|
|
4125
4155
|
function json(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
|
|
4126
4156
|
function blob(options?: SomewhereSchemaDeclaration.ColumnOptions<never>): SomewhereSchemaDeclaration.Column;
|
|
4127
|
-
function owner(options?: { column?: string; visitors?: boolean }): SomewhereSchemaDeclaration.OwnerScope;
|
|
4157
|
+
function owner(options?: { column?: string; visitors?: boolean; read?: string[] }): SomewhereSchemaDeclaration.OwnerScope;
|
|
4128
4158
|
function shared(): SomewhereSchemaDeclaration.Scope;
|
|
4129
4159
|
function serverOnly(): SomewhereSchemaDeclaration.Scope;
|
|
4130
|
-
function member(options: { group: string | string[]; membership: string; member_user: string; member_group: string | string[]; operations?: Array<'read' | 'create' | 'update' | 'delete' | 'upload' | 'replace'
|
|
4160
|
+
function member(options: { group: string | string[]; membership: string; member_user: string; member_group: string | string[]; operations?: Array<'read' | 'create' | 'update' | 'delete' | 'upload' | 'replace'>; read?: string[] }): SomewhereSchemaDeclaration.MemberScope;
|
|
4131
4161
|
function files(options: SomewhereSchemaDeclaration.FileCollectionOptions): SomewhereSchemaDeclaration.FileCollection;
|
|
4132
4162
|
function hasMany(table: string, foreignKey: string): SomewhereSchemaDeclaration.Relation;
|
|
4133
4163
|
function belongsTo(table: string, foreignKey: string): SomewhereSchemaDeclaration.Relation;
|
|
@@ -4135,13 +4165,13 @@ function removed(): SomewhereSchemaDeclaration.ColumnMarker;
|
|
|
4135
4165
|
function removedTable(): SomewhereSchemaDeclaration.TableMarker;
|
|
4136
4166
|
function exported(): SomewhereSchemaDeclaration.TableMarker;
|
|
4137
4167
|
`;
|
|
4138
|
-
var policySignatures = `function grant(options: { table: string; grantee: string; row?: string; operations: readonly ['read'] | readonly ['read', 'update'] }): SomewhereSchemaDeclaration.GrantScope;
|
|
4168
|
+
var policySignatures = `function grant(options: { table: string; grantee: string; row?: string; operations: readonly ['read'] | readonly ['read', 'update']; read?: string[] }): SomewhereSchemaDeclaration.GrantScope;
|
|
4139
4169
|
function anyOf(owner: SomewhereSchemaDeclaration.OwnerScope, other: SomewhereSchemaDeclaration.GrantScope | SomewhereSchemaDeclaration.MemberScope | SomewhereSchemaDeclaration.GroupScope | SomewhereSchemaDeclaration.AppRoleScope): SomewhereSchemaDeclaration.AnyOfScope;
|
|
4140
4170
|
function anyOf(other: SomewhereSchemaDeclaration.GrantScope | SomewhereSchemaDeclaration.MemberScope | SomewhereSchemaDeclaration.GroupScope | SomewhereSchemaDeclaration.AppRoleScope, owner: SomewhereSchemaDeclaration.OwnerScope): SomewhereSchemaDeclaration.AnyOfScope;
|
|
4141
4171
|
function allOf(gate: SomewhereSchemaDeclaration.MemberScope | SomewhereSchemaDeclaration.GroupScope | SomewhereSchemaDeclaration.AppRoleScope, who: SomewhereSchemaDeclaration.OwnerScope | SomewhereSchemaDeclaration.AnyOfScope): SomewhereSchemaDeclaration.Scope;
|
|
4142
4172
|
function parent(options: { via: string; table?: string }): SomewhereSchemaDeclaration.Scope;
|
|
4143
|
-
function group(options?: { column?: string; roles?: Partial<Record<'read' | 'create' | 'update' | 'delete', string[]
|
|
4144
|
-
function appRole(options: { roles: Partial<Record<'read' | 'create' | 'update' | 'delete', string[]
|
|
4173
|
+
function group(options?: { column?: string; roles?: Partial<Record<'read' | 'create' | 'update' | 'delete', string[]>>; read?: string[] }): SomewhereSchemaDeclaration.GroupScope;
|
|
4174
|
+
function appRole(options: { roles: Partial<Record<'read' | 'create' | 'update' | 'delete', string[]>>; read?: string[] }): SomewhereSchemaDeclaration.AppRoleScope;
|
|
4145
4175
|
`;
|
|
4146
4176
|
var SCHEMA_DECLARATION2 = types + signatures.replace(/^function /gm, "declare function ") + `declare module 'somewhere/db' {
|
|
4147
4177
|
${(signatures + policySignatures).replace(/^function /gm, " export function ")}}
|
|
@@ -4312,19 +4342,131 @@ var require_generated_declarations_render = __commonJS({
|
|
|
4312
4342
|
}
|
|
4313
4343
|
});
|
|
4314
4344
|
|
|
4345
|
+
// worker/containers/compile/consumer-contract.cjs
|
|
4346
|
+
var require_consumer_contract = __commonJS({
|
|
4347
|
+
"worker/containers/compile/consumer-contract.cjs"(exports2, module2) {
|
|
4348
|
+
"use strict";
|
|
4349
|
+
var CONSUMER_CONTRACT2 = Object.freeze({
|
|
4350
|
+
version: 1,
|
|
4351
|
+
digest: "caa6986fab558d2d8ccb9157f3e7ffb5e94b179b4d22254dfeefe991468005e4"
|
|
4352
|
+
});
|
|
4353
|
+
var MEMBERSHIPS = "memberships: table({ id: id(), team_id: text(), member_id: text() }, { scope: owner(), client: { read: true } }),";
|
|
4354
|
+
var SHARES = "shares: table({ id: id(), grantee_id: text() }, { scope: owner(), client: { read: true, create: ['grantee_id'], delete: true }, unique: [['user_id', 'grantee_id']] }),";
|
|
4355
|
+
var MEMBER = "member({ group: ['team_id'], membership: 'memberships', member_user: 'member_id', member_group: ['team_id'], operations: ['read', 'create', 'update', 'delete'] })";
|
|
4356
|
+
var one = (imports, tables, options = "") => `import { schema, table, id, text, integer, ${imports} } from 'somewhere/db';
|
|
4357
|
+
export default schema({
|
|
4358
|
+
${tables}
|
|
4359
|
+
}${options});
|
|
4360
|
+
`;
|
|
4361
|
+
var contacts = (scope, extra = "") => `contacts: table({ id: id(), team_id: text({ default: '' }), name: text()${extra} }, { scope: ${scope}, client: { read: true, create: ['name'], update: ['name'], delete: true } }),`;
|
|
4362
|
+
var CONSUMER_CONTRACT_FORMS = Object.freeze([
|
|
4363
|
+
["owner()", one("owner", "notes: table({ id: id(), title: text() }, { scope: owner(), client: { read: true, create: ['title'], update: ['title'], delete: true } }),")],
|
|
4364
|
+
["shared()", one("shared", "posts: table({ id: id(), body: text() }, { scope: shared(), client: { read: true, create: ['body'] } }),")],
|
|
4365
|
+
["publicRead", one("owner", "cards: table({ id: id(), title: text() }, { scope: owner(), client: { read: ['id', 'title'], publicRead: true } }),")],
|
|
4366
|
+
["member()", one("owner, member", `${MEMBERSHIPS}
|
|
4367
|
+
${contacts(MEMBER)}`)],
|
|
4368
|
+
["anyOf(owner(), member())", one("owner, member, anyOf", `${MEMBERSHIPS}
|
|
4369
|
+
${contacts(`anyOf(owner(), ${MEMBER})`)}`)],
|
|
4370
|
+
["anyOf(owner(), appRole())", one("owner, anyOf, appRole", "tickets: table({ id: id(), subject: text() }, { scope: anyOf(owner(), appRole({ roles: { read: ['staff'] } })), client: { read: true } }),", ", { appRoles: { staff: { grantableBy: [] } } }")],
|
|
4371
|
+
["anyOf(owner(), grant())", one("owner, anyOf, grant", `${SHARES}
|
|
4372
|
+
${contacts("anyOf(owner(), grant({ table: 'shares', grantee: 'grantee_id', operations: ['read'] }))")}`)],
|
|
4373
|
+
["anyOf(owner(), grant({ row }))", one("owner, anyOf, grant", "doc_shares: table({ id: id(), doc_id: text({ references: 'docs', onDelete: 'cascade' }), grantee_id: text() }, { scope: owner(), unique: [['doc_id', 'grantee_id']] }),\n docs: table({ id: id(), title: text() }, { scope: anyOf(owner(), grant({ table: 'doc_shares', grantee: 'grantee_id', row: 'doc_id', operations: ['read', 'update'] })), client: { read: true, update: ['title'] } }),")],
|
|
4374
|
+
["anyOf(owner({ read }), appRole())", one("owner, anyOf, appRole", "tickets: table({ id: id(), subject: text(), customer_email: text() }, { scope: anyOf(owner({ read: ['id', 'subject'] }), appRole({ roles: { read: ['staff'] } })), client: { read: ['id', 'subject', 'customer_email'] } }),", ", { appRoles: { staff: { grantableBy: [] } } }")],
|
|
4375
|
+
["allOf(member(), owner())", one("owner, member, allOf", `${MEMBERSHIPS}
|
|
4376
|
+
${contacts(`allOf(${MEMBER}, owner())`)}`)],
|
|
4377
|
+
["parent()", one("owner, parent, hasMany", "tickets: table({ id: id(), subject: text() }, { scope: owner(), client: { read: true }, relations: { comments: hasMany('comments', 'ticket_id') } }),\n comments: table({ id: id(), ticket_id: text({ references: 'tickets' }), body: text() }, { scope: parent({ via: 'ticket_id' }), client: { read: true } }),")],
|
|
4378
|
+
// Refused by deploy (scripts/check-cli-vendor-parity.mjs asserts the same reasons).
|
|
4379
|
+
["refused: grant() inside allOf", one("owner, member, anyOf, allOf, grant", `${SHARES}
|
|
4380
|
+
${MEMBERSHIPS}
|
|
4381
|
+
${contacts(`allOf(${MEMBER}, anyOf(owner(), grant({ table: 'shares', grantee: 'grantee_id', operations: ['read'] })))`)}`)],
|
|
4382
|
+
["refused: grant() update without read", one("owner, anyOf, grant", `${SHARES}
|
|
4383
|
+
${contacts("anyOf(owner(), grant({ table: 'shares', grantee: 'grantee_id', operations: ['update'] }))")}`)],
|
|
4384
|
+
["refused: grant() delete", one("owner, anyOf, grant", `${SHARES}
|
|
4385
|
+
${contacts("anyOf(owner(), grant({ table: 'shares', grantee: 'grantee_id', operations: ['read', 'update', 'delete'] }))")}`)],
|
|
4386
|
+
["refused: grant() alone", one("owner, grant", `${SHARES}
|
|
4387
|
+
${contacts("grant({ table: 'shares', grantee: 'grantee_id', operations: ['read'] })")}`)],
|
|
4388
|
+
["refused: a column named mine", one("owner", "notes: table({ id: id(), mine: text() }, { scope: owner(), client: { read: true } }),")]
|
|
4389
|
+
]);
|
|
4390
|
+
var DERIVED = ["generateFromFiles", "localDeclarationFromFiles", "declaredTablesFromFiles", "runtimeDeclarationFromFiles", "filesDeclarationFromFiles"];
|
|
4391
|
+
function consumerContractComponents2(data, sourceLintVersion, hash) {
|
|
4392
|
+
const forms = {};
|
|
4393
|
+
for (const [name, source] of CONSUMER_CONTRACT_FORMS) {
|
|
4394
|
+
const files = { "db/schema.ts": source };
|
|
4395
|
+
let verdict;
|
|
4396
|
+
try {
|
|
4397
|
+
verdict = { accepted: true, outputs: DERIVED.map((key) => typeof data[key] === "function" ? data[key](files) ?? null : `missing ${key}`) };
|
|
4398
|
+
} catch (error) {
|
|
4399
|
+
verdict = { accepted: false, refusal: String(error && error.message || error) };
|
|
4400
|
+
}
|
|
4401
|
+
forms[name] = hash(JSON.stringify(verdict));
|
|
4402
|
+
}
|
|
4403
|
+
return {
|
|
4404
|
+
schema_declaration: hash(String(data.SCHEMA_DECLARATION)),
|
|
4405
|
+
runtime_context_declaration: hash(String(data.RUNTIME_CONTEXT_DECLARATION)),
|
|
4406
|
+
endpoint_declaration: hash(String(data.ENDPOINT_DECLARATION)),
|
|
4407
|
+
files_declaration: hash(String(data.FILES_DECLARATION_FILE)),
|
|
4408
|
+
source_lint_version: String(sourceLintVersion),
|
|
4409
|
+
forms
|
|
4410
|
+
};
|
|
4411
|
+
}
|
|
4412
|
+
function consumerContractFingerprint2(components) {
|
|
4413
|
+
const sorted3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? Object.fromEntries(Object.keys(value).sort().map((k) => [k, sorted3(value[k])])) : value;
|
|
4414
|
+
return JSON.stringify(sorted3(components));
|
|
4415
|
+
}
|
|
4416
|
+
function consumerContractHeader(contract = CONSUMER_CONTRACT2) {
|
|
4417
|
+
return `${contract.version}; digest=${contract.digest}`;
|
|
4418
|
+
}
|
|
4419
|
+
function parseConsumerContractHeader2(value) {
|
|
4420
|
+
const m = /^\s*(\d{1,9}); digest=([0-9a-f]{64})\s*$/.exec(String(value || ""));
|
|
4421
|
+
return m ? { version: Number(m[1]), digest: m[2] } : null;
|
|
4422
|
+
}
|
|
4423
|
+
function consumerContractVerdict2(cli, seen, now = Date.now()) {
|
|
4424
|
+
if (!cli || !Number.isInteger(cli.version) || !/^[0-9a-f]{64}$/.test(cli.digest || "")) {
|
|
4425
|
+
return { status: "unknown", compatible: null, message: "This CLI does not record which platform consumer contract it implements." };
|
|
4426
|
+
}
|
|
4427
|
+
if (!seen) {
|
|
4428
|
+
return { status: "unknown", compatible: null, message: `The platform's consumer contract is not known on this machine yet (no platform response recorded). This CLI checks against contract ${cli.version}.` };
|
|
4429
|
+
}
|
|
4430
|
+
const age = Number.isFinite(seen.seen_at) ? ` (last seen ${Math.max(0, Math.round((now - seen.seen_at) / 36e5))} h ago${seen.origin ? ` from ${seen.origin}` : ""})` : "";
|
|
4431
|
+
if (seen.digest === cli.digest) return { status: "current", compatible: true, message: Number.isFinite(seen.seen_at) ? `This CLI implements consumer contract ${cli.version}, the one the platform last announced${age}. A platform release since then is not known until the CLI next talks to the platform.` : `This CLI implements consumer contract ${cli.version}.` };
|
|
4432
|
+
if (seen.version > cli.version) {
|
|
4433
|
+
return { status: "outdated", compatible: false, message: `This CLI implements consumer contract ${cli.version}; the platform is at ${seen.version}${age}. Its typecheck and lint judge your code by older platform rules, so a form the platform accepts may be reported as an error and newer forms are unknown to it. Update the CLI (somewhere update), then run this again.` };
|
|
4434
|
+
}
|
|
4435
|
+
if (seen.version < cli.version) {
|
|
4436
|
+
return { status: "ahead", compatible: false, message: `This CLI implements consumer contract ${cli.version}; the platform is still at ${seen.version}${age}. It may accept forms the platform does not yet deploy. Use the CLI version that matches the platform, or wait for the platform release.` };
|
|
4437
|
+
}
|
|
4438
|
+
return { status: "mismatch", compatible: false, message: `This CLI and the platform both say consumer contract ${cli.version} but with different digests${age}; one of them is not a released build. Reinstall the CLI from npm.` };
|
|
4439
|
+
}
|
|
4440
|
+
module2.exports = {
|
|
4441
|
+
CONSUMER_CONTRACT: CONSUMER_CONTRACT2,
|
|
4442
|
+
CONSUMER_CONTRACT_FORMS,
|
|
4443
|
+
consumerContractComponents: consumerContractComponents2,
|
|
4444
|
+
consumerContractFingerprint: consumerContractFingerprint2,
|
|
4445
|
+
consumerContractHeader,
|
|
4446
|
+
parseConsumerContractHeader: parseConsumerContractHeader2,
|
|
4447
|
+
consumerContractVerdict: consumerContractVerdict2
|
|
4448
|
+
};
|
|
4449
|
+
}
|
|
4450
|
+
});
|
|
4451
|
+
|
|
4315
4452
|
// declared-data-vendor-entry.js
|
|
4316
4453
|
var declared_data_vendor_entry_exports = {};
|
|
4317
4454
|
__export(declared_data_vendor_entry_exports, {
|
|
4455
|
+
CONSUMER_CONTRACT: () => import_consumer_contract.CONSUMER_CONTRACT,
|
|
4318
4456
|
ENDPOINT_DECLARATION: () => import_runtime_types3.ENDPOINT_DECLARATION,
|
|
4319
4457
|
FILES_DECLARATION_FILE: () => import_typed_files2.CLIENT_FILE,
|
|
4320
4458
|
RUNTIME_CONTEXT_DECLARATION: () => import_runtime_types3.RUNTIME_CONTEXT_DECLARATION,
|
|
4321
4459
|
SCHEMA_DECLARATION: () => import_schema_types.SCHEMA_DECLARATION,
|
|
4322
4460
|
checkerContract: () => checkerContract,
|
|
4461
|
+
consumerContractComponents: () => import_consumer_contract.consumerContractComponents,
|
|
4462
|
+
consumerContractFingerprint: () => import_consumer_contract.consumerContractFingerprint,
|
|
4463
|
+
consumerContractVerdict: () => import_consumer_contract.consumerContractVerdict,
|
|
4323
4464
|
declaredTablesFromFiles: () => declaredTablesFromFiles,
|
|
4324
4465
|
filesDeclarationFromFiles: () => filesDeclarationFromFiles,
|
|
4325
4466
|
generateFromFiles: () => generateFromFiles,
|
|
4326
4467
|
isRuntimeEndpointGlobal: () => isRuntimeEndpointGlobal,
|
|
4327
4468
|
localDeclarationFromFiles: () => localDeclarationFromFiles,
|
|
4469
|
+
parseConsumerContractHeader: () => import_consumer_contract.parseConsumerContractHeader,
|
|
4328
4470
|
referenceCheckDeclarations: () => referenceCheckDeclarations,
|
|
4329
4471
|
runtimeDeclarationFromFiles: () => runtimeDeclarationFromFiles
|
|
4330
4472
|
});
|
|
@@ -4339,6 +4481,9 @@ function isCanonicalBigintText(value) {
|
|
|
4339
4481
|
return digits.length < limit.length || digits.length === limit.length && digits <= limit;
|
|
4340
4482
|
}
|
|
4341
4483
|
|
|
4484
|
+
// worker/src/utils/db-schema-deploy/schema-ownership.ts
|
|
4485
|
+
var SHARED_AUTHOR_COLUMN = "_sw_author_id";
|
|
4486
|
+
|
|
4342
4487
|
// worker/src/utils/db-schema-deploy/client-permissions.ts
|
|
4343
4488
|
function tableAllowsClientWrites(table) {
|
|
4344
4489
|
return Boolean(table.client && (table.client.create !== null || table.client.update !== null || table.client.delete || table.client.intake !== void 0));
|
|
@@ -4391,6 +4536,9 @@ function parseClientPermissions(value, columns, scope) {
|
|
|
4391
4536
|
if (Array.isArray(read)) {
|
|
4392
4537
|
const names = [];
|
|
4393
4538
|
for (const entry of read) {
|
|
4539
|
+
if (typeof entry === "string" && ((scope.kind === "owner" || scope.kind === "policy") && entry.toLowerCase() === scope.column || entry.toLowerCase() === SHARED_AUTHOR_COLUMN)) {
|
|
4540
|
+
return fail(`client.read cannot list "${entry}": the platform assigns who created each row and never sends that identity to the browser. To show the signed-in user which rows are theirs, read with { can: true }: each row then carries mine (true for rows this user created) and can.update / can.delete.`);
|
|
4541
|
+
}
|
|
4394
4542
|
if (typeof entry !== "string" || !fields.has(entry.toLowerCase())) return fail("client.read must contain only declared column names.");
|
|
4395
4543
|
const name = entry.toLowerCase();
|
|
4396
4544
|
if (names.includes(name)) return fail(`client.read declares field "${entry}" twice.`);
|
|
@@ -4651,9 +4799,6 @@ function bakeIntakePermissions(schema, declarations, intents, scopes) {
|
|
|
4651
4799
|
}
|
|
4652
4800
|
}
|
|
4653
4801
|
|
|
4654
|
-
// worker/src/utils/db-schema-deploy/schema-ownership.ts
|
|
4655
|
-
var SHARED_AUTHOR_COLUMN = "_sw_author_id";
|
|
4656
|
-
|
|
4657
4802
|
// worker/src/services/jobs/limits.ts
|
|
4658
4803
|
var STALE_JOB_WINDOW_MS = 45 * 60 * 1e3;
|
|
4659
4804
|
|
|
@@ -4901,6 +5046,11 @@ function validGroupRoles(value) {
|
|
|
4901
5046
|
const roles = value;
|
|
4902
5047
|
return exactKeys(roles, ["read", "create", "update", "delete"]) && ["read", "create", "update", "delete"].every((op) => Array.isArray(roles[op]) && roles[op].every((role) => typeof role === "string" && GROUP_ROLE_NAME.test(role)) && new Set(roles[op]).size === roles[op].length);
|
|
4903
5048
|
}
|
|
5049
|
+
function validBranchReads(value) {
|
|
5050
|
+
if (!Array.isArray(value) || value.length !== 2) return false;
|
|
5051
|
+
const list = (entry) => entry === null || Array.isArray(entry) && entry.length > 0 && entry.every((name, i) => typeof name === "string" && SAFE_SCOPE_IDENTIFIER.test(name) && name === name.toLowerCase() && (i === 0 || entry[i - 1] < name));
|
|
5052
|
+
return list(value[0]) && list(value[1]) && !(value[0] === null && value[1] === null);
|
|
5053
|
+
}
|
|
4904
5054
|
function exactKeys(value, keys) {
|
|
4905
5055
|
const actual = Object.keys(value).sort();
|
|
4906
5056
|
return actual.length === keys.length && actual.every((key, index) => key === [...keys].sort()[index]);
|
|
@@ -4942,7 +5092,7 @@ function validBakedPolicy(value, root = true, edges = 0) {
|
|
|
4942
5092
|
}
|
|
4943
5093
|
if (node.k === "a") {
|
|
4944
5094
|
const second = node.p?.[1]?.k;
|
|
4945
|
-
return exactKeys(node, ["k", "p"]) && Array.isArray(node.p) && node.p.length === 2 && node.p[0]?.k === "o" && validBakedPolicy(node.p[0], false) && (!(node.p[1]?.k === "m" && node.p[1].m?.og) || node.p[0].c === "user_id") && (second === "m" ? validBakedPolicy(node.p[1], false) : (second === "g" || second === "r") && validBakedPolicy(node.p[1], true));
|
|
5095
|
+
return (exactKeys(node, ["k", "p"]) || edges === 0 && exactKeys(node, ["k", "p", "reads"]) && validBranchReads(node.reads)) && Array.isArray(node.p) && node.p.length === 2 && node.p[0]?.k === "o" && validBakedPolicy(node.p[0], false) && (!(node.p[1]?.k === "m" && node.p[1].m?.og) || node.p[0].c === "user_id") && (second === "m" ? validBakedPolicy(node.p[1], false) : (second === "g" || second === "r") && validBakedPolicy(node.p[1], true));
|
|
4946
5096
|
}
|
|
4947
5097
|
if (root && node.k === "n") {
|
|
4948
5098
|
if (!ALLOF_POLICY_ADMITTED || !exactKeys(node, ["k", "g", "w"])) return false;
|
|
@@ -5091,7 +5241,9 @@ function bakedPolicyFromDeclared(value, depth = 0, edges = 0, underParent = fals
|
|
|
5091
5241
|
if (depth !== 0 && !underParent || node.policies[0]?.kind !== "owner" || second !== "member" && second !== "group" && second !== "app_role") return null;
|
|
5092
5242
|
const left = bakedPolicyFromDeclared(node.policies[0], depth + 1);
|
|
5093
5243
|
const right = bakedPolicyFromDeclared(node.policies[1], depth + 1, edges, second !== "member");
|
|
5094
|
-
const
|
|
5244
|
+
const reads = node.reads === void 0 || edges > 0 || underParent ? void 0 : Array.isArray(node.reads) && node.reads.length === 2 ? node.reads.map((list) => Array.isArray(list) ? [...new Set(list.map((name) => String(name).toLowerCase()))].sort() : null) : null;
|
|
5245
|
+
if (reads === null || reads !== void 0 && !validBranchReads(reads)) return null;
|
|
5246
|
+
const baked = left && right ? { k: "a", p: [left, right], ...reads ? { reads } : {} } : null;
|
|
5095
5247
|
return baked && validBakedPolicy(pinShape(baked), true, edges) ? baked : null;
|
|
5096
5248
|
}
|
|
5097
5249
|
if (node.kind === "all_of") {
|
|
@@ -5195,6 +5347,7 @@ function readGrantScope(r, table, line, fail) {
|
|
|
5195
5347
|
r.expectPunct("{", "in grant options");
|
|
5196
5348
|
let membership, grantee, row;
|
|
5197
5349
|
let operations;
|
|
5350
|
+
let read;
|
|
5198
5351
|
const seen = /* @__PURE__ */ new Set();
|
|
5199
5352
|
while (!r.tryPunct("}")) {
|
|
5200
5353
|
const key = r.readKey("in grant options");
|
|
@@ -5222,7 +5375,9 @@ function readGrantScope(r, table, line, fail) {
|
|
|
5222
5375
|
}
|
|
5223
5376
|
if (!ops.includes("read")) fail(key.line, "grant operations require read; update alone is not allowed.");
|
|
5224
5377
|
operations = ops.includes("update") ? ["read", "update"] : ["read"];
|
|
5225
|
-
} else
|
|
5378
|
+
} else if (key.name === "read") {
|
|
5379
|
+
read = readBranchReadColumns(r, `grant({ read }) on table "${table}"`);
|
|
5380
|
+
} else fail(key.line, `Unknown grant option "${key.name}". Allowed: table, grantee, row, operations, read.`);
|
|
5226
5381
|
if (!r.tryPunct(",")) {
|
|
5227
5382
|
r.expectPunct("}", "closing grant options");
|
|
5228
5383
|
break;
|
|
@@ -5232,7 +5387,7 @@ function readGrantScope(r, table, line, fail) {
|
|
|
5232
5387
|
r.expectPunct(")", "closing grant");
|
|
5233
5388
|
if (!membership || !grantee || !operations || grantee === "user_id") fail(line, `grant() on "${table}" requires table, a grantee column distinct from managed user_id, and explicit operations.`);
|
|
5234
5389
|
if (row !== void 0 && (row === "user_id" || row === grantee)) fail(line, `grant() on "${table}": row names the shares column holding the shared row's id, distinct from user_id and the grantee.`);
|
|
5235
|
-
return { kind: "member", group: ["user_id"], membership, memberUser: grantee, memberGroup: row === void 0 ? ["user_id"] : ["user_id", row], operations, ownerGrant: true };
|
|
5390
|
+
return { kind: "member", group: ["user_id"], membership, memberUser: grantee, memberGroup: row === void 0 ? ["user_id"] : ["user_id", row], operations, ownerGrant: true, ...read ? { read } : {} };
|
|
5236
5391
|
}
|
|
5237
5392
|
function resolveGrantRowKeys(scope, key, table, line, fail) {
|
|
5238
5393
|
const visit = (node) => {
|
|
@@ -6182,6 +6337,7 @@ function readRoleList(r, fail, ctx) {
|
|
|
6182
6337
|
function readGroupScope(r, tableName, line, fail) {
|
|
6183
6338
|
r.expectPunct("(", 'after "group"');
|
|
6184
6339
|
let column = DEFAULT_GROUP_COLUMN;
|
|
6340
|
+
let read;
|
|
6185
6341
|
const explicit = {};
|
|
6186
6342
|
if (!r.tryPunct(")")) {
|
|
6187
6343
|
r.expectPunct("{", `in group() options for table "${tableName}"`);
|
|
@@ -6210,8 +6366,10 @@ function readGroupScope(r, tableName, line, fail) {
|
|
|
6210
6366
|
break;
|
|
6211
6367
|
}
|
|
6212
6368
|
}
|
|
6369
|
+
} else if (key.name === "read") {
|
|
6370
|
+
read = readBranchReadColumns(r, `group({ read }) on table "${tableName}"`);
|
|
6213
6371
|
} else {
|
|
6214
|
-
fail(key.line, `group() on table "${tableName}" has an unknown option "${key.name}". Allowed: column, roles.`);
|
|
6372
|
+
fail(key.line, `group() on table "${tableName}" has an unknown option "${key.name}". Allowed: column, roles, read.`);
|
|
6215
6373
|
}
|
|
6216
6374
|
if (!r.tryPunct(",")) {
|
|
6217
6375
|
r.expectPunct("}", `closing group() options for table "${tableName}"`);
|
|
@@ -6221,7 +6379,7 @@ function readGroupScope(r, tableName, line, fail) {
|
|
|
6221
6379
|
r.tryPunct(",");
|
|
6222
6380
|
r.expectPunct(")", "closing group()");
|
|
6223
6381
|
}
|
|
6224
|
-
const node = { kind: "group", column, roles: { read: [], create: [], update: [], delete: [] } };
|
|
6382
|
+
const node = { kind: "group", column, roles: { read: [], create: [], update: [], delete: [] }, ...read ? { read } : {} };
|
|
6225
6383
|
declaredGroupRoles.set(node, { line, explicit });
|
|
6226
6384
|
return node;
|
|
6227
6385
|
}
|
|
@@ -6330,7 +6488,17 @@ function readAppRoleScope(r, tableName, line, fail) {
|
|
|
6330
6488
|
let sawRoles = false;
|
|
6331
6489
|
while (!r.tryPunct("}")) {
|
|
6332
6490
|
const key = r.readKey(`for an option of ${where}`);
|
|
6333
|
-
if (key.name
|
|
6491
|
+
if (key.name === "read") {
|
|
6492
|
+
if (node.read) fail(key.line, `${where} declares read twice.`);
|
|
6493
|
+
r.expectPunct(":", `after "read" in ${where}`);
|
|
6494
|
+
node.read = readBranchReadColumns(r, `appRole({ read }) on table "${tableName}"`);
|
|
6495
|
+
if (!r.tryPunct(",")) {
|
|
6496
|
+
r.expectPunct("}", `closing ${where}`);
|
|
6497
|
+
break;
|
|
6498
|
+
}
|
|
6499
|
+
continue;
|
|
6500
|
+
}
|
|
6501
|
+
if (key.name !== "roles") fail(key.line, `${where} has an unknown option "${key.name}". Allowed: roles, read.`);
|
|
6334
6502
|
if (sawRoles) fail(key.line, `${where} declares roles twice.`);
|
|
6335
6503
|
sawRoles = true;
|
|
6336
6504
|
r.expectPunct(":", `after "roles" in ${where}`);
|
|
@@ -6622,6 +6790,7 @@ var SCHEMA_CALLABLE = /* @__PURE__ */ new Set([
|
|
|
6622
6790
|
]);
|
|
6623
6791
|
var SAFE_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
6624
6792
|
var RESERVED_PREFIXES = ["app_", "sqlite_", "d1_", "__sw_"];
|
|
6793
|
+
var ROW_HINT_FIELDS = ["can", "mine"];
|
|
6625
6794
|
var MAX_TABLES = 100;
|
|
6626
6795
|
var MAX_COLUMNS = 100;
|
|
6627
6796
|
var MAX_INDEXES = 40;
|
|
@@ -6869,6 +7038,9 @@ function checkDeclaredName(kind, raw, line) {
|
|
|
6869
7038
|
if (name.startsWith("_") || RESERVED_PREFIXES.some((p) => name.startsWith(p))) {
|
|
6870
7039
|
throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" uses a reserved platform prefix and cannot be declared: names starting with "app_" or "_" belong to the platform.`);
|
|
6871
7040
|
}
|
|
7041
|
+
if (kind === "Column" && ROW_HINT_FIELDS.includes(name)) {
|
|
7042
|
+
throw new SchemaTsError(`line ${line}: column name "${raw}" is reserved for row hints: a read with { can: true } returns can and mine on every row, so a column cannot take either name.`);
|
|
7043
|
+
}
|
|
6872
7044
|
return name;
|
|
6873
7045
|
}
|
|
6874
7046
|
function readColumn(r, tableName, rawName, line) {
|
|
@@ -7058,6 +7230,7 @@ function readMemberScope(r, tableName, scopeLine) {
|
|
|
7058
7230
|
let memberUser = null;
|
|
7059
7231
|
let memberGroup = null;
|
|
7060
7232
|
let operations, operationsSeen = false;
|
|
7233
|
+
let read;
|
|
7061
7234
|
while (!r.tryPunct("}")) {
|
|
7062
7235
|
const key = r.readKey(`for a member() option of table "${tableName}"`);
|
|
7063
7236
|
r.expectPunct(":", `after "${key.name}" in member() for table "${tableName}"`);
|
|
@@ -7073,9 +7246,12 @@ function readMemberScope(r, tableName, scopeLine) {
|
|
|
7073
7246
|
if (operationsSeen) throw new SchemaTsError(`line ${key.line}: member() on table "${tableName}" declares operations more than once.`);
|
|
7074
7247
|
operationsSeen = true;
|
|
7075
7248
|
operations = readMemberOperations(r, tableName);
|
|
7249
|
+
} else if (key.name === "read") {
|
|
7250
|
+
if (read) throw new SchemaTsError(`line ${key.line}: member() on table "${tableName}" declares read more than once.`);
|
|
7251
|
+
read = readBranchReadColumns(r, `member({ read }) on table "${tableName}"`);
|
|
7076
7252
|
} else {
|
|
7077
7253
|
throw new SchemaTsError(
|
|
7078
|
-
`line ${key.line}: member() on table "${tableName}" has an unknown option "${key.name}". Allowed: group, membership, member_user, member_group, operations.`
|
|
7254
|
+
`line ${key.line}: member() on table "${tableName}" has an unknown option "${key.name}". Allowed: group, membership, member_user, member_group, operations, read.`
|
|
7079
7255
|
);
|
|
7080
7256
|
}
|
|
7081
7257
|
if (!r.tryPunct(",")) {
|
|
@@ -7105,7 +7281,7 @@ function readMemberScope(r, tableName, scopeLine) {
|
|
|
7105
7281
|
`line ${scopeLine}: member() on table "${tableName}" has a ${group.length}-column group but a ${memberGroup.length}-column member_group. The group key must have the same number of columns on both sides.`
|
|
7106
7282
|
);
|
|
7107
7283
|
}
|
|
7108
|
-
const node = { kind: "member", group, membership, memberUser, memberGroup, ...operations !== void 0 ? { operations } : {} };
|
|
7284
|
+
const node = { kind: "member", group, membership, memberUser, memberGroup, ...operations !== void 0 ? { operations } : {}, ...read ? { read } : {} };
|
|
7109
7285
|
if (operationsSeen) explicitMemberOperations.add(node);
|
|
7110
7286
|
return node;
|
|
7111
7287
|
}
|
|
@@ -7113,21 +7289,56 @@ var explicitMemberOperations = /* @__PURE__ */ new WeakSet();
|
|
|
7113
7289
|
function readPolicyOwner(r, tableName, line) {
|
|
7114
7290
|
r.expectPunct("(", 'after "owner"');
|
|
7115
7291
|
let column = DEFAULT_OWNER_COLUMN;
|
|
7292
|
+
let read;
|
|
7116
7293
|
if (!r.tryPunct(")")) {
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7294
|
+
r.expectPunct("{", `in owner() inside anyOf() for table "${tableName}"`);
|
|
7295
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7296
|
+
while (!r.tryPunct("}")) {
|
|
7297
|
+
const key = r.readKey(`for an owner() option inside anyOf() for table "${tableName}"`);
|
|
7298
|
+
if (seen.has(key.name)) throw new SchemaTsError(`line ${key.line}: option "${key.name}" appears twice in owner() inside anyOf() for table "${tableName}".`);
|
|
7299
|
+
seen.add(key.name);
|
|
7300
|
+
r.expectPunct(":", `after "${key.name}" in owner() inside anyOf() for table "${tableName}"`);
|
|
7301
|
+
if (key.name === "column") {
|
|
7302
|
+
const value = r.readLiteral(`for "column" in owner() inside anyOf() for table "${tableName}"`);
|
|
7303
|
+
if (typeof value !== "string" || !SAFE_IDENT.test(value) || value.length > MAX_NAME_LENGTH) {
|
|
7304
|
+
throw new SchemaTsError(`line ${key.line}: owner({ column }) on table "${tableName}" must be a valid column name.`);
|
|
7305
|
+
}
|
|
7306
|
+
column = value.toLowerCase();
|
|
7307
|
+
} else if (key.name === "read") {
|
|
7308
|
+
read = readBranchReadColumns(r, `owner({ read }) on table "${tableName}"`);
|
|
7309
|
+
} else {
|
|
7310
|
+
throw new SchemaTsError(`line ${key.line}: owner() inside anyOf() only allows column and read. Visitor ownership is not part of policy scope.`);
|
|
7121
7311
|
}
|
|
7122
|
-
if (
|
|
7123
|
-
|
|
7312
|
+
if (!r.tryPunct(",")) {
|
|
7313
|
+
r.expectPunct("}", `closing owner() options inside anyOf() for table "${tableName}"`);
|
|
7314
|
+
break;
|
|
7124
7315
|
}
|
|
7125
|
-
column = value.toLowerCase();
|
|
7126
7316
|
}
|
|
7127
7317
|
r.tryPunct(",");
|
|
7128
7318
|
r.expectPunct(")", "closing owner()");
|
|
7129
7319
|
}
|
|
7130
|
-
return { kind: "owner", column };
|
|
7320
|
+
return { kind: "owner", column, ...read ? { read } : {} };
|
|
7321
|
+
}
|
|
7322
|
+
function readBranchReadColumns(r, ctx) {
|
|
7323
|
+
const open = r.next();
|
|
7324
|
+
if (open.kind !== "punct" || open.value !== "[") throw new SchemaTsError(`line ${open.line}: ${ctx} must be a list of quoted column names, such as ['id', 'subject'].`);
|
|
7325
|
+
const out = [];
|
|
7326
|
+
while (!r.tryPunct("]")) {
|
|
7327
|
+
const t = r.next();
|
|
7328
|
+
if (t.kind !== "string" || !SAFE_IDENT.test(t.value) || t.value.length > MAX_NAME_LENGTH) throw new SchemaTsError(`line ${t.line}: ${ctx} must be a list of quoted column names; values must be literal strings.`);
|
|
7329
|
+
const name = t.value.toLowerCase();
|
|
7330
|
+
if (out.includes(name)) throw new SchemaTsError(`line ${t.line}: ${ctx} lists "${t.value}" twice.`);
|
|
7331
|
+
out.push(name);
|
|
7332
|
+
if (!r.tryPunct(",")) {
|
|
7333
|
+
r.expectPunct("]", `closing ${ctx}`);
|
|
7334
|
+
break;
|
|
7335
|
+
}
|
|
7336
|
+
}
|
|
7337
|
+
if (out.length === 0) throw new SchemaTsError(`line ${open.line}: ${ctx} must name at least one column; leave read out for every column.`);
|
|
7338
|
+
return out;
|
|
7339
|
+
}
|
|
7340
|
+
function refuseBranchRead(node, line, tableName, where) {
|
|
7341
|
+
if (node.read) throw new SchemaTsError(`line ${line}: read on ${where} of table "${tableName}" is accepted only on the branches of the table's own anyOf(); it names the columns that branch may read.`);
|
|
7131
7342
|
}
|
|
7132
7343
|
function readPolicyMember(r, tableName, line) {
|
|
7133
7344
|
r.expectPunct("(", 'after "member"');
|
|
@@ -7162,7 +7373,11 @@ function readAnyOfScope(r, tableName, line) {
|
|
|
7162
7373
|
const owner = owners[0];
|
|
7163
7374
|
const other = firstPolicy.kind === "owner" ? secondPolicy : firstPolicy;
|
|
7164
7375
|
if (other.kind === "member" && other.ownerGrant && owner.column !== DEFAULT_OWNER_COLUMN) throw new SchemaTsError(`line ${line}: grant() requires owner() with managed user_id.`);
|
|
7165
|
-
|
|
7376
|
+
const otherRead = other;
|
|
7377
|
+
const reads = [owner.read ?? null, otherRead.read ?? null];
|
|
7378
|
+
delete owner.read;
|
|
7379
|
+
delete otherRead.read;
|
|
7380
|
+
return { kind: "policy", policy: { kind: "any_of", policies: [owner, other], ...reads[0] || reads[1] ? { reads } : {} } };
|
|
7166
7381
|
}
|
|
7167
7382
|
var ALL_OF_FORMS = "allOf() takes a gate \u2014 member(), group() or appRole() \u2014 and then owner() or anyOf(owner(), ...).";
|
|
7168
7383
|
var ALL_OF_EXPLICIT = "allOf needs explicit operations";
|
|
@@ -7188,6 +7403,9 @@ function readAllOfScope(r, tableName, line) {
|
|
|
7188
7403
|
} else throw new SchemaTsError(`line ${whoTok.line}: ${ALL_OF_FORMS}`);
|
|
7189
7404
|
r.tryPunct(",");
|
|
7190
7405
|
r.expectPunct(")", "closing allOf()");
|
|
7406
|
+
refuseBranchRead(gate, line, tableName, "the gate of allOf()");
|
|
7407
|
+
refuseBranchRead(who, line, tableName, "who in allOf()");
|
|
7408
|
+
if (who.kind === "any_of" && who.reads) throw new SchemaTsError(`line ${line}: read on a branch of anyOf() inside allOf() on table "${tableName}" is not accepted; per-branch columns belong on the table's own anyOf().`);
|
|
7191
7409
|
const x = who.kind === "any_of" ? who.policies[1] : null;
|
|
7192
7410
|
if (x?.kind === "member" && x.ownerGrant) throw new SchemaTsError(`line ${line}: grant() belongs in anyOf(owner(), grant()), not allOf().`);
|
|
7193
7411
|
for (const node of [gate, ...x ? [x] : []]) {
|
|
@@ -7279,13 +7497,17 @@ function readScope(r, tableName) {
|
|
|
7279
7497
|
const failAt = (line, message) => {
|
|
7280
7498
|
throw new SchemaTsError(`line ${line}: ${message}`);
|
|
7281
7499
|
};
|
|
7282
|
-
if (tok.value === "group"
|
|
7283
|
-
|
|
7500
|
+
if (tok.value === "group" || tok.value === "appRole") {
|
|
7501
|
+
const policy = tok.value === "group" ? readGroupScope(r, tableName, tok.line, failAt) : readAppRoleScope(r, tableName, tok.line, failAt);
|
|
7502
|
+
refuseBranchRead(policy, tok.line, tableName, `the ${tok.value}() scope`);
|
|
7503
|
+
return { kind: "policy", policy };
|
|
7504
|
+
}
|
|
7284
7505
|
if (tok.value === "member") {
|
|
7285
7506
|
r.expectPunct("(", 'after "member"');
|
|
7286
7507
|
const scope = readMemberScope(r, tableName, tok.line);
|
|
7287
7508
|
r.tryPunct(",");
|
|
7288
7509
|
r.expectPunct(")", "closing member()");
|
|
7510
|
+
if (scope.kind === "member") refuseBranchRead(scope, tok.line, tableName, "the member() scope");
|
|
7289
7511
|
return scope;
|
|
7290
7512
|
}
|
|
7291
7513
|
r.expectPunct("(", `after "${tok.value}"`);
|
|
@@ -7535,6 +7757,22 @@ function readTable(r, rawName, nameLine) {
|
|
|
7535
7757
|
}
|
|
7536
7758
|
uniques.push(entry.columns);
|
|
7537
7759
|
}
|
|
7760
|
+
if (scope.kind === "policy" && scope.policy.kind === "any_of" && scope.policy.reads) {
|
|
7761
|
+
const key = columns.filter((column) => column.helper === "id").length === 1 ? columns.find((column) => column.helper === "id").name : null;
|
|
7762
|
+
const declared = new Set(columns.map((column) => column.name));
|
|
7763
|
+
scope.policy.reads.forEach((list, i) => {
|
|
7764
|
+
if (!list) return;
|
|
7765
|
+
const branch = scope.policy.kind === "any_of" ? scope.policy.policies[i] : null;
|
|
7766
|
+
const label = branch?.kind === "owner" ? "owner()" : branch?.kind === "member" ? branch.ownerGrant ? "grant()" : "member()" : branch?.kind === "group" ? "group()" : "appRole()";
|
|
7767
|
+
for (const column of list) {
|
|
7768
|
+
if (!declared.has(column)) throw new SchemaTsError(`line ${nameLine}: table "${rawName}": ${label} read lists "${column}", which is not one of its declared columns.`);
|
|
7769
|
+
}
|
|
7770
|
+
if (key && !list.includes(key)) throw new SchemaTsError(`line ${nameLine}: table "${rawName}": ${label} read must include the id column "${key}", which every reader needs to page and address rows.`);
|
|
7771
|
+
for (const relation of relations) {
|
|
7772
|
+
if (relation.kind === "belongsTo" && !list.includes(relation.fk)) throw new SchemaTsError(`line ${nameLine}: table "${rawName}": ${label} read must include "${relation.fk}", the key relation "${relation.name}" joins by; a reader without it would get an empty relation.`);
|
|
7773
|
+
}
|
|
7774
|
+
});
|
|
7775
|
+
}
|
|
7538
7776
|
let client;
|
|
7539
7777
|
if (clientInput) {
|
|
7540
7778
|
if (columns.filter((column) => column.helper === "id").length !== 1) {
|
|
@@ -7821,6 +8059,7 @@ function isRuntimeEndpointGlobal(diag, functions) {
|
|
|
7821
8059
|
}
|
|
7822
8060
|
|
|
7823
8061
|
// declared-data-vendor-entry.js
|
|
8062
|
+
var import_consumer_contract = __toESM(require_consumer_contract());
|
|
7824
8063
|
function generateFromFiles(files) {
|
|
7825
8064
|
const authority = clientAuthorityFromSource(files);
|
|
7826
8065
|
return authority ? (0, import_typed_data.generateDataClient)(authority) : void 0;
|
|
@@ -7841,16 +8080,21 @@ function filesDeclarationFromFiles(files) {
|
|
|
7841
8080
|
}
|
|
7842
8081
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7843
8082
|
0 && (module.exports = {
|
|
8083
|
+
CONSUMER_CONTRACT,
|
|
7844
8084
|
ENDPOINT_DECLARATION,
|
|
7845
8085
|
FILES_DECLARATION_FILE,
|
|
7846
8086
|
RUNTIME_CONTEXT_DECLARATION,
|
|
7847
8087
|
SCHEMA_DECLARATION,
|
|
7848
8088
|
checkerContract,
|
|
8089
|
+
consumerContractComponents,
|
|
8090
|
+
consumerContractFingerprint,
|
|
8091
|
+
consumerContractVerdict,
|
|
7849
8092
|
declaredTablesFromFiles,
|
|
7850
8093
|
filesDeclarationFromFiles,
|
|
7851
8094
|
generateFromFiles,
|
|
7852
8095
|
isRuntimeEndpointGlobal,
|
|
7853
8096
|
localDeclarationFromFiles,
|
|
8097
|
+
parseConsumerContractHeader,
|
|
7854
8098
|
referenceCheckDeclarations,
|
|
7855
8099
|
runtimeDeclarationFromFiles
|
|
7856
8100
|
});
|