@sequenceholdings/orm 2.7.1 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -4
- package/dist/authoring.cjs +21 -0
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +1 -1
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +21 -0
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.d.cts +71 -71
- package/dist/canonical.d.ts +71 -71
- package/dist/cli.cjs +3460 -3337
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +13 -1
- package/dist/cli.d.ts +13 -1
- package/dist/cli.js +3459 -3338
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +24 -6
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +12 -3
- package/dist/client.d.ts +12 -3
- package/dist/client.js +24 -6
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +844 -15
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +12 -2
- package/dist/compiler.d.ts +12 -2
- package/dist/compiler.js +844 -17
- package/dist/compiler.js.map +1 -1
- package/dist/index.cjs +21 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -57
- package/dist/index.d.ts +57 -57
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/ir.d.cts +72 -72
- package/dist/ir.d.ts +72 -72
- package/dist/sdl.cjs +3 -3
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +2 -0
- package/dist/sdl.d.ts +2 -0
- package/dist/sdl.js +3 -3
- package/dist/sdl.js.map +1 -1
- package/package.json +1 -1
- package/templates/orm-namespace/graphql/example/queries.ts +52 -5
- package/templates/orm-namespace/schema/tables.ts +5 -2
- package/templates/orm-namespace/sequence.config.ts +6 -4
- package/dist/{definition-DjfC7Lll.d.cts → definition-B99BsOAO.d.cts} +4 -4
- package/dist/{definition-DjfC7Lll.d.ts → definition-B99BsOAO.d.ts} +4 -4
package/dist/compiler.cjs
CHANGED
|
@@ -1841,9 +1841,15 @@ function typeName(table) {
|
|
|
1841
1841
|
if (last === void 0) return pascalCase(table);
|
|
1842
1842
|
return [...segments.slice(0, -1), singularizeWord(last)].map(capitalizeFirst).join("");
|
|
1843
1843
|
}
|
|
1844
|
+
function fieldName(column) {
|
|
1845
|
+
return camelCase(column);
|
|
1846
|
+
}
|
|
1844
1847
|
function enumTypeName(name2) {
|
|
1845
1848
|
return pascalCase(name2);
|
|
1846
1849
|
}
|
|
1850
|
+
function comparisonExpName(gqlTypeName) {
|
|
1851
|
+
return `${capitalizeFirst(gqlTypeName)}ComparisonExp`;
|
|
1852
|
+
}
|
|
1847
1853
|
var SCALAR_FOR_COLUMN_TYPE = {
|
|
1848
1854
|
uuid: "uuid",
|
|
1849
1855
|
text: "String",
|
|
@@ -1867,7 +1873,19 @@ function columnTypeForScalar(scalarName) {
|
|
|
1867
1873
|
return COLUMN_TYPE_BY_SCALAR.get(scalarName);
|
|
1868
1874
|
}
|
|
1869
1875
|
var VARIABLE_SCALAR_NAMES = [...COLUMN_TYPE_BY_SCALAR.keys()];
|
|
1876
|
+
var CUSTOM_SCALARS = ["bigint", "date", "jsonb", "numeric", "timestamptz", "uuid"];
|
|
1877
|
+
var NUMERICISH_COLUMN_TYPES = ["integer", "bigint", "numeric", "double"];
|
|
1878
|
+
var ORDER_BY_ENUM = "OrderBy";
|
|
1879
|
+
var ORDER_BY_ENUM_VALUES = [
|
|
1880
|
+
"ASC",
|
|
1881
|
+
"ASC_NULLS_FIRST",
|
|
1882
|
+
"ASC_NULLS_LAST",
|
|
1883
|
+
"DESC",
|
|
1884
|
+
"DESC_NULLS_FIRST",
|
|
1885
|
+
"DESC_NULLS_LAST"
|
|
1886
|
+
];
|
|
1870
1887
|
var PLATFORM_USER_TYPENAME = "PlatformUser";
|
|
1888
|
+
var DATE_BUCKET_ENUM = "DateBucket";
|
|
1871
1889
|
function tableNames(table) {
|
|
1872
1890
|
const root = camelCase(table);
|
|
1873
1891
|
const rootPascal = pascalCase(table);
|
|
@@ -3480,8 +3498,8 @@ var COMPILER_MAJOR = 1;
|
|
|
3480
3498
|
var OP_HASH_DOMAIN = "sequence-orm-op\0";
|
|
3481
3499
|
var OPS_HASH_DOMAIN = "sequence-orm-opset\0";
|
|
3482
3500
|
function canonicalOperationText(definition) {
|
|
3483
|
-
const
|
|
3484
|
-
return graphql.print(
|
|
3501
|
+
const document2 = { kind: graphql.Kind.DOCUMENT, definitions: [definition] };
|
|
3502
|
+
return graphql.print(document2);
|
|
3485
3503
|
}
|
|
3486
3504
|
function computeOpHash({
|
|
3487
3505
|
definition,
|
|
@@ -4729,9 +4747,9 @@ function parseDocuments({
|
|
|
4729
4747
|
const parsed = [];
|
|
4730
4748
|
const diagnostics = [];
|
|
4731
4749
|
for (const source of documents) {
|
|
4732
|
-
let
|
|
4750
|
+
let document2;
|
|
4733
4751
|
try {
|
|
4734
|
-
|
|
4752
|
+
document2 = graphql.parse(source.contents);
|
|
4735
4753
|
} catch (error) {
|
|
4736
4754
|
diagnostics.push({
|
|
4737
4755
|
severity: "error",
|
|
@@ -4741,7 +4759,7 @@ function parseDocuments({
|
|
|
4741
4759
|
});
|
|
4742
4760
|
continue;
|
|
4743
4761
|
}
|
|
4744
|
-
const validationErrors = graphql.validate(schema,
|
|
4762
|
+
const validationErrors = graphql.validate(schema, document2);
|
|
4745
4763
|
if (validationErrors.length > 0) {
|
|
4746
4764
|
for (const validationError of validationErrors) {
|
|
4747
4765
|
diagnostics.push({
|
|
@@ -4755,11 +4773,11 @@ function parseDocuments({
|
|
|
4755
4773
|
}
|
|
4756
4774
|
const operations = [];
|
|
4757
4775
|
const fragments = /* @__PURE__ */ new Map();
|
|
4758
|
-
for (const definition of
|
|
4776
|
+
for (const definition of document2.definitions) {
|
|
4759
4777
|
if (definition.kind === graphql.Kind.OPERATION_DEFINITION) operations.push(definition);
|
|
4760
4778
|
if (definition.kind === graphql.Kind.FRAGMENT_DEFINITION) fragments.set(definition.name.value, definition);
|
|
4761
4779
|
}
|
|
4762
|
-
parsed.push({ path: source.path, document, operations, fragments });
|
|
4780
|
+
parsed.push({ path: source.path, document: document2, operations, fragments });
|
|
4763
4781
|
}
|
|
4764
4782
|
return { documents: parsed, diagnostics };
|
|
4765
4783
|
}
|
|
@@ -4957,12 +4975,12 @@ function compileNamespace({ canonical, sdl, documents }) {
|
|
|
4957
4975
|
diagnostics.push(...parsed.diagnostics);
|
|
4958
4976
|
const operations = {};
|
|
4959
4977
|
const operationSources = /* @__PURE__ */ new Map();
|
|
4960
|
-
for (const
|
|
4961
|
-
for (const operationNode of
|
|
4978
|
+
for (const document2 of parsed.documents) {
|
|
4979
|
+
for (const operationNode of document2.operations) {
|
|
4962
4980
|
const operationName = operationNode.name?.value;
|
|
4963
4981
|
try {
|
|
4964
4982
|
const restricted = restrictOperation({ operation: operationNode, ctx });
|
|
4965
|
-
const flattened = flattenOperation({ operation: restricted.definition, fragments:
|
|
4983
|
+
const flattened = flattenOperation({ operation: restricted.definition, fragments: document2.fragments });
|
|
4966
4984
|
const previousSource = operationSources.get(restricted.name);
|
|
4967
4985
|
if (previousSource !== void 0) {
|
|
4968
4986
|
throw new DocumentCompileError(
|
|
@@ -4994,14 +5012,14 @@ function compileNamespace({ canonical, sdl, documents }) {
|
|
|
4994
5012
|
tableSet,
|
|
4995
5013
|
roots
|
|
4996
5014
|
};
|
|
4997
|
-
operationSources.set(restricted.name,
|
|
5015
|
+
operationSources.set(restricted.name, document2.path);
|
|
4998
5016
|
} catch (error) {
|
|
4999
5017
|
if (error instanceof DocumentCompileError) {
|
|
5000
5018
|
diagnostics.push({
|
|
5001
5019
|
severity: "error",
|
|
5002
5020
|
code: error.code,
|
|
5003
5021
|
message: error.message,
|
|
5004
|
-
document:
|
|
5022
|
+
document: document2.path,
|
|
5005
5023
|
...operationName !== void 0 ? { operation: operationName } : {}
|
|
5006
5024
|
});
|
|
5007
5025
|
continue;
|
|
@@ -5429,6 +5447,815 @@ function validateOnConflictInput({
|
|
|
5429
5447
|
return finish(collector, { constraint, updateColumns });
|
|
5430
5448
|
}
|
|
5431
5449
|
|
|
5450
|
+
// src/sdl/generate.ts
|
|
5451
|
+
var SdlGenerationError = class extends Error {
|
|
5452
|
+
constructor(message) {
|
|
5453
|
+
super(message);
|
|
5454
|
+
this.name = "SdlGenerationError";
|
|
5455
|
+
}
|
|
5456
|
+
};
|
|
5457
|
+
var GRAPHQL_NAME_RE = /^[_A-Za-z][_0-9A-Za-z]*$/;
|
|
5458
|
+
var RESERVED_ENUM_VALUES = /* @__PURE__ */ new Set(["true", "false", "null"]);
|
|
5459
|
+
var NUMERICISH = new Set(NUMERICISH_COLUMN_TYPES);
|
|
5460
|
+
var PLATFORM_USER_FIELDS = [
|
|
5461
|
+
{ name: "sub", gqlType: "String", nullable: false },
|
|
5462
|
+
{ name: "email", gqlType: "String", nullable: true },
|
|
5463
|
+
{ name: "name", gqlType: "String", nullable: true },
|
|
5464
|
+
{ name: "organization", gqlType: "String", nullable: true },
|
|
5465
|
+
{ name: "role", gqlType: "String", nullable: true },
|
|
5466
|
+
{ name: "managerId", gqlType: "String", nullable: true },
|
|
5467
|
+
{ name: "accountEnabled", gqlType: "Boolean", nullable: false }
|
|
5468
|
+
];
|
|
5469
|
+
function generateSdl(canonical, options = {}) {
|
|
5470
|
+
const def = composeNamespace(normalizeCanonical(canonicalDefinitionSchema.parse(canonical))).definition;
|
|
5471
|
+
const exposed = canonicalRelations(def).filter((table) => table.internal !== true && (table.source === void 0 || options.includeSyncedReads === true && (table.primaryKey?.length ?? 0) > 0));
|
|
5472
|
+
const cellsByTable = new Map(exposed.map((table) => [table.table, trackedCells(table)]));
|
|
5473
|
+
const tables = exposed.map((table) => ({ ...table, columns: table.columns.filter((column) => column.cell === void 0) }));
|
|
5474
|
+
if (tables.length === 0) {
|
|
5475
|
+
throw new SdlGenerationError(
|
|
5476
|
+
`Namespace "${def.namespace}" exposes no relations (synced tables are excluded from SDL in v2.0) \u2014 nothing to generate.`
|
|
5477
|
+
);
|
|
5478
|
+
}
|
|
5479
|
+
for (const table of tables) {
|
|
5480
|
+
if (table.columns.length === 0) {
|
|
5481
|
+
throw new SdlGenerationError(`Relation "${table.table}" has no columns \u2014 cannot generate an object type.`);
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
5484
|
+
const namesByTable = new Map(tables.map((table) => [table.table, tableNames(table.table)]));
|
|
5485
|
+
const enumByName = new Map(def.enums.map((entry) => [entry.name, entry]));
|
|
5486
|
+
const hasPlatformUser = tables.some((table) => table.links.some((link) => link.kind === "toPlatformUser"));
|
|
5487
|
+
const usedEnums = /* @__PURE__ */ new Set();
|
|
5488
|
+
for (const table of tables) {
|
|
5489
|
+
for (const column of table.columns) {
|
|
5490
|
+
if (typeof column.type === "object") {
|
|
5491
|
+
if (!enumByName.has(column.type.enum)) {
|
|
5492
|
+
throw new SdlGenerationError(
|
|
5493
|
+
`Column "${table.table}.${column.columnName}" references undeclared enum "${column.type.enum}".`
|
|
5494
|
+
);
|
|
5495
|
+
}
|
|
5496
|
+
usedEnums.add(column.type.enum);
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
5500
|
+
const aggregateBoolExpTargets = /* @__PURE__ */ new Set();
|
|
5501
|
+
for (const table of tables) {
|
|
5502
|
+
for (const link of table.links) {
|
|
5503
|
+
if (link.kind === "toMany" && link.target !== void 0 && namesByTable.has(link.target)) {
|
|
5504
|
+
aggregateBoolExpTargets.add(link.target);
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
}
|
|
5508
|
+
const usedComparisonScalars = /* @__PURE__ */ new Set();
|
|
5509
|
+
for (const table of tables) {
|
|
5510
|
+
for (const column of table.columns) {
|
|
5511
|
+
if (typeof column.type !== "object") usedComparisonScalars.add(graphqlScalarFor(column.type));
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5514
|
+
if (hasPlatformUser) {
|
|
5515
|
+
usedComparisonScalars.add("String");
|
|
5516
|
+
usedComparisonScalars.add("Boolean");
|
|
5517
|
+
}
|
|
5518
|
+
if (aggregateBoolExpTargets.size > 0) usedComparisonScalars.add("Int");
|
|
5519
|
+
const blocks = [];
|
|
5520
|
+
blocks.push(
|
|
5521
|
+
`# Generated by @sequenceholdings/orm \u2014 GraphQL SDL for namespace "${def.namespace}".
|
|
5522
|
+
# Do not edit: regenerate with \`seq-studio orm generate\`.`
|
|
5523
|
+
);
|
|
5524
|
+
blocks.push("directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT");
|
|
5525
|
+
blocks.push(
|
|
5526
|
+
'"""Gates an operation behind a platform capability or custom role \u2014 checked by the data plane before execution."""\ndirective @requires(capability: String, role: String) on QUERY | MUTATION'
|
|
5527
|
+
);
|
|
5528
|
+
if (!options.omitReadBudgetDirective) blocks.push(
|
|
5529
|
+
'"""Sets a document-authored ceiling of 1\u201350001 rows for one flat select root. Runtime variables cannot raise it."""\ndirective @readBudget(maxRows: Int!) on QUERY'
|
|
5530
|
+
);
|
|
5531
|
+
blocks.push("directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT");
|
|
5532
|
+
blocks.push(
|
|
5533
|
+
'"""Abort the entire mutation unless this root affects exactly the document-authored number of rows. Mutation roots only; cannot be conditional."""\ndirective @assertAffectedRows(exactly: Int!) on FIELD'
|
|
5534
|
+
);
|
|
5535
|
+
if (tables.some((table) => table.resolved !== void 0)) blocks.push(...RESOLVED_DIRECTIVE_BLOCKS);
|
|
5536
|
+
for (const scalar of CUSTOM_SCALARS) blocks.push(`scalar ${scalar}`);
|
|
5537
|
+
blocks.push(...renderSharedCellTypes([...cellsByTable.values()]));
|
|
5538
|
+
blocks.push(renderEnum(ORDER_BY_ENUM, [...ORDER_BY_ENUM_VALUES]));
|
|
5539
|
+
blocks.push(renderEnum(DATE_BUCKET_ENUM, DATE_BUCKETS.map((bucket) => bucket.toUpperCase())));
|
|
5540
|
+
for (const enumName of [...usedEnums].sort()) {
|
|
5541
|
+
const entry = enumByName.get(enumName);
|
|
5542
|
+
if (!entry) continue;
|
|
5543
|
+
for (const value of entry.values) assertEnumValue(value, `enum "${enumName}"`);
|
|
5544
|
+
blocks.push(renderEnum(enumTypeName(enumName), entry.values));
|
|
5545
|
+
}
|
|
5546
|
+
const patternTypes = options.stringOnlyPatternOperators ? STRING_PATTERN_COMPARISON_TYPES : PATTERN_COMPARISON_TYPES;
|
|
5547
|
+
for (const scalar of [...usedComparisonScalars].sort()) {
|
|
5548
|
+
blocks.push(renderComparisonExp({ gqlTypeName: scalar, patternTypes }));
|
|
5549
|
+
}
|
|
5550
|
+
for (const enumName of [...usedEnums].sort()) {
|
|
5551
|
+
blocks.push(renderComparisonExp({ gqlTypeName: enumTypeName(enumName), patternTypes }));
|
|
5552
|
+
}
|
|
5553
|
+
if (hasPlatformUser) blocks.push(...renderPlatformUserBlocks());
|
|
5554
|
+
for (const table of tables) {
|
|
5555
|
+
const names = namesByTable.get(table.table);
|
|
5556
|
+
if (!names) continue;
|
|
5557
|
+
blocks.push(
|
|
5558
|
+
...renderTableBlocks({
|
|
5559
|
+
table,
|
|
5560
|
+
names,
|
|
5561
|
+
namesByTable,
|
|
5562
|
+
emitAggregateBoolExp: aggregateBoolExpTargets.has(table.table),
|
|
5563
|
+
pinned: options.pinnedColumns?.[table.table] ?? [],
|
|
5564
|
+
cells: cellsByTable.get(table.table) ?? null
|
|
5565
|
+
})
|
|
5566
|
+
);
|
|
5567
|
+
}
|
|
5568
|
+
blocks.push(renderQueryRoot(tables, namesByTable));
|
|
5569
|
+
const mutationRoot = renderMutationRoot(tables, namesByTable);
|
|
5570
|
+
if (mutationRoot !== null) blocks.push(mutationRoot);
|
|
5571
|
+
assertNoDuplicateTypeNames(blocks);
|
|
5572
|
+
return `${blocks.join("\n\n")}
|
|
5573
|
+
`;
|
|
5574
|
+
}
|
|
5575
|
+
var TYPE_DECLARATION_RE = /^(?:type|input|enum|scalar|interface|union)\s+([_A-Za-z][_0-9A-Za-z]*)/;
|
|
5576
|
+
function assertNoDuplicateTypeNames(blocks) {
|
|
5577
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5578
|
+
for (const block of blocks) {
|
|
5579
|
+
const declared = TYPE_DECLARATION_RE.exec(block)?.[1];
|
|
5580
|
+
if (declared === void 0) continue;
|
|
5581
|
+
if (seen.has(declared)) {
|
|
5582
|
+
throw new SdlGenerationError(
|
|
5583
|
+
`Generated schema declares "${declared}" twice. A canonical enum or table name is PascalCased into this GraphQL type name and collides with another declaration (for example an enum named "date_bucket" renders as "DateBucket", the shared bucket enum). Rename the offending enum or table.`
|
|
5584
|
+
);
|
|
5585
|
+
}
|
|
5586
|
+
seen.add(declared);
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
function assertEnumValue(value, context) {
|
|
5590
|
+
if (!GRAPHQL_NAME_RE.test(value) || RESERVED_ENUM_VALUES.has(value)) {
|
|
5591
|
+
throw new SdlGenerationError(
|
|
5592
|
+
`Value "${value}" in ${context} is not a valid GraphQL enum value (must match ${GRAPHQL_NAME_RE} and not be true/false/null).`
|
|
5593
|
+
);
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
function renderEnum(name2, values) {
|
|
5597
|
+
return `enum ${name2} {
|
|
5598
|
+
${values.map((value) => ` ${value}`).join("\n")}
|
|
5599
|
+
}`;
|
|
5600
|
+
}
|
|
5601
|
+
function descriptionLines(text, indent) {
|
|
5602
|
+
const escaped = text.replace(/"""/g, '\\"""');
|
|
5603
|
+
return [`${indent}"""`, ...escaped.split("\n").map((line) => `${indent}${line}`), `${indent}"""`];
|
|
5604
|
+
}
|
|
5605
|
+
var PATTERN_COMPARISON_TYPES = /* @__PURE__ */ new Set(["String", "Int", "bigint", "uuid"]);
|
|
5606
|
+
var STRING_PATTERN_COMPARISON_TYPES = /* @__PURE__ */ new Set(["String"]);
|
|
5607
|
+
function renderComparisonExp({ gqlTypeName, patternTypes }) {
|
|
5608
|
+
const fields = [
|
|
5609
|
+
["_eq", gqlTypeName],
|
|
5610
|
+
["_neq", gqlTypeName],
|
|
5611
|
+
["_gt", gqlTypeName],
|
|
5612
|
+
["_gte", gqlTypeName],
|
|
5613
|
+
["_lt", gqlTypeName],
|
|
5614
|
+
["_lte", gqlTypeName],
|
|
5615
|
+
["_in", `[${gqlTypeName}!]`],
|
|
5616
|
+
["_nin", `[${gqlTypeName}!]`],
|
|
5617
|
+
["_isNull", "Boolean"]
|
|
5618
|
+
];
|
|
5619
|
+
if (patternTypes.has(gqlTypeName)) {
|
|
5620
|
+
fields.push(["_like", "String"], ["_ilike", "String"]);
|
|
5621
|
+
}
|
|
5622
|
+
if (gqlTypeName === "jsonb") {
|
|
5623
|
+
fields.push(["_contains", "jsonb"], ["_hasKey", "String"]);
|
|
5624
|
+
}
|
|
5625
|
+
fields.sort(([a], [b]) => a.localeCompare(b));
|
|
5626
|
+
const body = fields.map(([field, type]) => ` ${field}: ${type}`).join("\n");
|
|
5627
|
+
return `input ${comparisonExpName(gqlTypeName)} {
|
|
5628
|
+
${body}
|
|
5629
|
+
}`;
|
|
5630
|
+
}
|
|
5631
|
+
function renderPlatformUserBlocks() {
|
|
5632
|
+
const typeFields = PLATFORM_USER_FIELDS.map(
|
|
5633
|
+
(field) => ` ${field.name}: ${field.gqlType}${field.nullable ? "" : "!"}`
|
|
5634
|
+
).join("\n");
|
|
5635
|
+
const boolExpName = `${PLATFORM_USER_TYPENAME}BoolExp`;
|
|
5636
|
+
const boolFields = [
|
|
5637
|
+
` _and: [${boolExpName}!]`,
|
|
5638
|
+
` _not: ${boolExpName}`,
|
|
5639
|
+
` _or: [${boolExpName}!]`,
|
|
5640
|
+
...PLATFORM_USER_FIELDS.map((field) => ` ${field.name}: ${comparisonExpName(field.gqlType)}`)
|
|
5641
|
+
].join("\n");
|
|
5642
|
+
const orderFields = PLATFORM_USER_FIELDS.map((field) => ` ${field.name}: ${ORDER_BY_ENUM}`).join("\n");
|
|
5643
|
+
return [
|
|
5644
|
+
`"""Curated read-only view over platform users (plan \xA74b) \u2014 reachable only through \`toPlatformUser\` links."""
|
|
5645
|
+
type ${PLATFORM_USER_TYPENAME} {
|
|
5646
|
+
${typeFields}
|
|
5647
|
+
}`,
|
|
5648
|
+
`input ${boolExpName} {
|
|
5649
|
+
${boolFields}
|
|
5650
|
+
}`,
|
|
5651
|
+
`input ${PLATFORM_USER_TYPENAME}OrderBy {
|
|
5652
|
+
${orderFields}
|
|
5653
|
+
}`
|
|
5654
|
+
];
|
|
5655
|
+
}
|
|
5656
|
+
var RESOLVED_DIRECTIVE_BLOCKS = [
|
|
5657
|
+
'"""Refresh this resolved-table root from a prefetch source before reading. Key-addressed roots only; the source must support the root shape."""\ndirective @prefetch(source: String!, maxStaleness: String) repeatable on FIELD',
|
|
5658
|
+
renderEnum("WritebackMode", ["SYNC", "ASYNC"]),
|
|
5659
|
+
'"""Write this resolved-table mutation back to a prefetch source (SYNC rolls back on failure; ASYNC queues)."""\ndirective @writeback(source: String!, mode: WritebackMode) repeatable on FIELD',
|
|
5660
|
+
'"""Declare that this mutation deliberately keeps writeback columns local to the named source."""\ndirective @localOnly(source: String!) repeatable on FIELD'
|
|
5661
|
+
];
|
|
5662
|
+
function trackedCells(table) {
|
|
5663
|
+
const columns = [...new Set(table.columns.flatMap((column) => column.cell ? [column.cell.of] : []))];
|
|
5664
|
+
if (columns.length === 0) return null;
|
|
5665
|
+
return { resolved: table.columns.some((column) => column.cell?.part === "source"), columns };
|
|
5666
|
+
}
|
|
5667
|
+
function renderSharedCellTypes(all) {
|
|
5668
|
+
const present = all.filter((cells) => cells !== null);
|
|
5669
|
+
const blocks = [];
|
|
5670
|
+
if (present.some((cells) => !cells.resolved)) {
|
|
5671
|
+
blocks.push(`"""When, and by whom, a cell was last written."""
|
|
5672
|
+
type ${CELL_TYPENAMES.stamp} {
|
|
5673
|
+
at: timestamptz!
|
|
5674
|
+
by: String
|
|
5675
|
+
}`);
|
|
5676
|
+
}
|
|
5677
|
+
if (present.some((cells) => cells.resolved)) {
|
|
5678
|
+
blocks.push(`"""A newer candidate value that lost to the column's resolve rule."""
|
|
5679
|
+
type ${CELL_TYPENAMES.conflict} {
|
|
5680
|
+
source: String!
|
|
5681
|
+
at: timestamptz
|
|
5682
|
+
value: jsonb
|
|
5683
|
+
}`);
|
|
5684
|
+
blocks.push(`"""How a resolved cell was decided: the winning source, its time and actor, and newer losers."""
|
|
5685
|
+
type ${CELL_TYPENAMES.resolved} {
|
|
5686
|
+
source: String!
|
|
5687
|
+
at: timestamptz
|
|
5688
|
+
by: String
|
|
5689
|
+
conflicts: [${CELL_TYPENAMES.conflict}!]!
|
|
5690
|
+
}`);
|
|
5691
|
+
}
|
|
5692
|
+
return blocks;
|
|
5693
|
+
}
|
|
5694
|
+
function renderCellsType(ctx) {
|
|
5695
|
+
if (!ctx.cells) return [];
|
|
5696
|
+
const cellType = ctx.cells.resolved ? CELL_TYPENAMES.resolved : CELL_TYPENAMES.stamp;
|
|
5697
|
+
const lines = ctx.cells.columns.map((column) => ` ${fieldName(column)}: ${cellType}`);
|
|
5698
|
+
return [`type ${cellsTypename(ctx.names.typename)} {
|
|
5699
|
+
${lines.join("\n")}
|
|
5700
|
+
}`];
|
|
5701
|
+
}
|
|
5702
|
+
function relationArgs(target) {
|
|
5703
|
+
return `where: ${target.boolExp}, orderBy: [${target.orderBy}!], limit: Int, offset: Int`;
|
|
5704
|
+
}
|
|
5705
|
+
function rootArgs(names) {
|
|
5706
|
+
return `where: ${names.boolExp}, orderBy: [${names.orderBy}!], distinctOn: [${names.selectColumn}!], limit: Int, offset: Int`;
|
|
5707
|
+
}
|
|
5708
|
+
function primaryKeyColumns(table) {
|
|
5709
|
+
if (!table.primaryKey || table.primaryKey.length === 0) return [];
|
|
5710
|
+
return table.primaryKey.map((identifier3) => {
|
|
5711
|
+
const column = table.columns.find((c) => c.columnName === identifier3 || c.name === identifier3);
|
|
5712
|
+
if (!column) {
|
|
5713
|
+
throw new SdlGenerationError(`Primary key column "${identifier3}" not found on table "${table.table}".`);
|
|
5714
|
+
}
|
|
5715
|
+
return column;
|
|
5716
|
+
});
|
|
5717
|
+
}
|
|
5718
|
+
function renderTableBlocks(ctx) {
|
|
5719
|
+
const { table, names } = ctx;
|
|
5720
|
+
const blocks = [
|
|
5721
|
+
renderObjectType(ctx),
|
|
5722
|
+
...renderCellsType(ctx),
|
|
5723
|
+
renderSelectColumnEnum(ctx),
|
|
5724
|
+
renderOrderByInput(ctx),
|
|
5725
|
+
renderBoolExp(ctx),
|
|
5726
|
+
...renderAggregateTypes(ctx),
|
|
5727
|
+
...renderGroupByTypes(ctx)
|
|
5728
|
+
];
|
|
5729
|
+
if (ctx.emitAggregateBoolExp) blocks.push(...renderAggregateBoolExp(ctx));
|
|
5730
|
+
const pk = primaryKeyColumns(table);
|
|
5731
|
+
if (pk.length > 0) {
|
|
5732
|
+
const fields = pk.map((column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join("\n");
|
|
5733
|
+
blocks.push(`input ${names.pkColumnsInput} {
|
|
5734
|
+
${fields}
|
|
5735
|
+
}`);
|
|
5736
|
+
}
|
|
5737
|
+
if (ctx.table.resolved !== void 0) {
|
|
5738
|
+
blocks.push(...renderResolvedWriteBlocks(ctx));
|
|
5739
|
+
} else if (allowsInsert(table) || allowsUpdate(table)) {
|
|
5740
|
+
blocks.push(...renderWriteBlocks(ctx, { insert: allowsInsert(table), update: allowsUpdate(table) }));
|
|
5741
|
+
}
|
|
5742
|
+
return blocks;
|
|
5743
|
+
}
|
|
5744
|
+
function resolvedWritableColumns(table) {
|
|
5745
|
+
const system = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((column) => column.name));
|
|
5746
|
+
return table.columns.filter((column) => !system.has(column.name));
|
|
5747
|
+
}
|
|
5748
|
+
function renderResolvedWriteBlocks(ctx) {
|
|
5749
|
+
const { table, names } = ctx;
|
|
5750
|
+
const writable = resolvedWritableColumns(table);
|
|
5751
|
+
const pk = new Set(table.primaryKey ?? []);
|
|
5752
|
+
const settable = writable.filter((column) => !pk.has(column.columnName));
|
|
5753
|
+
const line = (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`;
|
|
5754
|
+
const blocks = [];
|
|
5755
|
+
if (allowsInsert(table)) blocks.push(`input ${names.insertInput} {
|
|
5756
|
+
${writable.map(line).join("\n")}
|
|
5757
|
+
}`);
|
|
5758
|
+
if (allowsUpdate(table)) {
|
|
5759
|
+
blocks.push(`input ${names.setInput} {
|
|
5760
|
+
${settable.map(line).join("\n")}
|
|
5761
|
+
}`);
|
|
5762
|
+
blocks.push(renderEnum(names.updateColumnEnum, settable.map((column) => fieldName(column.name))));
|
|
5763
|
+
}
|
|
5764
|
+
return blocks;
|
|
5765
|
+
}
|
|
5766
|
+
function renderResolvedMutationFields(table, names) {
|
|
5767
|
+
const lines = [];
|
|
5768
|
+
const pk = primaryKeyColumns(table);
|
|
5769
|
+
if (allowsInsert(table)) lines.push(` ${names.insertOneRoot}(object: ${names.insertInput}!): ${names.typename}`);
|
|
5770
|
+
if (allowsUpdate(table) && pk.length > 0) {
|
|
5771
|
+
lines.push(` ${names.updateByPkRoot}(pkColumns: ${names.pkColumnsInput}!, _set: ${names.setInput}!): ${names.typename}`);
|
|
5772
|
+
lines.push(` ${names.revertByPkRoot}(pkColumns: ${names.pkColumnsInput}!, columns: [${names.updateColumnEnum}!]!): ${names.typename}`);
|
|
5773
|
+
if ((table.resolved?.deletedBy.length ?? 0) > 0) {
|
|
5774
|
+
const args = pk.map((column) => `${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join(", ");
|
|
5775
|
+
lines.push(` ${names.deleteByPkRoot}(${args}): ${names.typename}`);
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
return lines;
|
|
5779
|
+
}
|
|
5780
|
+
function allowsInsert(table) {
|
|
5781
|
+
return table.source === void 0 && (table.policies.write !== void 0 || table.policies.insert !== void 0);
|
|
5782
|
+
}
|
|
5783
|
+
function allowsUpdate(table) {
|
|
5784
|
+
return table.source === void 0 && (table.policies.write !== void 0 || table.policies.update !== void 0);
|
|
5785
|
+
}
|
|
5786
|
+
function allowsOnConflict(table) {
|
|
5787
|
+
if (!allowsInsert(table) || !allowsUpdate(table)) return false;
|
|
5788
|
+
if (table.policies.insert !== void 0 || table.policies.update !== void 0) return false;
|
|
5789
|
+
return table.policies.write !== void 0;
|
|
5790
|
+
}
|
|
5791
|
+
function renderObjectType(ctx) {
|
|
5792
|
+
const { table, names } = ctx;
|
|
5793
|
+
const lines = [];
|
|
5794
|
+
if (table.description !== void 0) lines.push(...descriptionLines(table.description, ""));
|
|
5795
|
+
lines.push(`type ${names.typename} {`);
|
|
5796
|
+
for (const column of table.columns) {
|
|
5797
|
+
if (column.description !== void 0) lines.push(...descriptionLines(column.description, " "));
|
|
5798
|
+
const bang = column.nullable ? "" : "!";
|
|
5799
|
+
const deprecated = column.deprecated ? " @deprecated" : "";
|
|
5800
|
+
lines.push(` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}${bang}${deprecated}`);
|
|
5801
|
+
}
|
|
5802
|
+
if (ctx.cells) {
|
|
5803
|
+
lines.push(' """Per-cell metadata: when each field was last written, and by whom."""');
|
|
5804
|
+
lines.push(` ${CELLS_FIELD}: ${cellsTypename(names.typename)}!`);
|
|
5805
|
+
}
|
|
5806
|
+
for (const link of table.links) {
|
|
5807
|
+
if (link.description !== void 0) lines.push(...descriptionLines(link.description, " "));
|
|
5808
|
+
if (link.kind === "toPlatformUser") {
|
|
5809
|
+
lines.push(` ${fieldName(link.name)}: ${PLATFORM_USER_TYPENAME}`);
|
|
5810
|
+
continue;
|
|
5811
|
+
}
|
|
5812
|
+
const target = link.target !== void 0 ? ctx.namesByTable.get(link.target) : void 0;
|
|
5813
|
+
if (!target) continue;
|
|
5814
|
+
if (link.kind === "toOne") {
|
|
5815
|
+
lines.push(` ${fieldName(link.name)}: ${target.typename}`);
|
|
5816
|
+
} else {
|
|
5817
|
+
lines.push(` ${fieldName(link.name)}(${relationArgs(target)}): [${target.typename}!]!`);
|
|
5818
|
+
lines.push(` ${fieldName(link.name)}Aggregate(${relationArgs(target)}): ${target.aggregate}!`);
|
|
5819
|
+
}
|
|
5820
|
+
}
|
|
5821
|
+
lines.push("}");
|
|
5822
|
+
return lines.join("\n");
|
|
5823
|
+
}
|
|
5824
|
+
function renderSelectColumnEnum(ctx) {
|
|
5825
|
+
const values = ctx.table.columns.map((column) => fieldName(column.name));
|
|
5826
|
+
for (const value of values) assertEnumValue(value, `select-column enum for table "${ctx.table.table}"`);
|
|
5827
|
+
return renderEnum(ctx.names.selectColumn, values);
|
|
5828
|
+
}
|
|
5829
|
+
function renderOrderByInput(ctx) {
|
|
5830
|
+
const { table, names } = ctx;
|
|
5831
|
+
const lines = table.columns.map((column) => ` ${fieldName(column.name)}: ${ORDER_BY_ENUM}`);
|
|
5832
|
+
for (const link of table.links) {
|
|
5833
|
+
if (link.kind === "toPlatformUser") {
|
|
5834
|
+
lines.push(` ${fieldName(link.name)}: ${PLATFORM_USER_TYPENAME}OrderBy`);
|
|
5835
|
+
} else if (link.kind === "toOne" && link.target !== void 0) {
|
|
5836
|
+
const target = ctx.namesByTable.get(link.target);
|
|
5837
|
+
if (target) lines.push(` ${fieldName(link.name)}: ${target.orderBy}`);
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
return `input ${names.orderBy} {
|
|
5841
|
+
${lines.join("\n")}
|
|
5842
|
+
}`;
|
|
5843
|
+
}
|
|
5844
|
+
function renderBoolExp(ctx) {
|
|
5845
|
+
const { table, names } = ctx;
|
|
5846
|
+
const lines = [
|
|
5847
|
+
` _and: [${names.boolExp}!]`,
|
|
5848
|
+
` _not: ${names.boolExp}`,
|
|
5849
|
+
` _or: [${names.boolExp}!]`
|
|
5850
|
+
];
|
|
5851
|
+
for (const column of table.columns) {
|
|
5852
|
+
lines.push(` ${fieldName(column.name)}: ${comparisonExpName(columnGqlTypeOf(table, column))}`);
|
|
5853
|
+
}
|
|
5854
|
+
for (const link of table.links) {
|
|
5855
|
+
if (link.kind === "toPlatformUser") {
|
|
5856
|
+
lines.push(` ${fieldName(link.name)}: ${PLATFORM_USER_TYPENAME}BoolExp`);
|
|
5857
|
+
continue;
|
|
5858
|
+
}
|
|
5859
|
+
const target = link.target !== void 0 ? ctx.namesByTable.get(link.target) : void 0;
|
|
5860
|
+
if (!target) continue;
|
|
5861
|
+
lines.push(` ${fieldName(link.name)}: ${target.boolExp}`);
|
|
5862
|
+
if (link.kind === "toMany") {
|
|
5863
|
+
lines.push(` ${fieldName(link.name)}Aggregate: ${target.aggregateBoolExp}`);
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5866
|
+
return `input ${names.boolExp} {
|
|
5867
|
+
${lines.join("\n")}
|
|
5868
|
+
}`;
|
|
5869
|
+
}
|
|
5870
|
+
function numericishColumns(table) {
|
|
5871
|
+
return table.columns.filter((column) => typeof column.type !== "object" && NUMERICISH.has(column.type));
|
|
5872
|
+
}
|
|
5873
|
+
function jsonbColumns(table) {
|
|
5874
|
+
return clientWritableColumns(table).filter((column) => column.type === "jsonb");
|
|
5875
|
+
}
|
|
5876
|
+
function clientWritableColumns(table) {
|
|
5877
|
+
return table.columns;
|
|
5878
|
+
}
|
|
5879
|
+
function renderAggregateTypes(ctx) {
|
|
5880
|
+
const { table, names } = ctx;
|
|
5881
|
+
const numericish = numericishColumns(table);
|
|
5882
|
+
const blocks = [];
|
|
5883
|
+
blocks.push(
|
|
5884
|
+
`type ${names.aggregate} {
|
|
5885
|
+
aggregate: ${names.aggregateFields}
|
|
5886
|
+
nodes: [${names.typename}!]!
|
|
5887
|
+
}`
|
|
5888
|
+
);
|
|
5889
|
+
const fieldLines = [` count(columns: [${names.selectColumn}!], distinct: Boolean): Int!`];
|
|
5890
|
+
if (numericish.length > 0) {
|
|
5891
|
+
fieldLines.push(` avg: ${names.avgFields}`);
|
|
5892
|
+
fieldLines.push(` sum: ${names.sumFields}`);
|
|
5893
|
+
}
|
|
5894
|
+
blocks.push(`type ${names.aggregateFields} {
|
|
5895
|
+
${fieldLines.join("\n")}
|
|
5896
|
+
}`);
|
|
5897
|
+
if (numericish.length > 0) {
|
|
5898
|
+
const avgLines = numericish.map((column) => ` ${fieldName(column.name)}: Float`);
|
|
5899
|
+
blocks.push(`type ${names.avgFields} {
|
|
5900
|
+
${avgLines.join("\n")}
|
|
5901
|
+
}`);
|
|
5902
|
+
const sumLines = numericish.map((column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`);
|
|
5903
|
+
blocks.push(`type ${names.sumFields} {
|
|
5904
|
+
${sumLines.join("\n")}
|
|
5905
|
+
}`);
|
|
5906
|
+
}
|
|
5907
|
+
return blocks;
|
|
5908
|
+
}
|
|
5909
|
+
function groupableColumns(table) {
|
|
5910
|
+
return table.columns.filter((column) => column.type !== "jsonb");
|
|
5911
|
+
}
|
|
5912
|
+
function renderGroupByTypes(ctx) {
|
|
5913
|
+
const { table, names } = ctx;
|
|
5914
|
+
const groupable = groupableColumns(table);
|
|
5915
|
+
if (groupable.length === 0) return [];
|
|
5916
|
+
const keyLines = groupable.map((column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`);
|
|
5917
|
+
return [
|
|
5918
|
+
renderEnum(names.groupByColumnEnum, groupable.map((column) => fieldName(column.name))),
|
|
5919
|
+
`input ${names.groupByInput} {
|
|
5920
|
+
column: ${names.groupByColumnEnum}!
|
|
5921
|
+
bucket: ${DATE_BUCKET_ENUM}
|
|
5922
|
+
}`,
|
|
5923
|
+
`type ${names.groupKey} {
|
|
5924
|
+
${keyLines.join("\n")}
|
|
5925
|
+
}`,
|
|
5926
|
+
`type ${names.aggregateGroup} {
|
|
5927
|
+
key: ${names.groupKey}!
|
|
5928
|
+
aggregate: ${names.aggregateFields}
|
|
5929
|
+
}`
|
|
5930
|
+
];
|
|
5931
|
+
}
|
|
5932
|
+
function groupRootArgs(names) {
|
|
5933
|
+
return `where: ${names.boolExp}, groupBy: [${names.groupByInput}!]!, limit: Int, offset: Int`;
|
|
5934
|
+
}
|
|
5935
|
+
function renderAggregateBoolExp(ctx) {
|
|
5936
|
+
const { names } = ctx;
|
|
5937
|
+
return [
|
|
5938
|
+
`input ${names.aggregateBoolExp} {
|
|
5939
|
+
count: ${names.aggregateBoolExpCount}
|
|
5940
|
+
}`,
|
|
5941
|
+
`input ${names.aggregateBoolExpCount} {
|
|
5942
|
+
arguments: [${names.selectColumn}!]
|
|
5943
|
+
distinct: Boolean
|
|
5944
|
+
filter: ${names.boolExp}
|
|
5945
|
+
predicate: IntComparisonExp!
|
|
5946
|
+
}`
|
|
5947
|
+
];
|
|
5948
|
+
}
|
|
5949
|
+
function constraintValues(table) {
|
|
5950
|
+
const values = /* @__PURE__ */ new Set();
|
|
5951
|
+
if (table.primaryKey && table.primaryKey.length > 0) values.add(`${table.table}_pkey`);
|
|
5952
|
+
for (const column of table.columns) {
|
|
5953
|
+
if (column.unique) values.add(`${table.table}_${column.columnName}_unique`);
|
|
5954
|
+
}
|
|
5955
|
+
for (const index of table.indexes) {
|
|
5956
|
+
if (index.unique) values.add(index.name ?? `${table.table}_${index.columns.join("_")}_key`);
|
|
5957
|
+
}
|
|
5958
|
+
return [...values].sort();
|
|
5959
|
+
}
|
|
5960
|
+
function clientSettableColumns(table) {
|
|
5961
|
+
return clientWritableColumns(table).filter((column) => column.onUpdateFn === void 0);
|
|
5962
|
+
}
|
|
5963
|
+
function renderWriteBlocks(ctx, access) {
|
|
5964
|
+
const { table, names, pinned } = ctx;
|
|
5965
|
+
const blocks = [];
|
|
5966
|
+
if (access.insert) {
|
|
5967
|
+
const pinnedSet = new Set(pinned);
|
|
5968
|
+
const insertable = clientWritableColumns(table).filter(
|
|
5969
|
+
(column) => !pinnedSet.has(column.name) && !pinnedSet.has(column.columnName)
|
|
5970
|
+
);
|
|
5971
|
+
if (insertable.length === 0) {
|
|
5972
|
+
throw new SdlGenerationError(
|
|
5973
|
+
`Table "${table.table}" has no insertable columns after excluding pinned columns [${pinned.join(", ")}].`
|
|
5974
|
+
);
|
|
5975
|
+
}
|
|
5976
|
+
const insertLines = insertable.map(
|
|
5977
|
+
(column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
|
|
5978
|
+
);
|
|
5979
|
+
blocks.push(`input ${names.insertInput} {
|
|
5980
|
+
${insertLines.join("\n")}
|
|
5981
|
+
}`);
|
|
5982
|
+
}
|
|
5983
|
+
if (access.update) {
|
|
5984
|
+
const setLines = clientSettableColumns(table).map(
|
|
5985
|
+
(column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
|
|
5986
|
+
);
|
|
5987
|
+
blocks.push(`input ${names.setInput} {
|
|
5988
|
+
${setLines.join("\n")}
|
|
5989
|
+
}`);
|
|
5990
|
+
}
|
|
5991
|
+
const jsonb = jsonbColumns(table);
|
|
5992
|
+
if (access.update && jsonb.length > 0) {
|
|
5993
|
+
const appendLines = jsonb.map((column) => ` ${fieldName(column.name)}: jsonb`);
|
|
5994
|
+
blocks.push(`input ${names.appendInput} {
|
|
5995
|
+
${appendLines.join("\n")}
|
|
5996
|
+
}`);
|
|
5997
|
+
}
|
|
5998
|
+
const numericish = numericishColumns(table);
|
|
5999
|
+
if (access.update && numericish.length > 0) {
|
|
6000
|
+
const incLines = numericish.map(
|
|
6001
|
+
(column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
|
|
6002
|
+
);
|
|
6003
|
+
blocks.push(`input ${names.incInput} {
|
|
6004
|
+
${incLines.join("\n")}
|
|
6005
|
+
}`);
|
|
6006
|
+
}
|
|
6007
|
+
const constraints = constraintValues(table);
|
|
6008
|
+
if (access.insert && allowsOnConflict(table) && constraints.length > 0) {
|
|
6009
|
+
for (const value of constraints) assertEnumValue(value, `constraint enum for table "${table.table}"`);
|
|
6010
|
+
blocks.push(renderEnum(names.constraintEnum, constraints));
|
|
6011
|
+
const updateColumns = clientSettableColumns(table).map((column) => fieldName(column.name));
|
|
6012
|
+
blocks.push(renderEnum(names.updateColumnEnum, updateColumns));
|
|
6013
|
+
blocks.push(
|
|
6014
|
+
`input ${names.onConflict} {
|
|
6015
|
+
constraint: ${names.constraintEnum}!
|
|
6016
|
+
updateColumns: [${names.updateColumnEnum}!]! = []
|
|
6017
|
+
where: ${names.boolExp}
|
|
6018
|
+
}`
|
|
6019
|
+
);
|
|
6020
|
+
}
|
|
6021
|
+
if (access.insert || access.update) {
|
|
6022
|
+
blocks.push(
|
|
6023
|
+
`type ${names.mutationResponse} {
|
|
6024
|
+
affectedRows: Int!
|
|
6025
|
+
returning: [${names.typename}!]!
|
|
6026
|
+
}`
|
|
6027
|
+
);
|
|
6028
|
+
}
|
|
6029
|
+
return blocks;
|
|
6030
|
+
}
|
|
6031
|
+
function renderQueryRoot(tables, namesByTable) {
|
|
6032
|
+
const lines = ["type Query {"];
|
|
6033
|
+
for (const table of tables) {
|
|
6034
|
+
const names = namesByTable.get(table.table);
|
|
6035
|
+
if (!names) continue;
|
|
6036
|
+
lines.push(` ${names.selectRoot}(${rootArgs(names)}): [${names.typename}!]!`);
|
|
6037
|
+
const pk = primaryKeyColumns(table);
|
|
6038
|
+
if (pk.length > 0) {
|
|
6039
|
+
const args = pk.map((column) => `${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join(", ");
|
|
6040
|
+
lines.push(` ${names.byPkRoot}(${args}): ${names.typename}`);
|
|
6041
|
+
}
|
|
6042
|
+
lines.push(` ${names.aggregateRoot}(${rootArgs(names)}): ${names.aggregate}!`);
|
|
6043
|
+
if (groupableColumns(table).length > 0) {
|
|
6044
|
+
lines.push(` ${names.aggregateGroupsRoot}(${groupRootArgs(names)}): [${names.aggregateGroup}!]!`);
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
6047
|
+
lines.push("}");
|
|
6048
|
+
return lines.join("\n");
|
|
6049
|
+
}
|
|
6050
|
+
function columnGqlTypeOf(table, column) {
|
|
6051
|
+
return typeof column.type === "object" ? enumTypeName(column.type.enum) : graphqlScalarFor(column.type);
|
|
6052
|
+
}
|
|
6053
|
+
function renderMutationRoot(tables, namesByTable) {
|
|
6054
|
+
const lines = [];
|
|
6055
|
+
for (const table of tables) {
|
|
6056
|
+
const canInsert = allowsInsert(table);
|
|
6057
|
+
const canUpdate = allowsUpdate(table);
|
|
6058
|
+
if (!canInsert && !canUpdate) continue;
|
|
6059
|
+
const names = namesByTable.get(table.table);
|
|
6060
|
+
if (!names) continue;
|
|
6061
|
+
if (table.resolved !== void 0) {
|
|
6062
|
+
lines.push(...renderResolvedMutationFields(table, names));
|
|
6063
|
+
continue;
|
|
6064
|
+
}
|
|
6065
|
+
const constraints = constraintValues(table);
|
|
6066
|
+
const onConflictArg = canInsert && allowsOnConflict(table) && constraints.length > 0 ? `, onConflict: ${names.onConflict}` : "";
|
|
6067
|
+
const updateArgs = [`_set: ${names.setInput}`];
|
|
6068
|
+
if (jsonbColumns(table).length > 0) updateArgs.push(`_append: ${names.appendInput}`);
|
|
6069
|
+
if (numericishColumns(table).length > 0) updateArgs.push(`_inc: ${names.incInput}`);
|
|
6070
|
+
const updateArgList = updateArgs.join(", ");
|
|
6071
|
+
if (canInsert) {
|
|
6072
|
+
lines.push(` ${names.insertRoot}(objects: [${names.insertInput}!]!${onConflictArg}): ${names.mutationResponse}`);
|
|
6073
|
+
lines.push(` ${names.insertOneRoot}(object: ${names.insertInput}!${onConflictArg}): ${names.typename}`);
|
|
6074
|
+
}
|
|
6075
|
+
if (canUpdate) {
|
|
6076
|
+
lines.push(` ${names.updateRoot}(where: ${names.boolExp}!, ${updateArgList}): ${names.mutationResponse}`);
|
|
6077
|
+
const pk = primaryKeyColumns(table);
|
|
6078
|
+
if (pk.length > 0) {
|
|
6079
|
+
lines.push(
|
|
6080
|
+
` ${names.updateByPkRoot}(pkColumns: ${names.pkColumnsInput}!, ${updateArgList}): ${names.typename}`
|
|
6081
|
+
);
|
|
6082
|
+
}
|
|
6083
|
+
lines.push(` ${names.deleteRoot}(where: ${names.boolExp}!): ${names.mutationResponse}`);
|
|
6084
|
+
if (pk.length > 0) {
|
|
6085
|
+
const args = pk.map((column) => `${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join(", ");
|
|
6086
|
+
lines.push(` ${names.deleteByPkRoot}(${args}): ${names.typename}`);
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
lines.push(...renderCowMutationFields(table, names));
|
|
6090
|
+
}
|
|
6091
|
+
if (lines.length === 0) return null;
|
|
6092
|
+
return `type Mutation {
|
|
6093
|
+
${lines.join("\n")}
|
|
6094
|
+
}`;
|
|
6095
|
+
}
|
|
6096
|
+
function renderCowMutationFields(table, names) {
|
|
6097
|
+
const lines = [];
|
|
6098
|
+
const columnByName = new Map(table.columns.map((column) => [column.name, column]));
|
|
6099
|
+
for (const mutation of table.cowMutations ?? []) {
|
|
6100
|
+
const pkColumn = columnByName.get(mutation.pk);
|
|
6101
|
+
if (!pkColumn) continue;
|
|
6102
|
+
const pkArg = `${fieldName(mutation.pk)}: ${columnGqlTypeOf(table, pkColumn)}!`;
|
|
6103
|
+
if (mutation.kind === "revert") {
|
|
6104
|
+
lines.push(` ${fieldName(mutation.name)}(${pkArg}): ${names.typename}`);
|
|
6105
|
+
continue;
|
|
6106
|
+
}
|
|
6107
|
+
const setArgs = mutation.set.map((property) => {
|
|
6108
|
+
const column = columnByName.get(property);
|
|
6109
|
+
if (!column) return null;
|
|
6110
|
+
const bang = column.nullable === false ? "!" : "";
|
|
6111
|
+
return `${fieldName(property)}: ${columnGqlTypeOf(table, column)}${bang}`;
|
|
6112
|
+
});
|
|
6113
|
+
if (setArgs.some((arg) => arg === null)) continue;
|
|
6114
|
+
lines.push(` ${fieldName(mutation.name)}(${pkArg}, ${setArgs.join(", ")}): ${names.typename}`);
|
|
6115
|
+
}
|
|
6116
|
+
return lines;
|
|
6117
|
+
}
|
|
6118
|
+
|
|
6119
|
+
// src/compiler-gql/default-operations.ts
|
|
6120
|
+
var DEFAULT_LIST_LIMIT = 100;
|
|
6121
|
+
function typeReference(type, nonNull = false) {
|
|
6122
|
+
if (type instanceof graphql.GraphQLNonNull) return `${typeReference(type.ofType, true)}`;
|
|
6123
|
+
const inner = type instanceof graphql.GraphQLList ? `[${typeReference(type.ofType)}]` : graphql.getNamedType(type).name;
|
|
6124
|
+
return `${inner}${nonNull ? "!" : ""}`;
|
|
6125
|
+
}
|
|
6126
|
+
function objectScalarFields(type) {
|
|
6127
|
+
return Object.entries(type.getFields()).filter(([, field]) => {
|
|
6128
|
+
const named = graphql.getNamedType(field.type);
|
|
6129
|
+
return named instanceof graphql.GraphQLScalarType || named instanceof graphql.GraphQLEnumType;
|
|
6130
|
+
}).map(([name2]) => name2);
|
|
6131
|
+
}
|
|
6132
|
+
function argumentInputFields(arg) {
|
|
6133
|
+
if (arg.name !== "pkColumns") return void 0;
|
|
6134
|
+
const input = graphql.getNamedType(arg.type);
|
|
6135
|
+
if (!(input instanceof graphql.GraphQLInputObjectType)) return void 0;
|
|
6136
|
+
return Object.values(input.getFields()).map((field) => ({
|
|
6137
|
+
name: field.name,
|
|
6138
|
+
type: field.type,
|
|
6139
|
+
variable: field.name
|
|
6140
|
+
}));
|
|
6141
|
+
}
|
|
6142
|
+
function inputFieldsTouchWriteback(root, writebackColumns) {
|
|
6143
|
+
if (!root) return false;
|
|
6144
|
+
const input = graphql.getNamedType(root.type);
|
|
6145
|
+
return input instanceof graphql.GraphQLInputObjectType && Object.keys(input.getFields()).some((field) => writebackColumns.has(field));
|
|
6146
|
+
}
|
|
6147
|
+
function variableDefinitions(args, defaults = {}) {
|
|
6148
|
+
return args.flatMap((arg) => {
|
|
6149
|
+
const inputFields = argumentInputFields(arg);
|
|
6150
|
+
if (inputFields) {
|
|
6151
|
+
return inputFields.map((field) => `$${field.variable}: ${typeReference(field.type)}`);
|
|
6152
|
+
}
|
|
6153
|
+
const defaultValue = defaults[arg.name];
|
|
6154
|
+
return [`$${arg.name}: ${typeReference(arg.type)}${defaultValue === void 0 ? "" : ` = ${defaultValue}`}`];
|
|
6155
|
+
}).join(", ");
|
|
6156
|
+
}
|
|
6157
|
+
function argumentUses(args) {
|
|
6158
|
+
return args.map((arg) => {
|
|
6159
|
+
const inputFields = argumentInputFields(arg);
|
|
6160
|
+
if (inputFields) {
|
|
6161
|
+
const values = inputFields.map((field) => `${field.name}: $${field.variable}`);
|
|
6162
|
+
return `${arg.name}: { ${values.join(", ")} }`;
|
|
6163
|
+
}
|
|
6164
|
+
return `${arg.name}: $${arg.name}`;
|
|
6165
|
+
}).join(", ");
|
|
6166
|
+
}
|
|
6167
|
+
function callArguments(args) {
|
|
6168
|
+
const uses = argumentUses(args);
|
|
6169
|
+
return uses ? `(${uses})` : "";
|
|
6170
|
+
}
|
|
6171
|
+
function listOperationName(table) {
|
|
6172
|
+
const raw = pascalCase(table);
|
|
6173
|
+
if (raw !== typeName(table)) return `List${raw}`;
|
|
6174
|
+
if (raw.endsWith("y") && !/[aeiou]y$/.test(raw)) return `List${raw.slice(0, -1)}ies`;
|
|
6175
|
+
if (/(s|x|ch|sh)$/.test(raw)) return `List${raw}es`;
|
|
6176
|
+
return `List${raw}s`;
|
|
6177
|
+
}
|
|
6178
|
+
function document(path, operation) {
|
|
6179
|
+
return { path: `graphql/__generated__/${path}.graphql`, contents: `${operation.trim()}
|
|
6180
|
+
` };
|
|
6181
|
+
}
|
|
6182
|
+
function generateDefaultOperations(canonical, sdl, existingOperationNames = []) {
|
|
6183
|
+
const definition = canonicalDefinitionSchema.parse(canonical);
|
|
6184
|
+
const composed = composeNamespace(definition).definition;
|
|
6185
|
+
const schema = parseSdl(sdl);
|
|
6186
|
+
const query = schema.getQueryType();
|
|
6187
|
+
const mutation = schema.getMutationType();
|
|
6188
|
+
if (!query) return [];
|
|
6189
|
+
const owned = /* @__PURE__ */ new Set([
|
|
6190
|
+
...definition.tables.map((table) => table.table),
|
|
6191
|
+
...(definition.views ?? []).map((view) => view.view)
|
|
6192
|
+
]);
|
|
6193
|
+
const occupied = new Set(existingOperationNames);
|
|
6194
|
+
const generatedNames = /* @__PURE__ */ new Map();
|
|
6195
|
+
const results = [];
|
|
6196
|
+
const claimName = (name2, relation) => {
|
|
6197
|
+
if (occupied.has(name2)) return false;
|
|
6198
|
+
const previous = generatedNames.get(name2);
|
|
6199
|
+
if (previous !== void 0) throw new Error(`default operation name collision '${name2}' for '${previous}' and '${relation}'`);
|
|
6200
|
+
generatedNames.set(name2, relation);
|
|
6201
|
+
return true;
|
|
6202
|
+
};
|
|
6203
|
+
for (const relation of canonicalRelations(composed).filter((item) => owned.has(item.table) && item.internal !== true)) {
|
|
6204
|
+
const names = tableNames(relation.table);
|
|
6205
|
+
const fields = query.getFields();
|
|
6206
|
+
const objectType = schema.getType(names.typename);
|
|
6207
|
+
const scalarSelection = objectType instanceof graphql.GraphQLObjectType ? objectScalarFields(objectType) : [];
|
|
6208
|
+
const selection = scalarSelection.length > 0 ? `{ ${scalarSelection.join(" ")} }` : "";
|
|
6209
|
+
const byPk = fields[names.byPkRoot];
|
|
6210
|
+
const getName = `Get${names.typename}`;
|
|
6211
|
+
if (byPk && selection && claimName(getName, relation.table)) {
|
|
6212
|
+
const vars = variableDefinitions(byPk.args);
|
|
6213
|
+
results.push(document(getName, `query ${getName}${vars ? `(${vars})` : ""} { record: ${names.byPkRoot}${callArguments(byPk.args)} ${selection} }`));
|
|
6214
|
+
}
|
|
6215
|
+
const list2 = fields[names.selectRoot];
|
|
6216
|
+
const listName = listOperationName(relation.table);
|
|
6217
|
+
if (list2 && selection && claimName(listName, relation.table)) {
|
|
6218
|
+
const listArgs = list2.args.filter((arg) => ["where", "orderBy", "limit", "offset"].includes(arg.name));
|
|
6219
|
+
const defaults = { limit: String(DEFAULT_LIST_LIMIT), offset: "0" };
|
|
6220
|
+
const vars = variableDefinitions(listArgs, defaults);
|
|
6221
|
+
results.push(document(listName, `query ${listName}${vars ? `(${vars})` : ""} { records: ${names.selectRoot}${callArguments(listArgs)} ${selection} }`));
|
|
6222
|
+
}
|
|
6223
|
+
if (!mutation || !selection) continue;
|
|
6224
|
+
const mutationFields = mutation.getFields();
|
|
6225
|
+
const mutationOps = [
|
|
6226
|
+
{ root: names.insertOneRoot, name: `Create${names.typename}`, args: ["object"], writeArg: "object" },
|
|
6227
|
+
{ root: names.updateByPkRoot, name: `Update${names.typename}`, args: ["pkColumns", "_set"], writeArg: "_set" },
|
|
6228
|
+
{ root: names.deleteByPkRoot, name: `Delete${names.typename}` }
|
|
6229
|
+
];
|
|
6230
|
+
const writebackColumns = new Set((relation.resolved?.sources.flatMap((source) => source.writeback?.columns ?? []) ?? []).map(fieldName));
|
|
6231
|
+
for (const item of mutationOps) {
|
|
6232
|
+
const root = mutationFields[item.root];
|
|
6233
|
+
if (root && item.writeArg && inputFieldsTouchWriteback(root.args.find((arg) => arg.name === item.writeArg), writebackColumns)) continue;
|
|
6234
|
+
if (!root || !claimName(item.name, relation.table)) continue;
|
|
6235
|
+
const args = item.args ? root.args.filter((arg) => item.args?.includes(arg.name)) : root.args;
|
|
6236
|
+
const vars = variableDefinitions(args);
|
|
6237
|
+
results.push(document(item.name, `mutation ${item.name}${vars ? `(${vars})` : ""} { record: ${item.root}${callArguments(args)} ${selection} }`));
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6240
|
+
return results.map((source) => {
|
|
6241
|
+
try {
|
|
6242
|
+
return { ...source, contents: `${graphql.print(graphql.parse(source.contents)).trim()}
|
|
6243
|
+
` };
|
|
6244
|
+
} catch (error) {
|
|
6245
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}
|
|
6246
|
+
${source.contents}`);
|
|
6247
|
+
}
|
|
6248
|
+
});
|
|
6249
|
+
}
|
|
6250
|
+
function compileDefaultOperations(canonical, existingOperationNames = []) {
|
|
6251
|
+
const pinnedColumns = Object.fromEntries(
|
|
6252
|
+
Object.entries(computePinnedColumnsByTable(canonical)).map(([table, pins]) => [table, pins.map((pin) => pin.column)])
|
|
6253
|
+
);
|
|
6254
|
+
const sdl = generateSdl(canonical, { pinnedColumns, includeSyncedReads: true });
|
|
6255
|
+
const documents = generateDefaultOperations(canonical, sdl, existingOperationNames);
|
|
6256
|
+
return compileNamespaceOrThrow({ canonical, sdl, documents }).artifact;
|
|
6257
|
+
}
|
|
6258
|
+
|
|
5432
6259
|
// src/engine/types.ts
|
|
5433
6260
|
var SQL_TYPE_NAMES = {
|
|
5434
6261
|
double: "double precision",
|
|
@@ -6159,9 +6986,9 @@ async function compileOperationDocuments(input) {
|
|
|
6159
6986
|
return compileSqlDocuments({ input, result, sqlDocuments });
|
|
6160
6987
|
}
|
|
6161
6988
|
async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
6162
|
-
for (const
|
|
6989
|
+
for (const document2 of sqlDocuments) {
|
|
6163
6990
|
try {
|
|
6164
|
-
const definition = defineOperation(JSON.parse(
|
|
6991
|
+
const definition = defineOperation(JSON.parse(document2.contents));
|
|
6165
6992
|
if (definition.namespace !== input.canonical.namespace) throw new Error("SQL operation namespace does not match its owner");
|
|
6166
6993
|
if (result.artifact.operations[definition.name]) throw new Error("duplicate operation name");
|
|
6167
6994
|
const roles = input.canonical.capabilities.roles;
|
|
@@ -6241,7 +7068,7 @@ async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
|
6241
7068
|
return { ok: false, diagnostics: [...result.diagnostics, {
|
|
6242
7069
|
severity: "error",
|
|
6243
7070
|
code: "invalid-sql-operation",
|
|
6244
|
-
document:
|
|
7071
|
+
document: document2.path,
|
|
6245
7072
|
message: error instanceof Error ? error.message : "invalid SQL operation"
|
|
6246
7073
|
}] };
|
|
6247
7074
|
}
|
|
@@ -6449,6 +7276,7 @@ exports.classifyInputType = classifyInputType;
|
|
|
6449
7276
|
exports.collectPinnedColumns = collectPinnedColumns;
|
|
6450
7277
|
exports.compileBuiltinSqlArtifact = compileBuiltinSqlArtifact;
|
|
6451
7278
|
exports.compileDatasetPreviews = compileDatasetPreviews;
|
|
7279
|
+
exports.compileDefaultOperations = compileDefaultOperations;
|
|
6452
7280
|
exports.compileNamespace = compileNamespace;
|
|
6453
7281
|
exports.compileNamespaceOrThrow = compileNamespaceOrThrow;
|
|
6454
7282
|
exports.compileOperationDocuments = compileOperationDocuments;
|
|
@@ -6462,6 +7290,7 @@ exports.computeSchemaHash = computeSchemaHash;
|
|
|
6462
7290
|
exports.defineOperation = defineOperation;
|
|
6463
7291
|
exports.enumTypeName = enumTypeName;
|
|
6464
7292
|
exports.flattenOperation = flattenOperation;
|
|
7293
|
+
exports.generateDefaultOperations = generateDefaultOperations;
|
|
6465
7294
|
exports.injectPolicies = injectPolicies;
|
|
6466
7295
|
exports.inputTypeName = inputTypeName;
|
|
6467
7296
|
exports.isSqlOperationDocument = isSqlOperationDocument;
|