@lunora/codegen 1.0.0-alpha.2 → 1.0.0-alpha.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/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +444 -16
- package/dist/index.d.ts +444 -16
- package/dist/index.mjs +28 -22
- package/dist/packem_shared/{CONTAINERS_FILENAME-0K-pjNb8.mjs → CONTAINERS_FILENAME-DlP6YM_Q.mjs} +1 -1
- package/dist/packem_shared/{CodegenDiagnosticError-54jWDxA9.mjs → CodegenDiagnosticError-DeblMkzO.mjs} +3 -2
- package/dist/packem_shared/FLAGS_FILENAME-B1vE0LIo.mjs +139 -0
- package/dist/packem_shared/{emitApi-hRVC-kE7.mjs → GENERATED_HEADER-CI272wRm.mjs} +873 -54
- package/dist/packem_shared/LUNORA_SOLUTION_RULES-BTejmZp-.mjs +131 -0
- package/dist/packem_shared/MUTATORS_FILENAME-BhqdPtKp.mjs +81 -0
- package/dist/packem_shared/{buildOpenRpcDocument-BZGY1-jT.mjs → OPENRPC_VERSION-t5pV2NIN.mjs} +1 -1
- package/dist/packem_shared/QUEUES_FILENAME-B5_eWCRe.mjs +119 -0
- package/dist/packem_shared/{createCodegenProject-DGJm0_Pk.mjs → SCHEMA_SNAPSHOT_FILENAME-BMez-jgr.mjs} +187 -43
- package/dist/packem_shared/{buildSchemaSnapshot-DzLDbWk3.mjs → SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs} +12 -0
- package/dist/packem_shared/SHAPES_FILENAME-ChV7MqgE.mjs +94 -0
- package/dist/packem_shared/{discoverWorkflows-DRDQdhfq.mjs → WORKFLOWS_FILENAME-CCisG0Vy.mjs} +42 -2
- package/dist/packem_shared/{buildOpenApiDocument-yHVN66Xd.mjs → buildOpenApiDocument-Dx5QftUn.mjs} +1 -1
- package/dist/packem_shared/{discoverAuthApiCalls-C35R6z0T.mjs → discoverAuthApiCalls-CoirYbg6.mjs} +1 -1
- package/dist/packem_shared/{discoverCrons-BL6iGuJ3.mjs → discoverCrons-Cev7RRAf.mjs} +20 -17
- package/dist/packem_shared/{discoverFunctions-DEgAcRuD.mjs → discoverFunctions-BWMczzBx.mjs} +7 -2
- package/dist/packem_shared/{discoverHttpRoutes-C978pBiG.mjs → discoverHttpRoutes-CfP6cMzt.mjs} +2 -2
- package/dist/packem_shared/{discoverInserts-CRQdXvHO.mjs → discoverInserts-C7zxXkUf.mjs} +24 -2
- package/dist/packem_shared/{discoverMaskProcedures-B64zA740.mjs → discoverMaskProcedures-Cm81kwrb.mjs} +1 -1
- package/dist/packem_shared/{discoverMigrations-Doj_-BAA.mjs → discoverMigrations-Bi5nJ0mJ.mjs} +10 -4
- package/dist/packem_shared/{discoverNondeterministicCalls-4KiPQxQU.mjs → discoverNondeterministicCalls-C4M8AXmQ.mjs} +1 -1
- package/dist/packem_shared/{discoverQueries-BkIi0dBD.mjs → discoverQueries-B0wGT-xe.mjs} +1 -1
- package/dist/packem_shared/discoverR2sqlCalls-BVNMd428.mjs +80 -0
- package/dist/packem_shared/{discoverRlsMetadata-DpRB1HMe.mjs → discoverRlsMetadata-BS9GOGC5.mjs} +1 -1
- package/dist/packem_shared/{discoverSchema-BBulgGbH.mjs → discoverSchema-KYq55qu3.mjs} +338 -16
- package/dist/packem_shared/{discoverStorageRulesMetadata-DAqJUxUv.mjs → discoverStorageRulesMetadata-Da8BKXcI.mjs} +1 -1
- package/dist/packem_shared/{emitApp-CzZ6GbrD.mjs → emitApp-cOSypPuO.mjs} +74 -8
- package/dist/packem_shared/{lintSchema-DicbOHvH.mjs → formatAdvisories-DdjK7sgh.mjs} +15 -2
- package/dist/packem_shared/{parse-validator-tuQtHrsr.mjs → parse-validator-Cabb60UV.mjs} +2 -1
- package/package.json +8 -7
|
@@ -1,6 +1,212 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { diagnosticAt } from './CodegenDiagnosticError-
|
|
3
|
-
import { a as parseObjectShape } from './parse-validator-
|
|
1
|
+
import { Node, SyntaxKind } from 'ts-morph';
|
|
2
|
+
import { diagnosticAt } from './CodegenDiagnosticError-DeblMkzO.mjs';
|
|
3
|
+
import { a as parseObjectShape } from './parse-validator-Cabb60UV.mjs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const renderLiteralValue = (value) => {
|
|
8
|
+
switch (typeof value) {
|
|
9
|
+
case "bigint": {
|
|
10
|
+
return value.toString();
|
|
11
|
+
}
|
|
12
|
+
case "boolean":
|
|
13
|
+
case "number": {
|
|
14
|
+
return String(value);
|
|
15
|
+
}
|
|
16
|
+
case "string": {
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
}
|
|
19
|
+
default: {
|
|
20
|
+
return value === null ? "null" : void 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const columnMetaToIR = (column) => {
|
|
25
|
+
const hasDefault = column?.["defaultValue"] !== void 0 || column?.["defaultFn"] !== void 0 || column?.["serverDefault"] !== void 0;
|
|
26
|
+
return {
|
|
27
|
+
...hasDefault ? { hasDefault: true } : {},
|
|
28
|
+
...column?.["onUpdateFn"] === void 0 ? {} : { hasOnUpdate: true },
|
|
29
|
+
notNull: column?.["notNull"] ?? true,
|
|
30
|
+
...column?.["unique"] === true ? { unique: true } : {}
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const runtimeValidatorToIR = (validator) => {
|
|
34
|
+
const meta = validator?._meta ?? {};
|
|
35
|
+
const kind = validator?.kind ?? "unknown";
|
|
36
|
+
const ir = { column: columnMetaToIR(meta["column"]), kind };
|
|
37
|
+
switch (kind) {
|
|
38
|
+
case "array":
|
|
39
|
+
case "optional": {
|
|
40
|
+
ir.inner = runtimeValidatorToIR(meta["inner"]);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "id": {
|
|
44
|
+
ir.tableName = meta["tableName"];
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case "literal": {
|
|
48
|
+
ir.literalValue = renderLiteralValue(meta["value"]);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "object": {
|
|
52
|
+
const shape = meta["shape"] ?? {};
|
|
53
|
+
ir.shape = Object.fromEntries(Object.entries(shape).map(([name, child]) => [name, runtimeValidatorToIR(child)]));
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "record": {
|
|
57
|
+
ir.valueType = runtimeValidatorToIR(meta["valueValidator"]);
|
|
58
|
+
if (meta["keyValidator"] !== void 0) {
|
|
59
|
+
ir.keyType = runtimeValidatorToIR(meta["keyValidator"]);
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case "storage": {
|
|
64
|
+
if (typeof meta["bucket"] === "string") {
|
|
65
|
+
ir.bucket = meta["bucket"];
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case "union": {
|
|
70
|
+
const members = meta["members"] ?? [];
|
|
71
|
+
ir.members = members.map((member) => runtimeValidatorToIR(member));
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return ir;
|
|
76
|
+
};
|
|
77
|
+
const shardModeToIR = (shardMode) => {
|
|
78
|
+
if (shardMode?.kind === "global") {
|
|
79
|
+
return "global";
|
|
80
|
+
}
|
|
81
|
+
if (shardMode?.kind === "shardBy" && typeof shardMode.field === "string") {
|
|
82
|
+
return { field: shardMode.field, kind: "shardBy" };
|
|
83
|
+
}
|
|
84
|
+
return "root";
|
|
85
|
+
};
|
|
86
|
+
const warnDroppedTableFeatures = (builder, bareName) => {
|
|
87
|
+
const dropped = [];
|
|
88
|
+
if (builder.relationMap && Object.keys(builder.relationMap).length > 0) {
|
|
89
|
+
dropped.push("relations");
|
|
90
|
+
}
|
|
91
|
+
if (builder.rankIndexes && builder.rankIndexes.length > 0) {
|
|
92
|
+
dropped.push("rank indexes");
|
|
93
|
+
}
|
|
94
|
+
if (builder.vectorIndexes && builder.vectorIndexes.length > 0) {
|
|
95
|
+
dropped.push("vector indexes");
|
|
96
|
+
}
|
|
97
|
+
if (dropped.length > 0) {
|
|
98
|
+
console.warn(
|
|
99
|
+
`@lunora/codegen: package schema-extension table "${bareName}" declares ${dropped.join(" + ")}, which are not yet introspected from a node_modules extension — they will be absent from the generated types.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const runtimeTableToIR = (builder, bareName) => {
|
|
104
|
+
const shape = builder.shape ?? {};
|
|
105
|
+
const indexes = (builder.indexes ?? []).map((index) => {
|
|
106
|
+
return { fields: index.fields, name: index.name, ...index.unique ? { unique: true } : {} };
|
|
107
|
+
});
|
|
108
|
+
warnDroppedTableFeatures(builder, bareName);
|
|
109
|
+
return {
|
|
110
|
+
...builder.isExternallyManaged ? { externallyManaged: true } : {},
|
|
111
|
+
indexes,
|
|
112
|
+
name: bareName,
|
|
113
|
+
rankIndexes: [],
|
|
114
|
+
relations: [],
|
|
115
|
+
searchIndexes: (builder.searchIndexes ?? []).map((index) => {
|
|
116
|
+
return {
|
|
117
|
+
field: index.field,
|
|
118
|
+
...index.filterFields ? { filterFields: index.filterFields } : {},
|
|
119
|
+
name: index.name
|
|
120
|
+
};
|
|
121
|
+
}),
|
|
122
|
+
shape: Object.fromEntries(Object.entries(shape).map(([name, validator]) => [name, runtimeValidatorToIR(validator)])),
|
|
123
|
+
shardMode: shardModeToIR(builder.shardMode),
|
|
124
|
+
// Inline vector/rank indexes and relations are not yet introspected from a
|
|
125
|
+
// package extension; their presence is surfaced via `warnDroppedTableFeatures`.
|
|
126
|
+
vectorIndexes: []
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
const resolvedExtensionToIR = (value) => {
|
|
130
|
+
const { key, tables } = value;
|
|
131
|
+
if (typeof key !== "string" || typeof tables !== "object" || tables === null) {
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
const bareTables = Object.entries(tables).map(([bareName, builder]) => runtimeTableToIR(builder, bareName));
|
|
135
|
+
const vectorIndexes = value["vectorIndexes"] ?? {};
|
|
136
|
+
const bareVectorIndexes = Object.entries(vectorIndexes ?? {}).map(([name, index]) => {
|
|
137
|
+
return {
|
|
138
|
+
...index.dimensions === void 0 ? {} : { dimensions: index.dimensions },
|
|
139
|
+
...index.field === void 0 ? {} : { field: index.field },
|
|
140
|
+
...index.metadata === void 0 ? {} : { metadata: index.metadata },
|
|
141
|
+
...index.metric === void 0 ? {} : { metric: index.metric },
|
|
142
|
+
name,
|
|
143
|
+
table: index.table ?? ""
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
return { bareTables, bareVectorIndexes, key };
|
|
147
|
+
};
|
|
148
|
+
const receiverIdentifierText = (argument) => {
|
|
149
|
+
if (Node.isIdentifier(argument)) {
|
|
150
|
+
return argument.getText();
|
|
151
|
+
}
|
|
152
|
+
if (Node.isPropertyAccessExpression(argument)) {
|
|
153
|
+
const receiver = argument.getExpression();
|
|
154
|
+
return Node.isIdentifier(receiver) ? receiver.getText() : void 0;
|
|
155
|
+
}
|
|
156
|
+
return void 0;
|
|
157
|
+
};
|
|
158
|
+
const accessPathOf = (argument) => {
|
|
159
|
+
const receiverName = receiverIdentifierText(argument);
|
|
160
|
+
if (receiverName === void 0) {
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
const property = Node.isPropertyAccessExpression(argument) ? argument.getName() : void 0;
|
|
164
|
+
const propertyPath = property === void 0 ? [] : [property];
|
|
165
|
+
for (const declaration of argument.getSourceFile().getImportDeclarations()) {
|
|
166
|
+
const moduleSpecifier = declaration.getModuleSpecifierValue();
|
|
167
|
+
for (const named of declaration.getNamedImports()) {
|
|
168
|
+
const localName = named.getAliasNode()?.getText() ?? named.getName();
|
|
169
|
+
if (localName === receiverName) {
|
|
170
|
+
return { importedName: named.getName(), moduleSpecifier, propertyPath };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (declaration.getDefaultImport()?.getText() === receiverName) {
|
|
174
|
+
return { importedName: "default", moduleSpecifier, propertyPath };
|
|
175
|
+
}
|
|
176
|
+
if (declaration.getNamespaceImport()?.getText() === receiverName && property !== void 0) {
|
|
177
|
+
return { importedName: property, moduleSpecifier, propertyPath: [] };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return void 0;
|
|
181
|
+
};
|
|
182
|
+
const readExtensionValue = (module_, access) => {
|
|
183
|
+
let current = module_[access.importedName];
|
|
184
|
+
for (const segment of access.propertyPath) {
|
|
185
|
+
if (typeof current !== "object" || current === null) {
|
|
186
|
+
return void 0;
|
|
187
|
+
}
|
|
188
|
+
current = current[segment];
|
|
189
|
+
}
|
|
190
|
+
return current;
|
|
191
|
+
};
|
|
192
|
+
const resolvePackageExtension = (argument, projectRoot) => {
|
|
193
|
+
const access = accessPathOf(argument);
|
|
194
|
+
if (!access) {
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const projectRequire = createRequire(join(projectRoot, "noop.cjs"));
|
|
199
|
+
const resolved = projectRequire.resolve(access.moduleSpecifier);
|
|
200
|
+
const loadedModule = projectRequire(resolved);
|
|
201
|
+
const value = readExtensionValue(loadedModule, access);
|
|
202
|
+
if (typeof value !== "object" || value === null) {
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
205
|
+
return resolvedExtensionToIR(value);
|
|
206
|
+
} catch {
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
4
210
|
|
|
5
211
|
const VECTOR_METRICS = /* @__PURE__ */ new Set(["cosine", "dot-product", "euclidean"]);
|
|
6
212
|
const ON_DELETE_ACTIONS = /* @__PURE__ */ new Set(["cascade", "restrict", "set null"]);
|
|
@@ -179,6 +385,54 @@ const parseGlobalBackend = (args) => {
|
|
|
179
385
|
}
|
|
180
386
|
return "d1";
|
|
181
387
|
};
|
|
388
|
+
const softDeleteFieldOf = (optionsArgument) => {
|
|
389
|
+
if (optionsArgument && Node.isObjectLiteralExpression(optionsArgument)) {
|
|
390
|
+
const fieldProperty = optionsArgument.getProperty("field");
|
|
391
|
+
if (fieldProperty && Node.isPropertyAssignment(fieldProperty)) {
|
|
392
|
+
const initializer = fieldProperty.getInitializer();
|
|
393
|
+
if (initializer && Node.isStringLiteral(initializer)) {
|
|
394
|
+
return initializer.getLiteralText();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return "deletedAt";
|
|
399
|
+
};
|
|
400
|
+
const stringPropertyOf = (object, property) => {
|
|
401
|
+
const node = object.getProperty(property);
|
|
402
|
+
if (node && Node.isPropertyAssignment(node)) {
|
|
403
|
+
const initializer = node.getInitializer();
|
|
404
|
+
if (initializer && Node.isStringLiteral(initializer)) {
|
|
405
|
+
return initializer.getLiteralText();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return void 0;
|
|
409
|
+
};
|
|
410
|
+
const stringArrayPropertyOf = (object, property) => {
|
|
411
|
+
const node = object.getProperty(property);
|
|
412
|
+
if (node && Node.isPropertyAssignment(node)) {
|
|
413
|
+
const initializer = node.getInitializer();
|
|
414
|
+
if (initializer && Node.isArrayLiteralExpression(initializer)) {
|
|
415
|
+
const items = initializer.getElements().filter((element) => Node.isStringLiteral(element)).map((element) => element.getLiteralText());
|
|
416
|
+
return items.length > 0 ? items : void 0;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return void 0;
|
|
420
|
+
};
|
|
421
|
+
const parseSourceCall = (args) => {
|
|
422
|
+
const first = args[0];
|
|
423
|
+
if (!first || !Node.isObjectLiteralExpression(first)) {
|
|
424
|
+
return { binding: "", hasTenantBy: false, unanalyzable: true };
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
binding: stringPropertyOf(first, "binding") ?? "",
|
|
428
|
+
columns: stringArrayPropertyOf(first, "columns"),
|
|
429
|
+
hasReconcile: first.getProperty("reconcileEveryMs") !== void 0,
|
|
430
|
+
hasTenantBy: first.getProperty("tenantBy") !== void 0,
|
|
431
|
+
idColumn: stringPropertyOf(first, "idColumn"),
|
|
432
|
+
mode: stringPropertyOf(first, "mode"),
|
|
433
|
+
query: stringPropertyOf(first, "query")
|
|
434
|
+
};
|
|
435
|
+
};
|
|
182
436
|
const applyTableMethod = (accumulator, method, args, name) => {
|
|
183
437
|
switch (method) {
|
|
184
438
|
case "externallyManaged": {
|
|
@@ -214,6 +468,15 @@ const applyTableMethod = (accumulator, method, args, name) => {
|
|
|
214
468
|
accumulator.shardMode = { field: field && Node.isStringLiteral(field) ? field.getLiteralText() : "_unknown_", kind: "shardBy" };
|
|
215
469
|
break;
|
|
216
470
|
}
|
|
471
|
+
case "softDelete": {
|
|
472
|
+
accumulator.softDelete = { field: softDeleteFieldOf(args[0]) };
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
case "source": {
|
|
476
|
+
accumulator.externalSource = parseSourceCall(args);
|
|
477
|
+
accumulator.externallyManaged = true;
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
217
480
|
case "vectorize": {
|
|
218
481
|
const vectorIndex = parseVectorizeCall(args, name);
|
|
219
482
|
if (vectorIndex) {
|
|
@@ -251,8 +514,12 @@ const parseTableBuilder = (expression, name) => {
|
|
|
251
514
|
break;
|
|
252
515
|
}
|
|
253
516
|
}
|
|
517
|
+
if (accumulator.softDelete && !(accumulator.softDelete.field in shape)) {
|
|
518
|
+
shape = { ...shape, [accumulator.softDelete.field]: { inner: { kind: "number" }, kind: "optional" } };
|
|
519
|
+
}
|
|
254
520
|
return {
|
|
255
521
|
externallyManaged: accumulator.externallyManaged,
|
|
522
|
+
externalSource: accumulator.externalSource,
|
|
256
523
|
globalBackend: accumulator.shardMode === "global" ? accumulator.globalBackend ?? "d1" : void 0,
|
|
257
524
|
indexes: accumulator.indexes,
|
|
258
525
|
name,
|
|
@@ -261,6 +528,7 @@ const parseTableBuilder = (expression, name) => {
|
|
|
261
528
|
searchIndexes: accumulator.searchIndexes,
|
|
262
529
|
shape,
|
|
263
530
|
shardMode: accumulator.shardMode,
|
|
531
|
+
softDelete: accumulator.softDelete,
|
|
264
532
|
vectorIndexes: accumulator.vectorIndexes
|
|
265
533
|
};
|
|
266
534
|
};
|
|
@@ -354,7 +622,27 @@ const extensionTargetIdentifier = (argument) => {
|
|
|
354
622
|
return argument;
|
|
355
623
|
}
|
|
356
624
|
if (Node.isPropertyAccessExpression(argument)) {
|
|
357
|
-
|
|
625
|
+
const receiver = argument.getExpression();
|
|
626
|
+
return Node.isIdentifier(receiver) ? receiver : void 0;
|
|
627
|
+
}
|
|
628
|
+
return void 0;
|
|
629
|
+
};
|
|
630
|
+
const pluginConfigObject = (expression) => {
|
|
631
|
+
if (Node.isObjectLiteralExpression(expression)) {
|
|
632
|
+
return expression;
|
|
633
|
+
}
|
|
634
|
+
if (Node.isCallExpression(expression)) {
|
|
635
|
+
const callee = expression.getExpression();
|
|
636
|
+
let name;
|
|
637
|
+
if (Node.isIdentifier(callee)) {
|
|
638
|
+
name = callee.getText();
|
|
639
|
+
} else if (Node.isPropertyAccessExpression(callee)) {
|
|
640
|
+
name = callee.getName();
|
|
641
|
+
}
|
|
642
|
+
if (name === "definePlugin") {
|
|
643
|
+
const configArgument = expression.getArguments()[1];
|
|
644
|
+
return configArgument && Node.isObjectLiteralExpression(configArgument) ? configArgument : void 0;
|
|
645
|
+
}
|
|
358
646
|
}
|
|
359
647
|
return void 0;
|
|
360
648
|
};
|
|
@@ -363,8 +651,9 @@ const nextExpressionFromDeclaration = (declaration, argument) => {
|
|
|
363
651
|
if (!initializer) {
|
|
364
652
|
return void 0;
|
|
365
653
|
}
|
|
366
|
-
if (Node.isPropertyAccessExpression(argument)
|
|
367
|
-
|
|
654
|
+
if (Node.isPropertyAccessExpression(argument)) {
|
|
655
|
+
const configObject = pluginConfigObject(initializer);
|
|
656
|
+
return configObject ? objectPropertyInitializer(configObject, argument.getName()) : void 0;
|
|
368
657
|
}
|
|
369
658
|
return initializer;
|
|
370
659
|
};
|
|
@@ -434,24 +723,30 @@ const parseExtensionVectorIndexes = (options) => {
|
|
|
434
723
|
}
|
|
435
724
|
return parseStandaloneVectorIndexes(object);
|
|
436
725
|
};
|
|
437
|
-
const
|
|
438
|
-
const bareTables = parseExtensionTables(options);
|
|
726
|
+
const namespaceExtension = (key, bareTables, bareVectorIndexes) => {
|
|
439
727
|
const bareNames = new Set(bareTables.map((table) => table.name));
|
|
440
728
|
const tables = bareTables.map((table) => namespaceExtensionTable(table, key, bareNames));
|
|
441
|
-
const vectorIndexes =
|
|
729
|
+
const vectorIndexes = bareVectorIndexes.map((index) => {
|
|
442
730
|
return { ...index, name: prefixTableName(key, index.name), table: rewriteReference(index.table, key, bareNames) };
|
|
443
731
|
});
|
|
444
732
|
return { tables, vectorIndexes };
|
|
445
733
|
};
|
|
446
|
-
const
|
|
734
|
+
const mergeExtension = (key, options) => namespaceExtension(key, parseExtensionTables(options), parseExtensionVectorIndexes(options));
|
|
735
|
+
const mergeExtendCall = (extendCall, projectRoot) => {
|
|
447
736
|
const extendArgument = extendCall.getArguments()[0];
|
|
448
737
|
if (!extendArgument) {
|
|
449
738
|
return void 0;
|
|
450
739
|
}
|
|
451
740
|
const resolved = resolveSchemaExtensionCall(extendArgument);
|
|
452
741
|
if (!resolved) {
|
|
742
|
+
if (projectRoot !== void 0) {
|
|
743
|
+
const fromPackage = resolvePackageExtension(extendArgument, projectRoot);
|
|
744
|
+
if (fromPackage) {
|
|
745
|
+
return namespaceExtension(fromPackage.key, fromPackage.bareTables, fromPackage.bareVectorIndexes);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
453
748
|
console.warn(
|
|
454
|
-
`@lunora/codegen: skipping \`.extend(${extendArgument.getText()})\` — its \`defineSchemaExtension(...)\`
|
|
749
|
+
`@lunora/codegen: skipping \`.extend(${extendArgument.getText()})\` — its \`defineSchemaExtension(...)\` could not be resolved from local sources, and ${projectRoot === void 0 ? "no project root was available to resolve the package" : "the package could not be imported/introspected"}. Extension tables will be absent from the generated types.`
|
|
455
750
|
);
|
|
456
751
|
return void 0;
|
|
457
752
|
}
|
|
@@ -481,6 +776,33 @@ const extendCallsOf = (defineSchemaCall) => {
|
|
|
481
776
|
}
|
|
482
777
|
return calls;
|
|
483
778
|
};
|
|
779
|
+
const JURISDICTIONS = /* @__PURE__ */ new Set(["eu", "fedramp", "us"]);
|
|
780
|
+
const jurisdictionOf = (defineSchemaCall) => {
|
|
781
|
+
let current = defineSchemaCall;
|
|
782
|
+
for (; ; ) {
|
|
783
|
+
const parent = current.getParent();
|
|
784
|
+
if (!parent || !Node.isPropertyAccessExpression(parent)) {
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
const callParent = parent.getParent();
|
|
788
|
+
if (!callParent || !Node.isCallExpression(callParent)) {
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
if (parent.getName() === "jurisdiction") {
|
|
792
|
+
const argument = callParent.getArguments()[0];
|
|
793
|
+
if (!argument || !Node.isStringLiteral(argument)) {
|
|
794
|
+
throw diagnosticAt(callParent, '`.jurisdiction(...)` expects a string literal ("eu", "us", or "fedramp")');
|
|
795
|
+
}
|
|
796
|
+
const value = argument.getLiteralText();
|
|
797
|
+
if (!JURISDICTIONS.has(value)) {
|
|
798
|
+
throw diagnosticAt(argument, `unknown jurisdiction ${JSON.stringify(value)} — expected "eu", "us", or "fedramp"`);
|
|
799
|
+
}
|
|
800
|
+
return value;
|
|
801
|
+
}
|
|
802
|
+
current = callParent;
|
|
803
|
+
}
|
|
804
|
+
return void 0;
|
|
805
|
+
};
|
|
484
806
|
const parseBaseTables = (object) => {
|
|
485
807
|
const tables = [];
|
|
486
808
|
for (const property of object.getProperties()) {
|
|
@@ -496,11 +818,11 @@ const parseBaseTables = (object) => {
|
|
|
496
818
|
}
|
|
497
819
|
return tables;
|
|
498
820
|
};
|
|
499
|
-
const applyExtensions = (defineSchemaCall, tables) => {
|
|
821
|
+
const applyExtensions = (defineSchemaCall, tables, projectRoot) => {
|
|
500
822
|
const existingTableNames = new Set(tables.map((table) => table.name));
|
|
501
823
|
const vectorIndexes = [];
|
|
502
824
|
for (const extendCall of extendCallsOf(defineSchemaCall)) {
|
|
503
|
-
const merged = mergeExtendCall(extendCall);
|
|
825
|
+
const merged = mergeExtendCall(extendCall, projectRoot);
|
|
504
826
|
if (!merged) {
|
|
505
827
|
continue;
|
|
506
828
|
}
|
|
@@ -518,7 +840,7 @@ const applyExtensions = (defineSchemaCall, tables) => {
|
|
|
518
840
|
}
|
|
519
841
|
return vectorIndexes;
|
|
520
842
|
};
|
|
521
|
-
const discoverSchema = (project, schemaPath) => {
|
|
843
|
+
const discoverSchema = (project, schemaPath, projectRoot) => {
|
|
522
844
|
const file = project.addSourceFileAtPath(schemaPath);
|
|
523
845
|
const defineSchemaCall = file.getDescendantsOfKind(SyntaxKind.CallExpression).find((call) => {
|
|
524
846
|
const callee = call.getExpression();
|
|
@@ -534,9 +856,9 @@ const discoverSchema = (project, schemaPath) => {
|
|
|
534
856
|
const tables = parseBaseTables(argument);
|
|
535
857
|
const standaloneArgument = defineSchemaCall.getArguments()[1];
|
|
536
858
|
const standaloneVectorIndexes = standaloneArgument && Node.isObjectLiteralExpression(standaloneArgument) ? parseStandaloneVectorIndexes(standaloneArgument) : [];
|
|
537
|
-
const extensionStandaloneVectorIndexes = applyExtensions(defineSchemaCall, tables);
|
|
859
|
+
const extensionStandaloneVectorIndexes = applyExtensions(defineSchemaCall, tables, projectRoot);
|
|
538
860
|
const vectorIndexes = [...tables.flatMap((table) => table.vectorIndexes), ...standaloneVectorIndexes, ...extensionStandaloneVectorIndexes];
|
|
539
|
-
return { tables, vectorIndexes };
|
|
861
|
+
return { jurisdiction: jurisdictionOf(defineSchemaCall), tables, vectorIndexes };
|
|
540
862
|
};
|
|
541
863
|
|
|
542
864
|
export { discoverSchema as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Node } from 'ts-morph';
|
|
2
|
-
import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall } from './discoverFunctions-
|
|
2
|
+
import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall } from './discoverFunctions-BWMczzBx.mjs';
|
|
3
3
|
|
|
4
4
|
const STORAGE_OPERATIONS = /* @__PURE__ */ new Set(["delete", "list", "read", "write"]);
|
|
5
5
|
const isStorageRulesCall = (node) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GENERATED_HEADER } from './
|
|
1
|
+
import { GENERATED_HEADER } from './GENERATED_HEADER-CI272wRm.mjs';
|
|
2
2
|
|
|
3
3
|
const LONG_TAIL = [
|
|
4
4
|
["hasAi", "ai", "ai", "Override the Workers AI binding backing `ctx.ai` (defaults to `env.AI`)."],
|
|
@@ -13,11 +13,34 @@ const LONG_TAIL = [
|
|
|
13
13
|
["hasImages", "images", "images", "Override the Images binding backing `ctx.images` (defaults to `env.IMAGES`)."],
|
|
14
14
|
["hasKv", "kv", "kv", "Override the Workers KV binding backing `ctx.kv` (defaults to `env.KV`)."],
|
|
15
15
|
["hasPayments", "payment", "payment", "Wire the payment options backing `ctx.payments`."],
|
|
16
|
+
[
|
|
17
|
+
"hasR2sql",
|
|
18
|
+
"r2sql",
|
|
19
|
+
"r2sql",
|
|
20
|
+
"Wire the R2 SQL client backing `ctx.r2sql` — build it with `createR2Sql({ accountId, apiToken, bucket })` (defaults to env `R2_SQL_TOKEN` / `R2_SQL_ACCOUNT_ID` / `R2_SQL_BUCKET`)."
|
|
21
|
+
],
|
|
16
22
|
["hasVectors", "vectors", "vectors", "Wire the Vectorize index map backing `ctx.vectors`."]
|
|
17
23
|
];
|
|
18
24
|
const hasAnyLongTail = (options) => LONG_TAIL.some(([flag]) => options[flag]);
|
|
25
|
+
const buildAccessImports = (hasAccess, hasAuth) => hasAccess ? [
|
|
26
|
+
`import type { CreateAccessResolverOptions } from "@lunora/cloudflare-access";`,
|
|
27
|
+
`import { createAccessResolver${hasAuth ? ", composeResolvers" : ""} } from "@lunora/cloudflare-access";`
|
|
28
|
+
] : [];
|
|
19
29
|
const buildImportLines = (options) => {
|
|
20
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
hasAccess,
|
|
32
|
+
hasAuth,
|
|
33
|
+
hasFramework,
|
|
34
|
+
hasGlobal,
|
|
35
|
+
hasHyperdriveGlobal,
|
|
36
|
+
hasQueue,
|
|
37
|
+
hasScheduler,
|
|
38
|
+
hasStorage,
|
|
39
|
+
hasWorkflow,
|
|
40
|
+
useUmbrella,
|
|
41
|
+
wantsOpenApi,
|
|
42
|
+
wantsOpenRpc
|
|
43
|
+
} = options;
|
|
21
44
|
const runtimeModule = useUmbrella ? "lunorash/runtime" : "@lunora/runtime";
|
|
22
45
|
const runtimeTypeImports = ["ExecutionContextLike", "LunoraWorker", "Route", "ScheduledControllerLike", "ShardNamespaceLike", "WorkerOptions"];
|
|
23
46
|
if (hasGlobal) {
|
|
@@ -36,6 +59,7 @@ const buildImportLines = (options) => {
|
|
|
36
59
|
`import type { LunoraAuth, LunoraAuthOptions } from "@lunora/auth";`,
|
|
37
60
|
`import { createAuth, createAuthAdmin, ensureMigrated, handleAuthRequest, lunoraD1Adapter } from "@lunora/auth";`
|
|
38
61
|
] : [],
|
|
62
|
+
...buildAccessImports(hasAccess, hasAuth),
|
|
39
63
|
...hasGlobal ? [
|
|
40
64
|
`import type { D1CtxDbOptions, D1DatabaseLike, D1Exec } from "@lunora/d1";`,
|
|
41
65
|
`import { createD1CtxDb, facetGlobalColumn, listGlobalTables, readGlobalTablePage } from "@lunora/d1";`
|
|
@@ -54,6 +78,7 @@ const buildImportLines = (options) => {
|
|
|
54
78
|
...hasGlobal || hasHyperdriveGlobal ? [`import schema from "../schema.js";`] : [],
|
|
55
79
|
`import { LUNORA_CRONS } from "./crons.js";`,
|
|
56
80
|
`import { LUNORA_FUNCTIONS } from "./functions.js";`,
|
|
81
|
+
...hasQueue ? [`import { dispatchQueueBatch } from "@lunora/queue";`, `import { LUNORA_QUEUE_REGISTRY } from "./queues.js";`] : [],
|
|
57
82
|
...wantsOpenApi ? [`import { openApiSpec } from "./openapi.js";`] : [],
|
|
58
83
|
...wantsOpenRpc ? [`import { openRpcSpec } from "./openrpc.js";`] : [],
|
|
59
84
|
`import { createShardDO } from "./shard.js";`
|
|
@@ -113,9 +138,10 @@ interface AuthDeclaration<Env> {
|
|
|
113
138
|
] : []
|
|
114
139
|
];
|
|
115
140
|
const buildFieldLines = (options) => [
|
|
141
|
+
...options.hasAccess ? [` private accessSelector?: Selector<Env, CreateAccessResolverOptions>;`] : [],
|
|
116
142
|
` private adminToken?: Selector<Env, string>;`,
|
|
117
143
|
...options.hasAuth ? [` private authDeclaration?: AuthDeclaration<Env>;`] : [],
|
|
118
|
-
` private readonly extendFns: ((env: Env) => Partial<WorkerOptions>)[] = [];`,
|
|
144
|
+
` private readonly extendFns: ((env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>)[] = [];`,
|
|
119
145
|
...options.hasGlobal ? [` private globalDeclaration?: GlobalDeclaration<Env>;`] : [],
|
|
120
146
|
...options.hasHyperdriveGlobal ? [` private hyperdriveGlobalDeclaration?: HyperdriveGlobalDeclaration<Env>;`] : [],
|
|
121
147
|
` private readonly routeMap: Record<string, Route> = {};`,
|
|
@@ -133,6 +159,14 @@ const buildLongTailMethods = (options) => LONG_TAIL.filter(([flag]) => options[f
|
|
|
133
159
|
}`
|
|
134
160
|
);
|
|
135
161
|
const buildMethodBlocks = (options) => [
|
|
162
|
+
...options.hasAccess ? [
|
|
163
|
+
` /** Wire Cloudflare Access (Zero Trust) — verifies the \`Cf-Access-Jwt-Assertion\` JWT and feeds the identity into \`ctx.auth\` / RLS via \`resolveIdentity\`. When \`.auth(...)\` is also configured, Access is composed ahead of it (Access wins when its JWT is present; everyone else falls through to the app session). */
|
|
164
|
+
public access(selector: Selector<Env, CreateAccessResolverOptions>): this {
|
|
165
|
+
this.accessSelector = selector;
|
|
166
|
+
|
|
167
|
+
return this;
|
|
168
|
+
}`
|
|
169
|
+
] : [],
|
|
136
170
|
` /** Bearer token gating the \`/_lunora/admin/*\` endpoints the studio calls. */
|
|
137
171
|
public admin(selector: Selector<Env, string>): this {
|
|
138
172
|
this.adminToken = selector;
|
|
@@ -147,8 +181,8 @@ const buildMethodBlocks = (options) => [
|
|
|
147
181
|
return this;
|
|
148
182
|
}`
|
|
149
183
|
] : [],
|
|
150
|
-
` /** Escape hatch — merge raw \`WorkerOptions\` (anything not yet sugared) over the derived options at build time. */
|
|
151
|
-
public extend(fn: (env: Env) => Partial<WorkerOptions>): this {
|
|
184
|
+
` /** Escape hatch — merge raw \`WorkerOptions\` (anything not yet sugared) over the derived options at build time. The second \`derived\` argument is a snapshot of the options assembled so far (after \`.auth(...)\` etc.), so you can compose rather than clobber — e.g. wrap \`derived.resolveIdentity\` instead of replacing it. */
|
|
185
|
+
public extend(fn: (env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>): this {
|
|
152
186
|
this.extendFns.push(fn);
|
|
153
187
|
|
|
154
188
|
return this;
|
|
@@ -268,7 +302,7 @@ const buildShardFactoryBody = (options) => {
|
|
|
268
302
|
const namespace = this.schedulerDeclaration?.namespace(env);
|
|
269
303
|
const origin = this.schedulerDeclaration?.origin?.(env);
|
|
270
304
|
|
|
271
|
-
return namespace && origin ? createScheduler({ namespace, originUrl: origin }) : undefined;
|
|
305
|
+
return namespace && origin ? createScheduler({${options.jurisdiction ? ` jurisdiction: ${JSON.stringify(options.jurisdiction)},` : ""} namespace, originUrl: origin }) : undefined;
|
|
272
306
|
},
|
|
273
307
|
}
|
|
274
308
|
: {}),`
|
|
@@ -337,13 +371,40 @@ const buildWorkerOptionLines = (options) => [
|
|
|
337
371
|
|
|
338
372
|
options.authAdmin = authInstance ? createAuthAdmin(authInstance) : undefined;
|
|
339
373
|
}`
|
|
374
|
+
] : [],
|
|
375
|
+
// Cloudflare Access — runs AFTER the auth block so it can compose ahead of
|
|
376
|
+
// the better-auth resolver rather than clobber it. With `.auth()` present,
|
|
377
|
+
// a request carrying a verified Access JWT is authenticated by Access and
|
|
378
|
+
// everyone else falls through to the app session; without it, Access is the
|
|
379
|
+
// sole resolver.
|
|
380
|
+
...options.hasAccess ? [
|
|
381
|
+
options.hasAuth ? ` if (this.accessSelector) {
|
|
382
|
+
const accessResolver = createAccessResolver(this.accessSelector(env));
|
|
383
|
+
const fallback = options.resolveIdentity;
|
|
384
|
+
|
|
385
|
+
options.resolveIdentity = fallback ? composeResolvers(accessResolver, fallback) : accessResolver;
|
|
386
|
+
}` : ` if (this.accessSelector) {
|
|
387
|
+
options.resolveIdentity = createAccessResolver(this.accessSelector(env));
|
|
388
|
+
}`
|
|
340
389
|
] : []
|
|
341
390
|
];
|
|
342
391
|
const buildBaseWorkerOptions = (options) => [
|
|
343
392
|
` cronJobs: LUNORA_CRONS,`,
|
|
344
393
|
` functions: LUNORA_FUNCTIONS,`,
|
|
394
|
+
// Schema `.jurisdiction("…")` pins every DO the worker reaches to the
|
|
395
|
+
// Cloudflare data-residency region. Emitted only when declared, so apps
|
|
396
|
+
// without it keep the un-pinned global namespace (and unchanged output).
|
|
397
|
+
...options.jurisdiction ? [` jurisdiction: ${JSON.stringify(options.jurisdiction)},`] : [],
|
|
345
398
|
...options.wantsOpenApi ? [` openApiSpec,`] : [],
|
|
346
399
|
...options.wantsOpenRpc ? [` openRpcSpec,`] : [],
|
|
400
|
+
// The push-consumer handler backing the worker's `queue(batch, …)` entry:
|
|
401
|
+
// routes each delivered batch to its `defineQueue` handler. Built from
|
|
402
|
+
// `@lunora/queue` here (keeping the runtime decoupled) and wired only when the
|
|
403
|
+
// app declares push queues in `lunora/queues.ts`.
|
|
404
|
+
...options.hasQueue ? [
|
|
405
|
+
` queue: (batch: unknown, queueEnv: unknown, _context: ExecutionContextLike): Promise<void> =>`,
|
|
406
|
+
` dispatchQueueBatch(batch as Parameters<typeof dispatchQueueBatch>[0], LUNORA_QUEUE_REGISTRY, { env: queueEnv as Record<string, unknown> }),`
|
|
407
|
+
] : [],
|
|
347
408
|
` routes: this.routeMap,`,
|
|
348
409
|
` shardDO: this.shardSelector?.(env) ?? (undefined as unknown as ShardNamespaceLike),`
|
|
349
410
|
];
|
|
@@ -552,7 +613,12 @@ ${buildWorkerLine}
|
|
|
552
613
|
worker ??= buildWorker(rawEnv as Env);
|
|
553
614
|
|
|
554
615
|
return worker.serverQuery(request, rawEnv, reference, args, options);
|
|
555
|
-
}
|
|
616
|
+
},${options.hasQueue ? `
|
|
617
|
+
queue: async (batch: unknown, rawEnv: unknown, context: ExecutionContextLike): Promise<void> => {
|
|
618
|
+
worker ??= buildWorker(rawEnv as Env);
|
|
619
|
+
|
|
620
|
+
return worker.queue?.(batch, rawEnv, context);
|
|
621
|
+
},` : ""}
|
|
556
622
|
};
|
|
557
623
|
|
|
558
624
|
if (this.emailHandler) {
|
|
@@ -575,7 +641,7 @@ ${buildBaseWorkerOptions(options).join("\n")}
|
|
|
575
641
|
}
|
|
576
642
|
|
|
577
643
|
${workerOptionLines.join("\n\n")}${workerOptionLines.length > 0 ? "\n\n" : ""} for (const fn of this.extendFns) {
|
|
578
|
-
Object.assign(options, fn(env));
|
|
644
|
+
Object.assign(options, fn(env, { ...options }));
|
|
579
645
|
}
|
|
580
646
|
|
|
581
647
|
return options;
|
|
@@ -19,6 +19,12 @@ const toAdvisorSchema = (schema) => {
|
|
|
19
19
|
tables: schema.tables.map((table) => {
|
|
20
20
|
return {
|
|
21
21
|
externallyManaged: table.externallyManaged ?? false,
|
|
22
|
+
externalSource: table.externalSource ? {
|
|
23
|
+
hasReconcile: table.externalSource.hasReconcile ?? false,
|
|
24
|
+
hasTenantBy: table.externalSource.hasTenantBy,
|
|
25
|
+
mode: table.externalSource.mode,
|
|
26
|
+
unanalyzable: table.externalSource.unanalyzable
|
|
27
|
+
} : void 0,
|
|
22
28
|
fields: Object.keys(table.shape),
|
|
23
29
|
indexes: flattenIndexes(table),
|
|
24
30
|
name: table.name,
|
|
@@ -31,12 +37,16 @@ const toAdvisorSchema = (schema) => {
|
|
|
31
37
|
references: relation.references,
|
|
32
38
|
table: relation.table
|
|
33
39
|
};
|
|
34
|
-
})
|
|
40
|
+
}),
|
|
41
|
+
shardKind: typeof table.shardMode === "string" ? table.shardMode : "shardBy"
|
|
35
42
|
};
|
|
36
43
|
})
|
|
37
44
|
};
|
|
38
45
|
};
|
|
39
|
-
const
|
|
46
|
+
const toAdvisorShapes = (shapes) => shapes.map((shape) => {
|
|
47
|
+
return { exportName: shape.exportName, file: `lunora/${shape.filePath}.ts`, table: shape.table };
|
|
48
|
+
});
|
|
49
|
+
const lintSchema = (schema, queries = [], inserts, authApiCalls, rlsProcedures, containers, workflows, workflowCalls, maskProcedures, nondeterministicCalls, procedureProtections, argumentValidators, secretLiterals, sqlInterpolations, adminRoutes, r2sqlCalls, shapes, mutatorWrites) => runAdvisor(
|
|
40
50
|
{
|
|
41
51
|
adminRoutes,
|
|
42
52
|
argValidators: argumentValidators,
|
|
@@ -44,12 +54,15 @@ const lintSchema = (schema, queries = [], inserts, authApiCalls, rlsProcedures,
|
|
|
44
54
|
containers,
|
|
45
55
|
inserts,
|
|
46
56
|
maskProcedures,
|
|
57
|
+
mutatorWrites,
|
|
47
58
|
nondeterministicCalls,
|
|
48
59
|
procedureProtections,
|
|
49
60
|
queries,
|
|
61
|
+
r2sqlCalls,
|
|
50
62
|
rlsProcedures,
|
|
51
63
|
schema: toAdvisorSchema(schema),
|
|
52
64
|
secretLiterals,
|
|
65
|
+
shapes: shapes === void 0 ? void 0 : toAdvisorShapes(shapes),
|
|
53
66
|
sqlInterpolations,
|
|
54
67
|
workflowCalls,
|
|
55
68
|
workflows
|
|
@@ -124,7 +124,8 @@ const parseValidatorCall = (call) => {
|
|
|
124
124
|
}
|
|
125
125
|
if (TRANSPARENT_MODIFIERS.has(member)) {
|
|
126
126
|
const receiver = callee.getExpression();
|
|
127
|
-
|
|
127
|
+
const base = Node.isExpression(receiver) ? parseValidator(receiver) : { kind: "any" };
|
|
128
|
+
return member === "check" ? { ...base, hasRefinement: true } : base;
|
|
128
129
|
}
|
|
129
130
|
return parseBuilderMember(member, args);
|
|
130
131
|
};
|