@lunora/codegen 1.0.0-alpha.55 → 1.0.0-alpha.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +59 -5
- package/dist/index.d.ts +59 -5
- package/dist/index.mjs +1 -38
- package/dist/packem_shared/AGENTS_FILENAME-BesaDYkL.mjs +1 -0
- package/dist/packem_shared/CONTAINERS_FILENAME-CFwdyaCI.mjs +1 -0
- package/dist/packem_shared/CodegenDiagnosticError-DPezpZTz.mjs +1 -0
- package/dist/packem_shared/FLAGS_FILENAME-DW5oGq1q.mjs +1 -0
- package/dist/packem_shared/GENERATED_HEADER-C94PuLpQ.mjs +1 -0
- package/dist/packem_shared/LUNORA_ERROR_CODES-Um9hC1gr.mjs +1 -0
- package/dist/packem_shared/MUTATORS_FILENAME-BKKPROzu.mjs +1 -0
- package/dist/packem_shared/NOTIFY_FILENAME-C-c7Lg-U.mjs +1 -0
- package/dist/packem_shared/OPENRPC_VERSION-DpV3v2W8.mjs +3 -0
- package/dist/packem_shared/QUEUES_FILENAME-CSE6i-ld.mjs +1 -0
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-DJsxljOQ.mjs +3 -0
- package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-Bpf1FGMG.mjs +15 -0
- package/dist/packem_shared/SHAPES_FILENAME-DTkSJSsg.mjs +1 -0
- package/dist/packem_shared/WORKFLOWS_FILENAME-Bk5upkGM.mjs +1 -0
- package/dist/packem_shared/buildOpenApiDocument-DXX1G3GZ.mjs +3 -0
- package/dist/packem_shared/discover-ast-CezKqEhE.mjs +1 -0
- package/dist/packem_shared/discoverAuthApiCalls-DOfSQqVK.mjs +1 -0
- package/dist/packem_shared/discoverCrons-B09_uEuL.mjs +1 -0
- package/dist/packem_shared/discoverFunctions-BdibLQmc.mjs +1 -0
- package/dist/packem_shared/discoverHttpRoutes-BOWz6y_n.mjs +1 -0
- package/dist/packem_shared/discoverInserts-BtteTyNy.mjs +1 -0
- package/dist/packem_shared/discoverMaskProcedures-COPmwMBm.mjs +1 -0
- package/dist/packem_shared/discoverMigrations-iGMlp9YJ.mjs +1 -0
- package/dist/packem_shared/discoverNondeterministicCalls-Dmgc4QBx.mjs +1 -0
- package/dist/packem_shared/discoverQueries-CcfuGKHh.mjs +1 -0
- package/dist/packem_shared/discoverR2sqlCalls-CjZKvg-N.mjs +1 -0
- package/dist/packem_shared/discoverRlsMetadata-BXmNOgry.mjs +1 -0
- package/dist/packem_shared/discoverSandboxUsage-CDdBqWcM.mjs +1 -0
- package/dist/packem_shared/discoverSchema-m2voTncu.mjs +1 -0
- package/dist/packem_shared/discoverStorageRulesMetadata-b71cJJHz.mjs +1 -0
- package/dist/packem_shared/emit-Dr6XSLfW.mjs +2104 -0
- package/dist/packem_shared/emitApp-B671iU7r.mjs +413 -0
- package/dist/packem_shared/formatAdvisories-DZvv-0MG.mjs +2 -0
- package/dist/packem_shared/parse-validator-CSDlMEvE.mjs +1 -0
- package/dist/packem_shared/paths-BmX5O1sG.mjs +1 -0
- package/dist/packem_shared/redact-CQ8-to6l.mjs +1 -0
- package/dist/packem_shared/schemaFromIr-R1ZFzVyy.mjs +1 -0
- package/package.json +3 -3
- package/dist/packem_shared/AGENTS_FILENAME-Cxx7wiSs.mjs +0 -158
- package/dist/packem_shared/CONTAINERS_FILENAME-DjpXMqhp.mjs +0 -224
- package/dist/packem_shared/CodegenDiagnosticError-DyQ5FwkM.mjs +0 -24
- package/dist/packem_shared/FLAGS_FILENAME-D5igzqwR.mjs +0 -139
- package/dist/packem_shared/GENERATED_HEADER-C8tAvxqP.mjs +0 -4
- package/dist/packem_shared/LUNORA_ERROR_CODES-DvTLozCu.mjs +0 -68
- package/dist/packem_shared/MUTATORS_FILENAME-BZOfUhlY.mjs +0 -81
- package/dist/packem_shared/NOTIFY_FILENAME-CK2IrsrP.mjs +0 -152
- package/dist/packem_shared/OPENRPC_VERSION-DvDChMhC.mjs +0 -60
- package/dist/packem_shared/QUEUES_FILENAME-Xv6Vy-Ku.mjs +0 -145
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-YAL6GrxI.mjs +0 -3597
- package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-DGD73E_n.mjs +0 -252
- package/dist/packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs +0 -94
- package/dist/packem_shared/WORKFLOWS_FILENAME-Dtuzvluc.mjs +0 -158
- package/dist/packem_shared/buildOpenApiDocument-DCQSLuFc.mjs +0 -262
- package/dist/packem_shared/discover-ast-CT6BgBr4.mjs +0 -13
- package/dist/packem_shared/discoverAuthApiCalls-Dhwgf4J4.mjs +0 -62
- package/dist/packem_shared/discoverCrons-UbQLLmMS.mjs +0 -286
- package/dist/packem_shared/discoverFunctions-COG_Xkyz.mjs +0 -544
- package/dist/packem_shared/discoverHttpRoutes-CVXWZkzo.mjs +0 -139
- package/dist/packem_shared/discoverInserts-DdWnXmLS.mjs +0 -61
- package/dist/packem_shared/discoverMaskProcedures-BG58_fw3.mjs +0 -273
- package/dist/packem_shared/discoverMigrations-Bw1U01zr.mjs +0 -97
- package/dist/packem_shared/discoverNondeterministicCalls-BbXEFZUT.mjs +0 -122
- package/dist/packem_shared/discoverQueries-CDmxu2ET.mjs +0 -62
- package/dist/packem_shared/discoverR2sqlCalls-Bn7QVv7J.mjs +0 -80
- package/dist/packem_shared/discoverRlsMetadata-D9uN35no.mjs +0 -280
- package/dist/packem_shared/discoverSandboxUsage-B5jDU6Bt.mjs +0 -38
- package/dist/packem_shared/discoverSchema-BJp5_T4o.mjs +0 -906
- package/dist/packem_shared/discoverStorageRulesMetadata-BqMhuEjo.mjs +0 -97
- package/dist/packem_shared/emit-DIbr7U31.mjs +0 -3884
- package/dist/packem_shared/emitApp-DbMM8iHe.mjs +0 -706
- package/dist/packem_shared/formatAdvisories-Cans5TWS.mjs +0 -131
- package/dist/packem_shared/parse-validator-BDGV8iue.mjs +0 -144
- package/dist/packem_shared/paths-BRd6JHuF.mjs +0 -11
- package/dist/packem_shared/redact-oTmsol5A.mjs +0 -33
- package/dist/packem_shared/schemaFromIr-DTYsLBaA.mjs +0 -57
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
|
|
3
|
-
const SCHEMA_SNAPSHOT_VERSION = 1;
|
|
4
|
-
const encodeShardMode = (mode) => {
|
|
5
|
-
if (mode === "global" || mode === "root") {
|
|
6
|
-
return mode;
|
|
7
|
-
}
|
|
8
|
-
return `shardBy:${mode.field}`;
|
|
9
|
-
};
|
|
10
|
-
const fieldSnapshotOf = (validator) => {
|
|
11
|
-
if (validator.kind === "optional") {
|
|
12
|
-
return { kind: validator.inner?.kind ?? "unknown", optional: true };
|
|
13
|
-
}
|
|
14
|
-
return { kind: validator.kind, optional: false };
|
|
15
|
-
};
|
|
16
|
-
const tableSnapshotOf = (table) => {
|
|
17
|
-
const fields = {};
|
|
18
|
-
for (const [name, validator] of Object.entries(table.shape)) {
|
|
19
|
-
fields[name] = fieldSnapshotOf(validator);
|
|
20
|
-
}
|
|
21
|
-
const indexes = {};
|
|
22
|
-
for (const index of table.indexes) {
|
|
23
|
-
indexes[index.name] = { fields: [...index.fields], unique: index.unique ?? false };
|
|
24
|
-
}
|
|
25
|
-
const relations = {};
|
|
26
|
-
for (const relation of table.relations) {
|
|
27
|
-
relations[relation.name] = { field: relation.field, kind: relation.kind, table: relation.table };
|
|
28
|
-
}
|
|
29
|
-
return { fields, indexes, relations, shardMode: encodeShardMode(table.shardMode) };
|
|
30
|
-
};
|
|
31
|
-
const sortKeys = (record) => {
|
|
32
|
-
const sorted = {};
|
|
33
|
-
const keys = Object.keys(record);
|
|
34
|
-
keys.sort();
|
|
35
|
-
for (const key of keys) {
|
|
36
|
-
sorted[key] = record[key];
|
|
37
|
-
}
|
|
38
|
-
return sorted;
|
|
39
|
-
};
|
|
40
|
-
const buildSchemaSnapshot = (schema, migrationIds) => {
|
|
41
|
-
const tables = {};
|
|
42
|
-
for (const table of schema.tables) {
|
|
43
|
-
tables[table.name] = tableSnapshotOf(table);
|
|
44
|
-
}
|
|
45
|
-
const sortedMigrationIds = [...migrationIds].sort();
|
|
46
|
-
return {
|
|
47
|
-
jurisdiction: schema.jurisdiction,
|
|
48
|
-
migrationIds: sortedMigrationIds,
|
|
49
|
-
tables: sortKeys(tables),
|
|
50
|
-
version: SCHEMA_SNAPSHOT_VERSION
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
const serializeSchemaSnapshot = (snapshot) => `${JSON.stringify(snapshot, void 0, 2)}
|
|
54
|
-
`;
|
|
55
|
-
class SchemaSnapshotParseError extends LunoraError {
|
|
56
|
-
constructor(message) {
|
|
57
|
-
super("SCHEMA_SNAPSHOT_PARSE", message, { name: "SchemaSnapshotParseError" });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
61
|
-
const isValidTableSnapshot = (value) => isRecord(value) && isRecord(value.fields) && isRecord(value.indexes) && isRecord(value.relations) && typeof value.shardMode === "string";
|
|
62
|
-
const parseSchemaSnapshot = (content) => {
|
|
63
|
-
if (content === void 0 || content.trim() === "") {
|
|
64
|
-
return void 0;
|
|
65
|
-
}
|
|
66
|
-
let parsed;
|
|
67
|
-
try {
|
|
68
|
-
parsed = JSON.parse(content);
|
|
69
|
-
} catch (error) {
|
|
70
|
-
throw new SchemaSnapshotParseError(`baseline is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
71
|
-
}
|
|
72
|
-
if (!isRecord(parsed) || parsed.version !== SCHEMA_SNAPSHOT_VERSION || !isRecord(parsed.tables)) {
|
|
73
|
-
throw new SchemaSnapshotParseError(`baseline is malformed or written by an incompatible version (expected version ${String(SCHEMA_SNAPSHOT_VERSION)})`);
|
|
74
|
-
}
|
|
75
|
-
for (const [name, table] of Object.entries(parsed.tables)) {
|
|
76
|
-
if (!isValidTableSnapshot(table)) {
|
|
77
|
-
throw new SchemaSnapshotParseError(`baseline table "${name}" has an invalid structure`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const jurisdiction = typeof parsed.jurisdiction === "string" ? parsed.jurisdiction : void 0;
|
|
81
|
-
return {
|
|
82
|
-
jurisdiction,
|
|
83
|
-
migrationIds: Array.isArray(parsed.migrationIds) ? parsed.migrationIds : [],
|
|
84
|
-
tables: parsed.tables,
|
|
85
|
-
version: SCHEMA_SNAPSHOT_VERSION
|
|
86
|
-
};
|
|
87
|
-
};
|
|
88
|
-
const indexesEqual = (a, b) => a.unique === b.unique && a.fields.length === b.fields.length && a.fields.every((field, index) => field === b.fields[index]);
|
|
89
|
-
const diffExistingField = (tableName, name, old, field) => {
|
|
90
|
-
const changes = [];
|
|
91
|
-
if (old.kind !== field.kind) {
|
|
92
|
-
changes.push({
|
|
93
|
-
severity: "breaking",
|
|
94
|
-
summary: `field ${tableName}.${name} changed type: ${old.kind} → ${field.kind} — add a data migration to convert existing values`,
|
|
95
|
-
type: "changedFieldKind"
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
if (old.optional && !field.optional) {
|
|
99
|
-
changes.push({
|
|
100
|
-
severity: "breaking",
|
|
101
|
-
summary: `field ${tableName}.${name} became required — rows missing it would be invalid; add a data migration to backfill it`,
|
|
102
|
-
type: "fieldOptionalToRequired"
|
|
103
|
-
});
|
|
104
|
-
} else if (!old.optional && field.optional) {
|
|
105
|
-
changes.push({ severity: "safe", summary: `field ${tableName}.${name} became optional`, type: "fieldRequiredToOptional" });
|
|
106
|
-
}
|
|
107
|
-
return changes;
|
|
108
|
-
};
|
|
109
|
-
const addedFieldChange = (tableName, name, field) => field.optional ? { severity: "safe", summary: `added optional field ${tableName}.${name}`, type: "addedOptionalField" } : {
|
|
110
|
-
severity: "breaking",
|
|
111
|
-
summary: `added required field ${tableName}.${name} — existing rows have no value; add a data migration to backfill it`,
|
|
112
|
-
type: "addedRequiredField"
|
|
113
|
-
};
|
|
114
|
-
const diffFields = (tableName, baseline, current, changes) => {
|
|
115
|
-
for (const [name, field] of Object.entries(current.fields)) {
|
|
116
|
-
const old = baseline.fields[name];
|
|
117
|
-
if (old === void 0) {
|
|
118
|
-
changes.push(addedFieldChange(tableName, name, field));
|
|
119
|
-
} else {
|
|
120
|
-
changes.push(...diffExistingField(tableName, name, old, field));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
for (const name of Object.keys(baseline.fields)) {
|
|
124
|
-
if (current.fields[name] === void 0) {
|
|
125
|
-
changes.push({
|
|
126
|
-
severity: "breaking",
|
|
127
|
-
summary: `removed field ${tableName}.${name} — add a data migration if stored data must be cleaned up`,
|
|
128
|
-
type: "removedField"
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
const diffIndexes = (tableName, baseline, current, changes) => {
|
|
134
|
-
for (const [name, index] of Object.entries(current.indexes)) {
|
|
135
|
-
const old = baseline.indexes[name];
|
|
136
|
-
if (old === void 0) {
|
|
137
|
-
changes.push({ severity: "safe", summary: `added index ${name} on ${tableName}`, type: "addedIndex" });
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
if (!indexesEqual(old, index)) {
|
|
141
|
-
changes.push({
|
|
142
|
-
severity: "breaking",
|
|
143
|
-
summary: `index ${name} on ${tableName} changed shape — a query may have relied on the old index`,
|
|
144
|
-
type: "changedIndex"
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
for (const name of Object.keys(baseline.indexes)) {
|
|
149
|
-
if (current.indexes[name] === void 0) {
|
|
150
|
-
changes.push({
|
|
151
|
-
severity: "breaking",
|
|
152
|
-
summary: `removed index ${name} on ${tableName} — a query that used \`.withIndex("${name}")\` would break`,
|
|
153
|
-
type: "removedIndex"
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
const diffRelations = (tableName, baseline, current, changes) => {
|
|
159
|
-
for (const name of Object.keys(current.relations)) {
|
|
160
|
-
if (baseline.relations[name] === void 0) {
|
|
161
|
-
changes.push({ severity: "safe", summary: `added relation ${tableName}.${name}`, type: "addedRelation" });
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
for (const name of Object.keys(baseline.relations)) {
|
|
165
|
-
if (current.relations[name] === void 0) {
|
|
166
|
-
changes.push({ severity: "breaking", summary: `removed relation ${tableName}.${name}`, type: "removedRelation" });
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
const diffExistingTable = (tableName, baseline, current, changes) => {
|
|
171
|
-
if (baseline.shardMode !== current.shardMode) {
|
|
172
|
-
changes.push({
|
|
173
|
-
severity: "breaking",
|
|
174
|
-
summary: `table ${tableName} changed shard mode: ${baseline.shardMode} → ${current.shardMode} — its physical storage moves; add a data migration / re-shard plan`,
|
|
175
|
-
type: "changedShardMode"
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
diffFields(tableName, baseline, current, changes);
|
|
179
|
-
diffIndexes(tableName, baseline, current, changes);
|
|
180
|
-
diffRelations(tableName, baseline, current, changes);
|
|
181
|
-
};
|
|
182
|
-
const diffSchemaSnapshots = (baseline, current) => {
|
|
183
|
-
const changes = [];
|
|
184
|
-
const baselineTables = baseline?.tables ?? {};
|
|
185
|
-
for (const [tableName, table] of Object.entries(current.tables)) {
|
|
186
|
-
const old = baselineTables[tableName];
|
|
187
|
-
if (old === void 0) {
|
|
188
|
-
changes.push({ severity: "safe", summary: `added table ${tableName}`, type: "addedTable" });
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
diffExistingTable(tableName, old, table, changes);
|
|
192
|
-
}
|
|
193
|
-
for (const tableName of Object.keys(baselineTables)) {
|
|
194
|
-
if (current.tables[tableName] === void 0) {
|
|
195
|
-
changes.push({
|
|
196
|
-
severity: "breaking",
|
|
197
|
-
summary: `removed table ${tableName} — add a data migration if its data must be archived/cleaned up`,
|
|
198
|
-
type: "removedTable"
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
if (baseline !== void 0 && baseline.jurisdiction !== current.jurisdiction) {
|
|
203
|
-
const from = baseline.jurisdiction ?? "(none)";
|
|
204
|
-
const to = current.jurisdiction ?? "(none)";
|
|
205
|
-
changes.push({
|
|
206
|
-
severity: "breaking",
|
|
207
|
-
summary: `Durable Object jurisdiction changed from ${from} to ${to} — this re-homes every DO and strands all existing shard, scheduler, and session-DO data in the old region (no in-place migration; export then import to move it). Revert the change, or override the gate to proceed intentionally.`,
|
|
208
|
-
type: "changedJurisdiction"
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
return { changes };
|
|
212
|
-
};
|
|
213
|
-
const evaluateSchemaDrift = (options) => {
|
|
214
|
-
const { allowDrift = false, baseline, current } = options;
|
|
215
|
-
const drift = diffSchemaSnapshots(baseline, current);
|
|
216
|
-
const breaking = drift.changes.filter((change) => change.severity === "breaking");
|
|
217
|
-
const newMigrationIds = current.migrationIds.filter((id) => !(baseline?.migrationIds ?? []).includes(id));
|
|
218
|
-
if (drift.changes.length === 0 || baseline === void 0) {
|
|
219
|
-
return { blocked: false, changes: drift.changes, newMigrationIds, reason: "" };
|
|
220
|
-
}
|
|
221
|
-
if (breaking.length === 0) {
|
|
222
|
-
const summary = drift.changes.map((change) => ` - ${change.summary}`).join("\n");
|
|
223
|
-
return { blocked: false, changes: drift.changes, newMigrationIds, reason: `schema drift detected (all additive/safe):
|
|
224
|
-
${summary}` };
|
|
225
|
-
}
|
|
226
|
-
const breakingSummary = breaking.map((change) => ` - ${change.summary}`).join("\n");
|
|
227
|
-
if (newMigrationIds.length > 0) {
|
|
228
|
-
return {
|
|
229
|
-
blocked: false,
|
|
230
|
-
changes: drift.changes,
|
|
231
|
-
newMigrationIds,
|
|
232
|
-
reason: `breaking schema drift detected, but ${String(newMigrationIds.length)} new migration(s) were added (${newMigrationIds.join(", ")}):
|
|
233
|
-
${breakingSummary}`
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
const reason = `deploy blocked: ${String(breaking.length)} breaking schema change(s) detected since the last blessed schema baseline — these changes are incompatible with existing data without a migration:
|
|
237
|
-
${breakingSummary}
|
|
238
|
-
|
|
239
|
-
To fix:
|
|
240
|
-
• For breaking changes: add a \`defineMigration({ id, table, up })\` in lunora/ for each affected table, then run \`lunora migrate\` (or \`lunora codegen\`) to apply and re-bless the baseline.
|
|
241
|
-
• For backward-compatible changes (e.g. adding an optional field): pass \`--allow-schema-drift\` to skip the block.
|
|
242
|
-
• To accept the new shape without a migration (you know data is compatible): pass \`--update-schema-baseline\`.
|
|
243
|
-
Docs: https://lunora.dev/docs/migrations`;
|
|
244
|
-
if (allowDrift) {
|
|
245
|
-
return { blocked: false, changes: drift.changes, newMigrationIds, reason: `${reason}
|
|
246
|
-
|
|
247
|
-
(overridden by --allow-schema-drift)` };
|
|
248
|
-
}
|
|
249
|
-
return { blocked: true, changes: drift.changes, newMigrationIds, reason };
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
export { SCHEMA_SNAPSHOT_VERSION, SchemaSnapshotParseError, buildSchemaSnapshot, diffSchemaSnapshots, evaluateSchemaDrift, parseSchemaSnapshot, serializeSchemaSnapshot };
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { SyntaxKind, Node } from 'ts-morph';
|
|
4
|
-
import { diagnosticAt } from './CodegenDiagnosticError-DyQ5FwkM.mjs';
|
|
5
|
-
|
|
6
|
-
const SHAPES_FILENAME = "shapes.ts";
|
|
7
|
-
const SHAPE_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["@lunora/server", "lunorash/server"]);
|
|
8
|
-
const isDefineShape = (identifier) => {
|
|
9
|
-
const symbol = identifier.getSymbol();
|
|
10
|
-
if (!symbol) {
|
|
11
|
-
return identifier.getText() === "defineShape";
|
|
12
|
-
}
|
|
13
|
-
for (const declaration of symbol.getDeclarations()) {
|
|
14
|
-
if (!Node.isImportSpecifier(declaration)) {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
if (!SHAPE_MODULE_SPECIFIERS.has(declaration.getImportDeclaration().getModuleSpecifierValue())) {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
return declaration.getNameNode().getText() === "defineShape";
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
23
|
-
};
|
|
24
|
-
const isShapeNamespaceImport = (identifier) => {
|
|
25
|
-
const symbol = identifier.getSymbol();
|
|
26
|
-
if (!symbol) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
for (const declaration of symbol.getDeclarations()) {
|
|
30
|
-
if (!Node.isNamespaceImport(declaration)) {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
const importDeclaration = declaration.getFirstAncestorByKind(SyntaxKind.ImportDeclaration);
|
|
34
|
-
return importDeclaration !== void 0 && SHAPE_MODULE_SPECIFIERS.has(importDeclaration.getModuleSpecifierValue());
|
|
35
|
-
}
|
|
36
|
-
return false;
|
|
37
|
-
};
|
|
38
|
-
const isDefineShapeCallee = (callee) => {
|
|
39
|
-
if (Node.isIdentifier(callee)) {
|
|
40
|
-
return isDefineShape(callee);
|
|
41
|
-
}
|
|
42
|
-
if (Node.isPropertyAccessExpression(callee)) {
|
|
43
|
-
const object = callee.getExpression();
|
|
44
|
-
return callee.getName() === "defineShape" && Node.isIdentifier(object) && isShapeNamespaceImport(object);
|
|
45
|
-
}
|
|
46
|
-
return false;
|
|
47
|
-
};
|
|
48
|
-
const tableLiteralFrom = (call) => {
|
|
49
|
-
const [config] = call.getArguments();
|
|
50
|
-
if (!config || !Node.isObjectLiteralExpression(config)) {
|
|
51
|
-
return void 0;
|
|
52
|
-
}
|
|
53
|
-
const tableProperty = config.getProperty("table");
|
|
54
|
-
if (!tableProperty || !Node.isPropertyAssignment(tableProperty)) {
|
|
55
|
-
return void 0;
|
|
56
|
-
}
|
|
57
|
-
const value = tableProperty.getInitializer();
|
|
58
|
-
return value && Node.isStringLiteral(value) ? value.getLiteralValue() : void 0;
|
|
59
|
-
};
|
|
60
|
-
const shapesFromSource = (source) => {
|
|
61
|
-
const shapes = [];
|
|
62
|
-
for (const declaration of source.getVariableDeclarations()) {
|
|
63
|
-
if (!declaration.isExported()) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
const initializer = declaration.getInitializer();
|
|
67
|
-
if (initializer?.getKind() !== SyntaxKind.CallExpression) {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
const callExpression = initializer;
|
|
71
|
-
const callee = callExpression.getExpression();
|
|
72
|
-
if (!isDefineShapeCallee(callee)) {
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const nameNode = declaration.getNameNode();
|
|
76
|
-
if (!Node.isIdentifier(nameNode)) {
|
|
77
|
-
throw diagnosticAt(nameNode, "defineShape exports must be plain named exports (no destructuring)");
|
|
78
|
-
}
|
|
79
|
-
shapes.push({ exportName: nameNode.getText(), filePath: "shapes", table: tableLiteralFrom(callExpression) });
|
|
80
|
-
}
|
|
81
|
-
return shapes;
|
|
82
|
-
};
|
|
83
|
-
const discoverShapes = (project, lunoraDirectory) => {
|
|
84
|
-
const shapesPath = join(lunoraDirectory, SHAPES_FILENAME);
|
|
85
|
-
if (!existsSync(shapesPath)) {
|
|
86
|
-
return [];
|
|
87
|
-
}
|
|
88
|
-
const source = project.getSourceFile(shapesPath) ?? project.addSourceFileAtPath(shapesPath);
|
|
89
|
-
const shapes = shapesFromSource(source);
|
|
90
|
-
shapes.sort((a, b) => a.exportName.localeCompare(b.exportName));
|
|
91
|
-
return shapes;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export { SHAPES_FILENAME, discoverShapes };
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { LunoraError } from '@lunora/errors';
|
|
4
|
-
import { workflowDefaultName, workflowClassName, workflowBindingName } from '@lunora/workflow';
|
|
5
|
-
import { Node, SyntaxKind } from 'ts-morph';
|
|
6
|
-
import { diagnosticAt } from './CodegenDiagnosticError-DyQ5FwkM.mjs';
|
|
7
|
-
|
|
8
|
-
const WORKFLOWS_FILENAME = "workflows.ts";
|
|
9
|
-
const STEP_METHODS = /* @__PURE__ */ new Set(["do", "sleep", "sleepUntil", "waitForEvent"]);
|
|
10
|
-
const isDefineWorkflow = (identifier) => {
|
|
11
|
-
const symbol = identifier.getSymbol();
|
|
12
|
-
if (!symbol) {
|
|
13
|
-
return identifier.getText() === "defineWorkflow";
|
|
14
|
-
}
|
|
15
|
-
for (const declaration of symbol.getDeclarations()) {
|
|
16
|
-
if (!Node.isImportSpecifier(declaration)) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
if (declaration.getImportDeclaration().getModuleSpecifierValue() !== "@lunora/workflow") {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
return declaration.getNameNode().getText() === "defineWorkflow";
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
};
|
|
26
|
-
const stringProperty = (expression, exportName, property) => {
|
|
27
|
-
if (Node.isStringLiteral(expression) || Node.isNoSubstitutionTemplateLiteral(expression)) {
|
|
28
|
-
return expression.getLiteralValue();
|
|
29
|
-
}
|
|
30
|
-
throw diagnosticAt(
|
|
31
|
-
expression,
|
|
32
|
-
`workflow "${exportName}": \`${property}\` must be a static string literal — it is deploy configuration codegen writes into wrangler.jsonc`
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
const isStepCall = (call) => {
|
|
36
|
-
const callee = call.getExpression();
|
|
37
|
-
if (!Node.isPropertyAccessExpression(callee) || !STEP_METHODS.has(callee.getName())) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
const receiver = callee.getExpression();
|
|
41
|
-
if (Node.isPropertyAccessExpression(receiver) && receiver.getName() === "step") {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
return Node.isIdentifier(receiver) && receiver.getText() === "step";
|
|
45
|
-
};
|
|
46
|
-
const stepsFromHandler = (argument) => {
|
|
47
|
-
const handlerProperty = argument.getProperty("handler");
|
|
48
|
-
if (!handlerProperty) {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
const body = Node.isPropertyAssignment(handlerProperty) ? handlerProperty.getInitializer() : handlerProperty;
|
|
52
|
-
if (!body) {
|
|
53
|
-
return [];
|
|
54
|
-
}
|
|
55
|
-
const steps = [];
|
|
56
|
-
for (const call of body.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
57
|
-
if (!isStepCall(call)) {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
const nameArgument = call.getArguments()[0];
|
|
61
|
-
if (!nameArgument || !(Node.isStringLiteral(nameArgument) || Node.isNoSubstitutionTemplateLiteral(nameArgument))) {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
steps.push({
|
|
65
|
-
line: call.getStartLineNumber(),
|
|
66
|
-
// `isStepCall` already proved the callee is a `.<method>` property access.
|
|
67
|
-
method: call.getExpression().getName(),
|
|
68
|
-
name: nameArgument.getLiteralValue()
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
return steps;
|
|
72
|
-
};
|
|
73
|
-
const workflowFromCall = (call, exportName) => {
|
|
74
|
-
const argument = call.getArguments()[0];
|
|
75
|
-
if (!argument || !Node.isObjectLiteralExpression(argument)) {
|
|
76
|
-
throw diagnosticAt(call, `workflow "${exportName}": defineWorkflow must be passed an inline object literal`);
|
|
77
|
-
}
|
|
78
|
-
const ir = {
|
|
79
|
-
bindingName: workflowBindingName(exportName),
|
|
80
|
-
className: workflowClassName(exportName),
|
|
81
|
-
exportName,
|
|
82
|
-
name: workflowDefaultName(exportName),
|
|
83
|
-
steps: stepsFromHandler(argument)
|
|
84
|
-
};
|
|
85
|
-
const nameProperty = argument.getProperty("name");
|
|
86
|
-
if (nameProperty && Node.isPropertyAssignment(nameProperty)) {
|
|
87
|
-
ir.name = stringProperty(nameProperty.getInitializerOrThrow(), exportName, "name");
|
|
88
|
-
}
|
|
89
|
-
return ir;
|
|
90
|
-
};
|
|
91
|
-
const unwrapToCallExpression = (node) => {
|
|
92
|
-
let current = node;
|
|
93
|
-
while (current && (Node.isAsExpression(current) || Node.isSatisfiesExpression(current) || Node.isParenthesizedExpression(current))) {
|
|
94
|
-
current = current.getExpression();
|
|
95
|
-
}
|
|
96
|
-
return current && Node.isCallExpression(current) ? current : void 0;
|
|
97
|
-
};
|
|
98
|
-
const workflowsFromSource = (source) => {
|
|
99
|
-
const workflows = [];
|
|
100
|
-
for (const declaration of source.getVariableDeclarations()) {
|
|
101
|
-
if (!declaration.isExported()) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
const call = unwrapToCallExpression(declaration.getInitializer());
|
|
105
|
-
if (!call) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
const callee = call.getExpression();
|
|
109
|
-
if (!Node.isIdentifier(callee) || !isDefineWorkflow(callee)) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
const nameNode = declaration.getNameNode();
|
|
113
|
-
if (!Node.isIdentifier(nameNode)) {
|
|
114
|
-
throw diagnosticAt(nameNode, "defineWorkflow exports must be plain named exports (no destructuring)");
|
|
115
|
-
}
|
|
116
|
-
workflows.push(workflowFromCall(call, nameNode.getText()));
|
|
117
|
-
}
|
|
118
|
-
return workflows;
|
|
119
|
-
};
|
|
120
|
-
const assertUniqueNames = (workflows) => {
|
|
121
|
-
const seenNames = /* @__PURE__ */ new Map();
|
|
122
|
-
const seenBindings = /* @__PURE__ */ new Map();
|
|
123
|
-
for (const workflow of workflows) {
|
|
124
|
-
const priorName = seenNames.get(workflow.name);
|
|
125
|
-
if (priorName !== void 0) {
|
|
126
|
-
throw new LunoraError(
|
|
127
|
-
// eslint-disable-next-line no-secrets/no-secrets -- an error code, not a secret
|
|
128
|
-
"DUPLICATE_WORKFLOW_NAME",
|
|
129
|
-
`Duplicate workflow name "${workflow.name}": produced by both "${priorName}" and "${workflow.exportName}". Deployed workflow names must be unique across the project.`,
|
|
130
|
-
{ status: 500 }
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
seenNames.set(workflow.name, workflow.exportName);
|
|
134
|
-
const priorBinding = seenBindings.get(workflow.bindingName);
|
|
135
|
-
if (priorBinding !== void 0) {
|
|
136
|
-
throw new LunoraError(
|
|
137
|
-
// eslint-disable-next-line no-secrets/no-secrets -- an error code, not a secret
|
|
138
|
-
"DUPLICATE_WORKFLOW_BINDING",
|
|
139
|
-
`Duplicate workflow binding "${workflow.bindingName}": produced by both "${priorBinding}" and "${workflow.exportName}". Workflow export names must yield unique binding names.`,
|
|
140
|
-
{ status: 500 }
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
seenBindings.set(workflow.bindingName, workflow.exportName);
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
const discoverWorkflows = (project, lunoraDirectory) => {
|
|
147
|
-
const workflowsPath = join(lunoraDirectory, WORKFLOWS_FILENAME);
|
|
148
|
-
if (!existsSync(workflowsPath)) {
|
|
149
|
-
return [];
|
|
150
|
-
}
|
|
151
|
-
const source = project.getSourceFile(workflowsPath) ?? project.addSourceFileAtPath(workflowsPath);
|
|
152
|
-
const workflows = workflowsFromSource(source);
|
|
153
|
-
workflows.sort((a, b) => a.exportName.localeCompare(b.exportName));
|
|
154
|
-
assertUniqueNames(workflows);
|
|
155
|
-
return workflows;
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
export { WORKFLOWS_FILENAME, discoverWorkflows };
|