@xanots/sdk 0.0.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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/dist/agent-file-refresh-7XCACU34.js +95 -0
  4. package/dist/bin.d.ts +1 -0
  5. package/dist/bin.js +184 -0
  6. package/dist/capture-HUV5BNTC.js +32 -0
  7. package/dist/chunk-26CNBZE2.js +963 -0
  8. package/dist/chunk-33ND6LKO.js +78 -0
  9. package/dist/chunk-3DUAGYGP.js +1550 -0
  10. package/dist/chunk-3IGNIP6R.js +89 -0
  11. package/dist/chunk-47WDWMBJ.js +14 -0
  12. package/dist/chunk-4BXJGVZ3.js +318 -0
  13. package/dist/chunk-4HT3BNZ7.js +675 -0
  14. package/dist/chunk-4YMD2OOZ.js +23 -0
  15. package/dist/chunk-5WGEURVI.js +156 -0
  16. package/dist/chunk-5YTBTF25.js +29 -0
  17. package/dist/chunk-6DHBYBTO.js +166 -0
  18. package/dist/chunk-76QBEIGO.js +66 -0
  19. package/dist/chunk-7DKX2SPN.js +100 -0
  20. package/dist/chunk-7REDODS2.js +468 -0
  21. package/dist/chunk-C56BC2FY.js +254 -0
  22. package/dist/chunk-DGSF2Q5H.js +60 -0
  23. package/dist/chunk-EMCAWMHC.js +3982 -0
  24. package/dist/chunk-EZG76F7R.js +159 -0
  25. package/dist/chunk-FJ6ZS5KB.js +3607 -0
  26. package/dist/chunk-GNPVYOPB.js +17 -0
  27. package/dist/chunk-HJPTWBLH.js +38 -0
  28. package/dist/chunk-IZW6MK3U.js +248 -0
  29. package/dist/chunk-LH6Z6KFV.js +247 -0
  30. package/dist/chunk-MZUI3JNX.js +93 -0
  31. package/dist/chunk-NDZFBZHC.js +167 -0
  32. package/dist/chunk-NOO35BQ2.js +37 -0
  33. package/dist/chunk-NUQCEOKA.js +13 -0
  34. package/dist/chunk-QTNO2WD6.js +56 -0
  35. package/dist/chunk-RVB7ITKN.js +49 -0
  36. package/dist/chunk-TRU5OHQ5.js +6904 -0
  37. package/dist/chunk-VKFQJEMW.js +814 -0
  38. package/dist/chunk-VTIL47DT.js +125 -0
  39. package/dist/chunk-WHOJWOSV.js +112 -0
  40. package/dist/chunk-WKP4D5MO.js +1692 -0
  41. package/dist/chunk-X4DVXBFY.js +38 -0
  42. package/dist/chunk-XQ22GLYS.js +118 -0
  43. package/dist/chunk-YDZYMZBE.js +670 -0
  44. package/dist/chunk-YGPIY3WT.js +1146 -0
  45. package/dist/chunk-YYRDLFSJ.js +602 -0
  46. package/dist/chunk-ZUTSMMAG.js +46 -0
  47. package/dist/cli.d.ts +331 -0
  48. package/dist/cli.js +33 -0
  49. package/dist/codegen-command-OOKK6AZK.js +40 -0
  50. package/dist/codegen.d.ts +114 -0
  51. package/dist/codegen.js +71 -0
  52. package/dist/completion-TIPPZN7H.js +298 -0
  53. package/dist/config-NL33PN4D.js +84 -0
  54. package/dist/deploy-command-4EQ7XX5G.js +451 -0
  55. package/dist/env-target-IFJ5QDHC.js +16 -0
  56. package/dist/ephemeral-command-3X3I4ULC.js +242 -0
  57. package/dist/index.d.ts +4477 -0
  58. package/dist/index.js +300 -0
  59. package/dist/init-command-R6H7ZXW5.js +27 -0
  60. package/dist/internal.d.ts +74 -0
  61. package/dist/internal.js +2065 -0
  62. package/dist/io-AMIKRLPC.js +11 -0
  63. package/dist/live-diff-Y3AKETHE.js +196 -0
  64. package/dist/lock-3CVKALKT.js +37 -0
  65. package/dist/lock-commands-XTR2YKLY.js +232 -0
  66. package/dist/login-command-ACJF6KWQ.js +167 -0
  67. package/dist/logout-command-MX3MJS5U.js +68 -0
  68. package/dist/loop-OM3P2Z6O.js +93 -0
  69. package/dist/marketplace-command-RATXTKXN.js +379 -0
  70. package/dist/meta-client-OW5WKWW7.js +170 -0
  71. package/dist/node.d.ts +59 -0
  72. package/dist/node.js +442 -0
  73. package/dist/profile-command-SWJ3SPKR.js +77 -0
  74. package/dist/release-command-VV5UVZKL.js +427 -0
  75. package/dist/response-CVAE2kMj.d.ts +1915 -0
  76. package/dist/routes-manifest-SP3ZXLMR.js +244 -0
  77. package/dist/runtime-V4C3AC3A.js +13 -0
  78. package/dist/sandbox-details-command-HJE5SPVG.js +18 -0
  79. package/dist/sandbox-export-command-QCJY4GMV.js +24 -0
  80. package/dist/static-host-D6KS7X45.js +138 -0
  81. package/dist/store-BQ97nKIF.d.ts +7760 -0
  82. package/dist/test-command-TSPBQHBM.js +383 -0
  83. package/dist/validate-command-4ERHJSCO.js +122 -0
  84. package/dist/verify-rollout-FAVQ6EJY.js +40 -0
  85. package/dist/workspace-K72NP7SX.js +49 -0
  86. package/dist/workspace-command-OF23DPNI.js +156 -0
  87. package/dist/workspace-export-AJMGN3CQ.js +10 -0
  88. package/llms/fields.md +104 -0
  89. package/llms/filters.md +183 -0
  90. package/llms/kinds-agent-mcp.md +11 -0
  91. package/llms/kinds-core.md +68 -0
  92. package/llms/kinds-realtime.md +76 -0
  93. package/llms/lambda.md +111 -0
  94. package/llms/legacy.md +43 -0
  95. package/llms/object-kinds.md +31 -0
  96. package/llms/statements-calls.md +23 -0
  97. package/llms/statements-catalog.md +309 -0
  98. package/llms/statements-data.md +58 -0
  99. package/llms/statements-runtime.md +48 -0
  100. package/llms/tests.md +58 -0
  101. package/llms/triggers.md +22 -0
  102. package/llms/values.md +29 -0
  103. package/llms-full.txt +1672 -0
  104. package/llms.txt +483 -0
  105. package/manifest.json +9592 -0
  106. package/package.json +110 -0
@@ -0,0 +1,1550 @@
1
+ import {
2
+ DECODE_ONLY_STATEMENTS,
3
+ DiagnosticBag,
4
+ FILTER_NAMES,
5
+ SCALAR_FAMILY,
6
+ assertMockKeysResolved,
7
+ buildBundle,
8
+ encodeObject,
9
+ getKind,
10
+ isQueryExpressionFilter,
11
+ isSeedFileSource,
12
+ middlewareEntryGuid,
13
+ stackReferencesAuth,
14
+ tableColumns
15
+ } from "./chunk-EMCAWMHC.js";
16
+ import {
17
+ CANONICAL_PAYLOAD_KEYS,
18
+ REFERENCEABLE_KINDS,
19
+ WORKSPACE_KEY,
20
+ deriveGuid,
21
+ guidSeedHint,
22
+ lockKey,
23
+ mintCanonical,
24
+ recordObserved,
25
+ resolveRef
26
+ } from "./chunk-4HT3BNZ7.js";
27
+
28
+ // src/workspace/seed-coerce.ts
29
+ function coerceScalarValue(label, column, type, value, values) {
30
+ if (value === null) return null;
31
+ switch (SCALAR_FAMILY[type]) {
32
+ case "int":
33
+ case "decimal": {
34
+ if (typeof value === "number" && Number.isFinite(value)) return value;
35
+ if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) {
36
+ return Number(value);
37
+ }
38
+ throw located(label, column, type, value, "a number");
39
+ }
40
+ case "epochms": {
41
+ if (typeof value === "number" && Number.isFinite(value)) return value;
42
+ if (value instanceof Date) return value.getTime();
43
+ if (typeof value === "string") {
44
+ const ms = Date.parse(value);
45
+ if (Number.isFinite(ms)) return ms;
46
+ }
47
+ throw located(label, column, type, value, "an epoch-ms number, a Date, or a date string");
48
+ }
49
+ case "bool": {
50
+ if (typeof value === "boolean") return value;
51
+ if (value === "true" || value === 1) return true;
52
+ if (value === "false" || value === 0) return false;
53
+ throw located(label, column, type, value, "a boolean");
54
+ }
55
+ case "text": {
56
+ if (typeof value === "string") return value;
57
+ throw located(label, column, type, value, "a string");
58
+ }
59
+ case "enum": {
60
+ if (!values || values.length === 0) return value;
61
+ if (values.includes(value)) return value;
62
+ const allowed = values.map((v) => JSON.stringify(v)).join(", ");
63
+ throw new Error(
64
+ `${label}, column "${column}" (enum): ${JSON.stringify(value)} is not one of its declared values (${allowed}). Fix the seed value or add it to the column's enum.`
65
+ );
66
+ }
67
+ default:
68
+ assertJsonCarryable(label, column, type, value);
69
+ return value;
70
+ }
71
+ }
72
+ function assertJsonCarryable(label, column, type, value) {
73
+ const seen = /* @__PURE__ */ new Set();
74
+ const walk = (node, path) => {
75
+ if (typeof node === "number") {
76
+ if (Number.isFinite(node)) return;
77
+ throw jsonRefusal(
78
+ label,
79
+ column,
80
+ type,
81
+ path,
82
+ `${node} is not a finite number`,
83
+ "JSON has no NaN or Infinity, so it would ship as `null` and the column would import empty"
84
+ );
85
+ }
86
+ if (typeof node === "bigint") {
87
+ throw jsonRefusal(
88
+ label,
89
+ column,
90
+ type,
91
+ path,
92
+ "a BigInt",
93
+ "JSON cannot carry one \u2014 convert it to a number or a string first"
94
+ );
95
+ }
96
+ if (typeof node === "function" || typeof node === "symbol") {
97
+ throw jsonRefusal(
98
+ label,
99
+ column,
100
+ type,
101
+ path,
102
+ `a ${typeof node}`,
103
+ "JSON drops it, so the key would vanish from the imported row"
104
+ );
105
+ }
106
+ if (node === null || typeof node !== "object") return;
107
+ if (seen.has(node)) {
108
+ throw jsonRefusal(
109
+ label,
110
+ column,
111
+ type,
112
+ path,
113
+ "a circular reference",
114
+ "JSON cannot carry one \u2014 the archive would fail to serialize"
115
+ );
116
+ }
117
+ seen.add(node);
118
+ if (Array.isArray(node)) {
119
+ node.forEach((el, i) => walk(el, `${path}[${i}]`));
120
+ } else {
121
+ for (const [key, member] of Object.entries(node)) walk(member, `${path}.${key}`);
122
+ }
123
+ seen.delete(node);
124
+ };
125
+ walk(value, `"${column}"`);
126
+ }
127
+ function jsonRefusal(label, column, type, path, got, why) {
128
+ return new Error(
129
+ `${label}, column "${column}" (${type}): ${path} is ${got}. ${why}. Seed rows are carried to the engine as JSON \u2014 fix the value or the column type.`
130
+ );
131
+ }
132
+ function coerceColumnValue(label, col, value) {
133
+ if (value === null) return null;
134
+ if (col.array) {
135
+ if (!Array.isArray(value)) throw located(label, col.name, `${col.type}[]`, value, "an array");
136
+ return value.map(
137
+ (el, j) => coerceScalarValue(label, `${col.name}[${j}]`, col.type, el, col.values)
138
+ );
139
+ }
140
+ return coerceScalarValue(label, col.name, col.type, value, col.values);
141
+ }
142
+ function located(label, column, type, value, expected) {
143
+ const got = value instanceof Date ? "Date" : Array.isArray(value) ? "array" : typeof value;
144
+ return new Error(
145
+ `${label}, column "${column}" (${type}): expected ${expected}, got ${got} (${JSON.stringify(value)}). Fix the seed value or the column type.`
146
+ );
147
+ }
148
+ function coerceSeedRowValues(tableName, columns, rows) {
149
+ const colByName = new Map(columns.map((c) => [c.name, c]));
150
+ return rows.map((row, i) => {
151
+ const label = `table "${tableName}", seed row ${i}`;
152
+ if (row === null || typeof row !== "object" || Array.isArray(row)) {
153
+ throw new Error(`${label}: expected a row object, got ${Array.isArray(row) ? "array" : typeof row}.`);
154
+ }
155
+ const out = {};
156
+ for (const [key, value] of Object.entries(row)) {
157
+ if (value === void 0) continue;
158
+ const col = colByName.get(key);
159
+ if (col === void 0) {
160
+ const known = columns.map((c) => c.name).join(", ");
161
+ throw new Error(
162
+ `${label}: unknown column "${key}" (not in table schema). Known columns: ${known}.`
163
+ );
164
+ }
165
+ out[key] = coerceColumnValue(label, col, value);
166
+ }
167
+ return out;
168
+ });
169
+ }
170
+ function assertSeedIds(tableName, columns, rows) {
171
+ const idType = columns.find((c) => c.name === "id")?.type;
172
+ if (idType !== "int" && idType !== "uuid") return false;
173
+ const withId = rows.filter((r) => r.id !== void 0).length;
174
+ if (withId === rows.length) return false;
175
+ if (withId !== 0) {
176
+ const fill = idType === "int" ? "auto-numbered 1..N" : "assigned a generated uuid";
177
+ throw new Error(
178
+ `table "${tableName}": ${withId} of ${rows.length} seed rows set \`id\` and the rest omit it. The engine preserves seed ids and won't auto-fill a missing one, so a mix collides on the primary key. Provide \`id\` for every seed row, or none (they'll be ${fill}).`
179
+ );
180
+ }
181
+ return true;
182
+ }
183
+
184
+ // src/workspace/guards.ts
185
+ var BULK_UPDATE = "mvp:dbo_bulkupdate";
186
+ var DB_GET = "mvp:dbo_getby";
187
+ var SYSTEM_COLUMNS = /* @__PURE__ */ new Set(["id", "created_at"]);
188
+ function walkNodes(root, visit) {
189
+ const stack = [root];
190
+ while (stack.length > 0) {
191
+ const node = stack.pop();
192
+ if (Array.isArray(node)) {
193
+ for (let i = node.length - 1; i >= 0; i--) stack.push(node[i]);
194
+ continue;
195
+ }
196
+ if (!node || typeof node !== "object") continue;
197
+ const record = node;
198
+ visit(record);
199
+ const values = Object.values(record);
200
+ for (let i = values.length - 1; i >= 0; i--) stack.push(values[i]);
201
+ }
202
+ }
203
+ function collectStatements(node, out) {
204
+ walkNodes(node, (record) => {
205
+ if (typeof record.name === "string" && record.name.startsWith("mvp:")) {
206
+ out.push(record);
207
+ }
208
+ });
209
+ }
210
+ function statementInput(statement, name) {
211
+ if (!Array.isArray(statement.input)) return void 0;
212
+ return statement.input.find(
213
+ (entry) => entry?.name === name
214
+ );
215
+ }
216
+ function statementTableGuid(statement) {
217
+ const id = statement.context?.dbo?.id;
218
+ return typeof id === "string" ? id : void 0;
219
+ }
220
+ function checkBulkUpdate(statement, owner, tablesByGuid, bag) {
221
+ const guid = statementTableGuid(statement);
222
+ const def = guid === void 0 ? void 0 : tablesByGuid.get(guid);
223
+ if (!def) return;
224
+ const items = statementInput(statement, "items");
225
+ if (typeof items?.tag !== "string" || !items.tag.startsWith("const")) return;
226
+ if (typeof items.value !== "string") return;
227
+ let parsed;
228
+ try {
229
+ parsed = JSON.parse(items.value);
230
+ } catch {
231
+ return;
232
+ }
233
+ if (!Array.isArray(parsed) || parsed.length === 0) return;
234
+ const supplied = /* @__PURE__ */ new Set();
235
+ for (const item of parsed) {
236
+ if (!item || typeof item !== "object" || Array.isArray(item)) return;
237
+ for (const key of Object.keys(item)) supplied.add(key);
238
+ }
239
+ if (supplied.size === 0) return;
240
+ const cleared = tableColumns(def).map((col) => col.name).filter((name) => !SYSTEM_COLUMNS.has(name) && !supplied.has(name));
241
+ if (cleared.length === 0) return;
242
+ bag.warn(
243
+ "db.bulk-update-partial-item",
244
+ `${owner}: \`s.db.bulk.update\` on table "${def.name}" is a full-row REPLACE, and these items omit ${cleared.map((n) => `"${n}"`).join(", ")} \u2014 each omitted column is written to its zero value ("" / 0 / null), not left alone, with an HTTP 200 and no error. Use \`s.db.bulk.patch\` to write only the keys an item carries, or supply every column you mean to preserve. (Only a static \`items\` array is checked; a \`ref\` cannot be.)`
245
+ );
246
+ }
247
+ function outputColumns(statement) {
248
+ const output = statement.output;
249
+ if (!output || output.customize !== true || !Array.isArray(output.items)) return void 0;
250
+ const names = /* @__PURE__ */ new Set();
251
+ for (const item of output.items) {
252
+ const name = item?.name;
253
+ if (typeof name === "string") names.add(name);
254
+ }
255
+ return names;
256
+ }
257
+ function collectVarRefs(node, out) {
258
+ walkNodes(node, (record) => {
259
+ if (record.tag !== "var") return;
260
+ if (typeof record.value === "string") out.push(record.value);
261
+ if (typeof record.operand === "string") out.push(record.operand);
262
+ });
263
+ }
264
+ function checkInternalColumnReads(statements, owner, tablesByGuid, bag) {
265
+ const bindings = /* @__PURE__ */ new Map();
266
+ for (const statement of statements) {
267
+ if (statement.name !== DB_GET) continue;
268
+ const as = statement.as;
269
+ if (typeof as !== "string" || as === "") continue;
270
+ const guid = statementTableGuid(statement);
271
+ const def = guid === void 0 ? void 0 : tablesByGuid.get(guid);
272
+ if (!def) continue;
273
+ bindings.set(as, { def, output: outputColumns(statement) });
274
+ }
275
+ if (bindings.size === 0) return;
276
+ const refs = [];
277
+ for (const statement of statements) collectVarRefs(statement, refs);
278
+ const reported = /* @__PURE__ */ new Set();
279
+ for (const path of refs) {
280
+ const dot = path.indexOf(".");
281
+ if (dot <= 0) continue;
282
+ const root = path.slice(0, dot);
283
+ const column = path.slice(dot + 1);
284
+ const binding = bindings.get(root);
285
+ if (!binding || binding.output?.has(column)) continue;
286
+ const col = tableColumns(binding.def).find((c) => c.name === column);
287
+ if (!col || col.access !== "internal") continue;
288
+ if (reported.has(path)) continue;
289
+ reported.add(path);
290
+ bag.warn(
291
+ "db.internal-column-read",
292
+ `${owner}: reads \`ref("${path}")\`, but "${column}" on table "${binding.def.name}" is \`access: "internal"\` and a \`db.get\` does not return it \u2014 the row has no "${column}" key, so this fails at runtime with \`Unable to locate var: ${path}\`. Name the column in \`output\` on that \`db.get\` (\`output\` overrides column visibility), e.g. \`output: ["id", "${column}"]\`.`
293
+ );
294
+ }
295
+ }
296
+ function collectInputRefs(node, out) {
297
+ walkNodes(node, (record) => {
298
+ if (record.tag !== "input") return;
299
+ if (typeof record.value === "string") out.push(record.value);
300
+ if (typeof record.operand === "string") out.push(record.operand);
301
+ });
302
+ }
303
+ function declaredInputNames(record) {
304
+ const names = /* @__PURE__ */ new Set();
305
+ if (!Array.isArray(record.input)) return names;
306
+ for (const entry of record.input) {
307
+ const name = entry?.name;
308
+ if (typeof name === "string" && name !== "") names.add(name);
309
+ }
310
+ return names;
311
+ }
312
+ function inputBase(reference) {
313
+ const cut = reference.search(/[.[]/);
314
+ return cut === -1 ? reference : reference.slice(0, cut);
315
+ }
316
+ function boundChannelInputs(payloadKey, record, channelInputs) {
317
+ if (payloadKey === "message") {
318
+ const guid = record.channel?.id;
319
+ return (typeof guid === "string" ? channelInputs.get(guid) : void 0) ?? /* @__PURE__ */ new Set();
320
+ }
321
+ if (payloadKey === "trigger") {
322
+ const objType = record.obj_type;
323
+ if (objType !== "channel" && objType !== "workspace_realtime_channel") return /* @__PURE__ */ new Set();
324
+ const objId = record.obj_id;
325
+ return (typeof objId === "string" ? channelInputs.get(objId) : void 0) ?? /* @__PURE__ */ new Set();
326
+ }
327
+ return /* @__PURE__ */ new Set();
328
+ }
329
+ function checkInputScope(sections, bag) {
330
+ const channelInputs = /* @__PURE__ */ new Map();
331
+ for (const channel of sections.channel ?? []) {
332
+ if (!channel || typeof channel !== "object") continue;
333
+ const record = channel;
334
+ if (typeof record.guid === "string") {
335
+ channelInputs.set(record.guid, declaredInputNames(record));
336
+ }
337
+ }
338
+ for (const [payloadKey, arr] of Object.entries(sections)) {
339
+ if (payloadKey === "middleware") continue;
340
+ for (const obj of arr ?? []) {
341
+ if (!obj || typeof obj !== "object") continue;
342
+ const record = obj;
343
+ if (!Array.isArray(record.run)) continue;
344
+ const declared = declaredInputNames(record);
345
+ for (const name2 of boundChannelInputs(payloadKey, record, channelInputs)) {
346
+ declared.add(name2);
347
+ }
348
+ const references = [];
349
+ collectInputRefs(record.run, references);
350
+ collectInputRefs(record.result, references);
351
+ const missing = [...new Set(references.map(inputBase))].filter(
352
+ // A bare `inp("")` is the whole input object, not a named one.
353
+ (name2) => name2 !== "" && !declared.has(name2)
354
+ );
355
+ if (missing.length === 0) continue;
356
+ const name = typeof record.name === "string" ? record.name : "?";
357
+ const declaredList = declared.size === 0 ? "it declares no inputs at all" : `it declares ${[...declared].map((n) => `\`${n}\``).join(", ")}`;
358
+ bag.warn(
359
+ "stack.inp-undeclared",
360
+ `${payloadKey} "${name}" reads ${missing.map((r) => `\`inp("${r}")\``).join(", ")}, which ${missing.length === 1 ? "is not a declared input" : "are not declared inputs"} here \u2014 ${declaredList}. This deploys clean and fails at runtime with \`Unable to locate input: ${missing[0]}\`. If the value comes from earlier in the stack rather than the request, read it with \`ref("<var>.${missing[0]}")\`; if it really is a request input, declare it in this def's \`input\`.`
361
+ );
362
+ }
363
+ }
364
+ }
365
+ function checkMiddleware(sections, bag) {
366
+ for (const mw of sections.middleware ?? []) {
367
+ if (!mw || typeof mw !== "object") continue;
368
+ const record = mw;
369
+ const name = typeof record.name === "string" ? record.name : "?";
370
+ if (Array.isArray(record.input) && record.input.length > 0) {
371
+ bag.warn(
372
+ "middleware.input-never-bound",
373
+ `middleware "${name}" declares \`input\`, which the host request never binds \u2014 \`inp()\` inside a middleware fails at runtime with \`Unable to locate input\`, and a declared default does not stand in. Read the request body with \`s.util.get_all_input\` instead; it yields a \`{ type, vars }\` envelope. The field is kept because the engine stores it and existing workspaces carry one.`
374
+ );
375
+ }
376
+ const refs = [];
377
+ collectInputRefs(record.run, refs);
378
+ const unique = [...new Set(refs)];
379
+ if (unique.length > 0) {
380
+ bag.warn(
381
+ "middleware.inp-unresolvable",
382
+ `middleware "${name}" reads ${unique.map((r) => `\`inp("${r}")\``).join(", ")}, which cannot resolve \u2014 a middleware has no bound inputs, so this fails at runtime with \`Unable to locate input: ${unique[0]}\`. Use \`s.util.get_all_input\` and read the \`{ type, vars }\` envelope it binds.`
383
+ );
384
+ }
385
+ }
386
+ }
387
+ function checkApiGroupCors(sections, bag) {
388
+ for (const app of sections.app ?? []) {
389
+ if (!app || typeof app !== "object") continue;
390
+ const record = app;
391
+ const cors = record.cors;
392
+ if (!cors || cors.mode !== "custom") continue;
393
+ const name = typeof record.name === "string" ? record.name : "?";
394
+ const origins = Array.isArray(cors.allowOrigins) ? cors.allowOrigins : [];
395
+ if (origins.length === 0) {
396
+ bag.warn(
397
+ "api-group.cors-no-origins",
398
+ `apiGroup "${name}": \`cors.mode: "custom"\` with an empty \`allowOrigins\` matches no request, so responses carry NO \`access-control-*\` headers and every browser call fails on a missing \`access-control-allow-origin\`. List the origins (\`allowOrigins: ["https://app.example.com"]\`), or drop to \`mode: "default"\` for the permissive policy.`
399
+ );
400
+ } else if (origins.includes("*")) {
401
+ bag.warn(
402
+ "api-group.cors-wildcard-origin",
403
+ `apiGroup "${name}": \`allowOrigins\` contains \`"*"\`, which is compared as a LITERAL origin under \`mode: "custom"\` \u2014 no browser sends \`Origin: *\`, so it matches nothing and those responses carry no \`access-control-*\` headers at all. Name each origin, or use \`mode: "default"\`, which is the any-origin policy.`
404
+ );
405
+ }
406
+ const methods = cors.allowMethods ?? {};
407
+ if (!Object.values(methods).some((v) => v === true)) {
408
+ bag.warn(
409
+ "api-group.cors-no-methods",
410
+ `apiGroup "${name}": \`cors.mode: "custom"\` enables no \`allowMethods\`, so no \`access-control-allow-methods\` is sent and a preflight has nothing to approve. Enable every verb the group's queries use \u2014 the map also gates the REAL response, so a request whose method is off gets no CORS headers even after a passing preflight.`
411
+ );
412
+ }
413
+ }
414
+ }
415
+ function checkStacks(tables, sections, bag) {
416
+ checkMiddleware(sections, bag);
417
+ checkInputScope(sections, bag);
418
+ checkApiGroupCors(sections, bag);
419
+ if (tables.length === 0) return;
420
+ const tablesByGuid = /* @__PURE__ */ new Map();
421
+ for (const def of tables) tablesByGuid.set(resolveRef("dbo", def), def);
422
+ for (const [payloadKey, arr] of Object.entries(sections)) {
423
+ for (const obj of arr ?? []) {
424
+ if (!obj || typeof obj !== "object") continue;
425
+ const name = obj.name;
426
+ const owner = `${payloadKey} "${typeof name === "string" ? name : "?"}"`;
427
+ const statements = [];
428
+ collectStatements(obj, statements);
429
+ if (statements.length === 0) continue;
430
+ for (const statement of statements) {
431
+ if (statement.name === BULK_UPDATE) {
432
+ checkBulkUpdate(statement, owner, tablesByGuid, bag);
433
+ }
434
+ }
435
+ checkInternalColumnReads(statements, owner, tablesByGuid, bag);
436
+ }
437
+ }
438
+ }
439
+ var EMPTY_OPERANDS = /* @__PURE__ */ new Set(["", "[]", "{}"]);
440
+ var MEMBERSHIP_OPS = /* @__PURE__ */ new Set(["in"]);
441
+ function checkIgnoreEmptyOperand(node, owner, bag) {
442
+ const right = node.right;
443
+ if (!right || right.ignore_empty !== true) return;
444
+ if (typeof right.tag !== "string" || !right.tag.startsWith("const")) return;
445
+ if (typeof right.operand !== "string" || !EMPTY_OPERANDS.has(right.operand)) return;
446
+ const op = typeof node.op === "string" ? node.op : "?";
447
+ const left = node.left?.operand;
448
+ const subject = typeof left === "string" && left !== "" ? `\`${left}\` ` : "";
449
+ const consequence = MEMBERSHIP_OPS.has(op) ? `this clause selects EVERY row instead of none, the opposite of what \`${op} []\` reads as. On a permission filter that is the whole table handed to a caller entitled to none of it.` : `this clause is dropped from the query and constrains nothing.`;
450
+ bag.warn(
451
+ "expression.ignore-empty-static-empty",
452
+ `${owner}: comparison ${subject}\`${op}\` sets \`ignoreEmpty\` on an operand that is EMPTY in the bundle. \`ignoreEmpty\` REMOVES the predicate when its operand resolves empty rather than matching zero rows, so ${consequence} Drop \`ignoreEmpty\` to keep the empty-list reading (\`in []\` matches nothing), or pass the list you meant.`
453
+ );
454
+ }
455
+ function checkExpressionOperands(sections, bag) {
456
+ for (const [payloadKey, arr] of Object.entries(sections)) {
457
+ for (const obj of arr ?? []) {
458
+ if (!obj || typeof obj !== "object") continue;
459
+ const name = obj.name;
460
+ const owner = `${payloadKey} "${typeof name === "string" ? name : "?"}"`;
461
+ walkNodes(obj, (record) => {
462
+ const statement = record.statement;
463
+ if (statement && typeof statement === "object" && "op" in statement) {
464
+ checkIgnoreEmptyOperand(statement, owner, bag);
465
+ }
466
+ });
467
+ }
468
+ }
469
+ }
470
+ function checkSeed(tables, bag) {
471
+ for (const def of tables) {
472
+ const source = def.seed;
473
+ if (source === void 0 || typeof source === "function" || isSeedFileSource(source)) continue;
474
+ if (!Array.isArray(source) || source.length === 0) continue;
475
+ try {
476
+ const coerced = coerceSeedRowValues(def.name, tableColumns(def), source);
477
+ assertSeedIds(def.name, tableColumns(def), coerced);
478
+ } catch (cause) {
479
+ bag.error("seed.invalid", cause.message);
480
+ }
481
+ }
482
+ }
483
+ var GATING_ACTIONS = {
484
+ realtime_server: ["connect"],
485
+ channel: ["join"]
486
+ };
487
+ function checkRealtimeGates(sections, bag) {
488
+ for (const obj of sections.trigger ?? []) {
489
+ if (!obj || typeof obj !== "object") continue;
490
+ const t = obj;
491
+ if (!Array.isArray(t.result) || t.result.length > 0) continue;
492
+ const objType = typeof t.obj_type === "string" ? t.obj_type : "";
493
+ const gating = GATING_ACTIONS[objType];
494
+ if (!gating) continue;
495
+ const actions = t.meta?.[objType]?.action;
496
+ const declared = gating.filter((a) => actions?.[a] === true);
497
+ if (declared.length === 0) continue;
498
+ const name = typeof t.name === "string" ? t.name : "?";
499
+ bag.warn(
500
+ "realtime.gate-denies-everyone",
501
+ `trigger "${name}" gates \`${declared.join("`/`")}\` but declares no \`response\`, so it returns nothing on every run \u2014 and an empty return is a DENY, which refuses every client. A crash denies too, so there is no shape of this trigger that admits anyone. Add \`response: () => obj({ allowed: c.bool(true) })\` (any truthy value admits; an \`allowed: false\` with a \`reason\` reaches the client). If refusing everyone is the intent, say so explicitly with \`response: () => c.bool(false)\`.`
502
+ );
503
+ }
504
+ }
505
+ function checkRealtimeDeliver(bundleType, sections, bag) {
506
+ if (bundleType !== "workspace") return;
507
+ const channels = /* @__PURE__ */ new Map();
508
+ for (const obj of sections.channel ?? []) {
509
+ if (!obj || typeof obj !== "object") continue;
510
+ const ch = obj;
511
+ if (typeof ch.guid !== "string" || ch.guid === "") continue;
512
+ channels.set(ch.guid, {
513
+ name: typeof ch.name === "string" ? ch.name : "?",
514
+ perRecipient: ch.delivery?.per_recipient === true
515
+ });
516
+ }
517
+ const delivered = /* @__PURE__ */ new Set();
518
+ for (const obj of sections.trigger ?? []) {
519
+ if (!obj || typeof obj !== "object") continue;
520
+ const t = obj;
521
+ if (t.obj_type !== "channel" || t.active === false) continue;
522
+ const actions = t.meta?.channel?.action;
523
+ if (actions?.deliver !== true) continue;
524
+ if (typeof t.obj_id !== "string" || t.obj_id === "") continue;
525
+ const channel = channels.get(t.obj_id);
526
+ if (!channel) continue;
527
+ delivered.add(t.obj_id);
528
+ if (channel.perRecipient) continue;
529
+ const name = typeof t.name === "string" ? t.name : "?";
530
+ bag.warn(
531
+ "realtime.deliver-without-per-recipient",
532
+ `trigger "${name}" gates \`deliver\` on channel "${channel.name}", but that channel does not set \`delivery: { perRecipient: true }\` \u2014 the engine runs the per-recipient path only when BOTH halves are present, so this trigger never runs and every subscriber receives the UNREDACTED payload. Nothing errors and nothing is logged. Add \`delivery: { perRecipient: true }\` to the channel, noting that it costs one stack per recipient per message. If the redaction is no longer wanted, remove the trigger rather than leaving it as a gate that does not gate.`
533
+ );
534
+ }
535
+ for (const [guid, channel] of channels) {
536
+ if (!channel.perRecipient || delivered.has(guid)) continue;
537
+ bag.warn(
538
+ "realtime.per-recipient-without-deliver",
539
+ `channel "${channel.name}" sets \`delivery: { perRecipient: true }\` but no active \`deliver\` trigger is bound to it, so the flag is a no-op \u2014 the engine falls back to a plain broadcast rather than dropping anyone. Remove it, or add a \`realtimeChannelTrigger({ channel, actions: { deliver: true } })\` to redact per recipient.`
540
+ );
541
+ }
542
+ }
543
+ var IMPLICIT_VAR_BASES = /* @__PURE__ */ new Set(["input"]);
544
+ function collectBindings(statements) {
545
+ const bound = /* @__PURE__ */ new Set();
546
+ for (const statement of statements) {
547
+ if (typeof statement.as === "string" && statement.as !== "") bound.add(statement.as);
548
+ const context = statement.context;
549
+ if (typeof context?.as === "string" && context.as !== "") bound.add(context.as);
550
+ if (typeof context?.name === "string" && context.name !== "") bound.add(context.name);
551
+ }
552
+ return bound;
553
+ }
554
+ function editDistance(a, b) {
555
+ let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
556
+ for (let i = 1; i <= a.length; i++) {
557
+ const current = [i];
558
+ for (let j = 1; j <= b.length; j++) {
559
+ current[j] = Math.min(
560
+ previous[j] + 1,
561
+ current[j - 1] + 1,
562
+ previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
563
+ );
564
+ }
565
+ previous = current;
566
+ }
567
+ return previous[b.length];
568
+ }
569
+ function didYouMean(name, bound) {
570
+ let best;
571
+ let bestDistance = Infinity;
572
+ for (const candidate of bound) {
573
+ const distance = editDistance(name, candidate);
574
+ if (distance < bestDistance) {
575
+ best = candidate;
576
+ bestDistance = distance;
577
+ }
578
+ }
579
+ return best !== void 0 && bestDistance <= Math.max(1, Math.round(name.length / 3)) ? best : void 0;
580
+ }
581
+ function checkUnboundVarRefs(sections, bag) {
582
+ for (const [payloadKey, arr] of Object.entries(sections)) {
583
+ for (const obj of arr ?? []) {
584
+ if (!obj || typeof obj !== "object") continue;
585
+ const name = obj.name;
586
+ const owner = `${payloadKey} "${typeof name === "string" ? name : "?"}"`;
587
+ const statements = [];
588
+ collectStatements(obj, statements);
589
+ if (statements.length === 0) continue;
590
+ const bound = collectBindings(statements);
591
+ const refs = [];
592
+ collectVarRefs(obj, refs);
593
+ const reported = /* @__PURE__ */ new Set();
594
+ for (const path of refs) {
595
+ const dot = path.indexOf(".");
596
+ const base = dot === -1 ? path : path.slice(0, dot);
597
+ if (base === "" || base.startsWith("$") || IMPLICIT_VAR_BASES.has(base)) continue;
598
+ if (bound.has(base)) continue;
599
+ if (reported.has(base)) continue;
600
+ reported.add(base);
601
+ const suggestion = didYouMean(base, bound);
602
+ bag.warn(
603
+ "stack.unbound-var",
604
+ `${owner}: \`ref("${path}")\` names a variable that nothing in this stack binds with \`as\`. At runtime this raises \`Unable to locate var: ${base}\` \u2014 an HTTP 500 on the deployed environment.${suggestion ? ` Did you mean "${suggestion}"?` : ""} Bind it first (\`as: "${base}"\` on the statement that produces it). Do NOT reach for \`ref(\u2026, { safe: true })\` here: that is for drilling into a value that may be null, and against a name that binds nothing it just turns the 500 into a silent \`null\`.`
605
+ );
606
+ }
607
+ }
608
+ }
609
+ }
610
+ var DB_MATCH_ARG = {
611
+ "mvp:dbo_getby": "field_value",
612
+ "mvp:dbo_delby": "field_value",
613
+ "mvp:dbo_hasby": "field_value",
614
+ "mvp:dbo_editby": "field_value",
615
+ "mvp:dbo_patch": "field_value",
616
+ "mvp:dbo_addoreditby": "field_value",
617
+ "mvp:dbo_get": "id"
618
+ };
619
+ function isNullSafeDrill(cell) {
620
+ const value = cell;
621
+ if (value?.tag !== "var" || !Array.isArray(value.filters)) return false;
622
+ return value.filters.some((entry) => {
623
+ const filter = entry;
624
+ if (filter?.name !== "get") return false;
625
+ const fallback = Array.isArray(filter.arg) ? filter.arg[1] : void 0;
626
+ if (fallback === void 0) return true;
627
+ return fallback.tag === "const:null";
628
+ });
629
+ }
630
+ function checkSafeRefMatchArgs(sections, bag) {
631
+ for (const [payloadKey, arr] of Object.entries(sections)) {
632
+ for (const obj of arr ?? []) {
633
+ if (!obj || typeof obj !== "object") continue;
634
+ const name = obj.name;
635
+ const owner = `${payloadKey} "${typeof name === "string" ? name : "?"}"`;
636
+ const statements = [];
637
+ collectStatements(obj, statements);
638
+ for (const statement of statements) {
639
+ const matchArg = DB_MATCH_ARG[statement.name];
640
+ if (matchArg === void 0 || !Array.isArray(statement.input)) continue;
641
+ for (const entry of statement.input) {
642
+ const cell = entry;
643
+ if (cell?.name !== matchArg) continue;
644
+ if (!isNullSafeDrill(cell)) continue;
645
+ const base = typeof cell.value === "string" ? cell.value : "the base";
646
+ const arg = matchArg === "id" ? "`id`" : "`fieldValue`";
647
+ bag.warn(
648
+ "db.safe-ref-match-arg",
649
+ `${owner}: a \`ref(\u2026, { safe: true })\` is the ${arg} of \`${statement.name}\`. That opt-in exists to produce \`null\` when \`${base}\` is null, and \`null\` is never a legal match value \u2014 the engine rejects the request with HTTP 400 \`Missing param: ${matchArg}\` before any later statement runs, so a following precondition never gets to answer and the caller sees an internal-sounding 400 instead of your 404. \`{ safe: true }\` is for an EXPRESSION operand (a precondition/condition, an \`obj()\` member), not a db match argument. Guard existence first \u2014 \`s.precondition({ expr: expr(ref("${base}"), "!=", c.null()), error_type: "notfound", \u2026 })\` \u2014 then drill WITHOUT \`safe\`, since the base is known non-null by then.`
650
+ );
651
+ }
652
+ }
653
+ }
654
+ }
655
+ }
656
+ function checkNullMatchArgs(sections, bag) {
657
+ for (const [payloadKey, arr] of Object.entries(sections)) {
658
+ for (const obj of arr ?? []) {
659
+ if (!obj || typeof obj !== "object") continue;
660
+ const name = obj.name;
661
+ const owner = `${payloadKey} "${typeof name === "string" ? name : "?"}"`;
662
+ const statements = [];
663
+ collectStatements(obj, statements);
664
+ for (const statement of statements) {
665
+ const matchArg = DB_MATCH_ARG[statement.name];
666
+ if (matchArg === void 0 || !Array.isArray(statement.input)) continue;
667
+ for (const entry of statement.input) {
668
+ const cell = entry;
669
+ if (cell?.name !== matchArg) continue;
670
+ if (cell.tag !== "const:null") continue;
671
+ if (Array.isArray(cell.filters) && cell.filters.length > 0) continue;
672
+ const arg = matchArg === "id" ? "`id`" : "`fieldValue`";
673
+ bag.warn(
674
+ "db.null-match-arg",
675
+ `${owner}: \`c.null()\` is the ${arg} of \`${statement.name}\`. \`null\` is never a legal match value \u2014 the engine rejects the request with HTTP 400 \`Missing param: ${matchArg}\` on the FIRST call, and the caller sees an input error naming an argument they never sent. \`null\` belongs in an EXPRESSION operand (a precondition/condition, an \`obj()\` member), not in a lookup's match slot. Modelling an optional link? Store the sentinel instead \u2014 \`f.tableRef(users, { required: true, default: 0 })\` \u2014 and match on \`c.int(0)\`, which finds no row and binds \`null\`, the answer the null was after.`
676
+ );
677
+ }
678
+ }
679
+ }
680
+ }
681
+ }
682
+ function checkSameNameSiblings(sections, bag) {
683
+ for (const [payloadKey, arr] of Object.entries(sections)) {
684
+ const guidsByName = /* @__PURE__ */ new Map();
685
+ for (const obj of arr ?? []) {
686
+ if (!obj || typeof obj !== "object") continue;
687
+ const { name, guid } = obj;
688
+ if (typeof name !== "string" || name === "" || typeof guid !== "string") continue;
689
+ const guids = guidsByName.get(name) ?? /* @__PURE__ */ new Set();
690
+ guids.add(guid);
691
+ guidsByName.set(name, guids);
692
+ }
693
+ for (const [name, guids] of guidsByName) {
694
+ if (guids.size < 2) continue;
695
+ const advice = payloadKey === "query" ? `Give them distinct names (\`${name}\` and \`${name}/create\` for a GET/POST pair \u2014 the verb still carries the semantics). An explicit \`guid\` clears the export-time collision but not this one.` : `${payloadKey} identity is composed from more than the name here, but the lock key is not \u2014 name them uniquely across the workspace.`;
696
+ bag.warn(
697
+ "lock.same-name-siblings",
698
+ `${guids.size} ${payloadKey} objects share the name "${name}" while carrying different guids. This bundle exports, but a lock entry is keyed \`${payloadKey}:${name}\` and holds ONE guid, so \`xanots export --lock\` REFUSES the pair \u2014 and a committed \`xano.lock\` is the recommended workflow. ${advice} Fixing it now costs a rename; at the first locked export it costs the lock.`
699
+ );
700
+ }
701
+ }
702
+ }
703
+ var REALTIME_PUBLISH = "mvp:realtime_publish";
704
+ function constantCell(cell) {
705
+ const c = cell;
706
+ if (typeof c?.tag !== "string" || !c.tag.startsWith("const")) return void 0;
707
+ if (Array.isArray(c.filters) && c.filters.length > 0) return void 0;
708
+ return typeof c.value === "string" && c.value !== "" ? c.value : void 0;
709
+ }
710
+ function channelPathMatches(template, path) {
711
+ const pattern = template.split(/\{[^{}]*\}/).map((literal) => literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]+");
712
+ return new RegExp(`^${pattern}$`).test(path);
713
+ }
714
+ function checkRealtimePublish(bundleType, sections, bag) {
715
+ if (bundleType !== "workspace") return;
716
+ const objName = (obj) => {
717
+ const name = obj?.name;
718
+ return typeof name === "string" ? name : void 0;
719
+ };
720
+ const servers = (sections.realtime_server ?? []).map(objName).filter((n) => n !== void 0);
721
+ const channels = (sections.channel ?? []).map(objName).filter((n) => n !== void 0);
722
+ for (const [payloadKey, arr] of Object.entries(sections)) {
723
+ for (const obj of arr ?? []) {
724
+ if (!obj || typeof obj !== "object") continue;
725
+ const owner = `${payloadKey} "${objName(obj) ?? "?"}"`;
726
+ const statements = [];
727
+ collectStatements(obj, statements);
728
+ for (const statement of statements) {
729
+ if (statement.name !== REALTIME_PUBLISH) continue;
730
+ const context = statement.context ?? {};
731
+ const server = constantCell(context.realtime_server);
732
+ if (server !== void 0 && !servers.includes(server)) {
733
+ bag.warn(
734
+ "realtime.publish-unknown-server",
735
+ `${owner}: \`s.realtime.publish\` targets realtime server "${server}", which this workspace does not register. The engine resolves the server by NAME and FAILS SOFT \u2014 a miss is swallowed with nothing returned to the stack \u2014 so this publish goes nowhere and reports nothing. Pass the \`realtimeServer()\` handle rather than a bare name so a rename cannot break it.${servers.length > 0 ? ` Registered: ${servers.map((n) => `"${n}"`).join(", ")}.` : ""}`
736
+ );
737
+ }
738
+ const channel = constantCell(context.channel);
739
+ if (channel !== void 0 && !channels.some((path) => channelPathMatches(path, channel))) {
740
+ bag.warn(
741
+ "realtime.publish-unknown-channel",
742
+ `${owner}: \`s.realtime.publish\` targets channel path "${channel}", which matches no \`realtimeChannel()\` on this workspace, so no client can be subscribed to it \u2014 and the publish is swallowed silently rather than failing. Build the path with \`channel.getChannel({ \u2026 })\` instead of writing it out.${channels.length > 0 ? ` Registered: ${channels.map((n) => `"${n}"`).join(", ")}.` : ""}`
743
+ );
744
+ }
745
+ }
746
+ }
747
+ }
748
+ }
749
+ var TRIGGER_BINDINGS = {
750
+ database: "table",
751
+ realtime_server: "realtimeServer",
752
+ channel: "channel",
753
+ toolset: "mcpServer` / `agent"
754
+ };
755
+ function checkTriggerBindings(sections, bag) {
756
+ for (const obj of sections.trigger ?? []) {
757
+ if (!obj || typeof obj !== "object") continue;
758
+ const t = obj;
759
+ const objType = typeof t.obj_type === "string" ? t.obj_type : "";
760
+ const arg = TRIGGER_BINDINGS[objType];
761
+ if (!arg) continue;
762
+ if (t.obj_id !== 0 && t.obj_id !== "" && t.obj_id !== void 0 && t.obj_id !== null) continue;
763
+ const name = typeof t.name === "string" ? t.name : "?";
764
+ bag.warn(
765
+ "trigger.unbound",
766
+ `trigger "${name}" is an \`${objType}\` trigger but names no object to fire on, so it deploys bound to nothing and never runs. Bind it with \`${arg}\` \u2014 pass the def handle (or its name) and it resolves to the target's guid at export, surviving a \`--reset\` deploy. A raw numeric \`objId\` is the escape hatch, and is rarely what you want: ids are assigned at import and are not knowable from a def.`
767
+ );
768
+ }
769
+ }
770
+ function checkDecodeOnlyStatements(sections, bag) {
771
+ for (const [payloadKey, arr] of Object.entries(sections)) {
772
+ for (const obj of arr ?? []) {
773
+ if (!obj || typeof obj !== "object") continue;
774
+ const objName = obj.name;
775
+ const owner = `${payloadKey} "${typeof objName === "string" ? objName : "?"}"`;
776
+ const statements = [];
777
+ collectStatements(obj, statements);
778
+ const found = new Set(
779
+ statements.map((s) => s.name).filter((name) => DECODE_ONLY_STATEMENTS.has(name))
780
+ );
781
+ for (const name of found) {
782
+ bag.error(
783
+ "statement.decode-only",
784
+ `${owner} contains \`${name}\`, which is ${DECODE_ONLY_STATEMENTS.get(name)}. It reached this bundle from a pulled workspace \u2014 XanoTS has no factory for it \u2014 and the generated source carries it as a \`raw({ name: "${name}", \u2026 })\` call. Replace that call with the statement it stands in for.`
785
+ );
786
+ }
787
+ }
788
+ }
789
+ }
790
+ var GUID_RE = /^[0-9a-f]{32}$/;
791
+ var VALUE_KEYS = /* @__PURE__ */ new Set([
792
+ "input",
793
+ "value",
794
+ // The same tagged value under the name a COMPARISON gives it
795
+ // (`statement.left.operand` / `statement.right.operand`). Missing here made
796
+ // any workspace that compares against a hash, a checksum, an API key or a hex
797
+ // token fail its export outright — `md5("hello world")` asserted in a
798
+ // precondition read as a pointer at an object that was never registered
799
+ // (issue #132). `normalize` already pairs the two keys for the same reason.
800
+ "operand",
801
+ "default",
802
+ "filters",
803
+ "search",
804
+ "docs",
805
+ "description",
806
+ "mocks",
807
+ // A saved unit test's subtree: its `input[]` and its assertions' `vars[]` are
808
+ // author values like any other, and its `id` is the test's OWN identity, not
809
+ // a pointer at another object — the same case as `guid` below. Without this a
810
+ // pulled test's 32-hex id read as a reference to an object nothing registers,
811
+ // which is a hard export error.
812
+ "test",
813
+ "tag",
814
+ "settings_registry",
815
+ // An object's own identity, not a pointer at another object.
816
+ "guid"
817
+ ]);
818
+ var EXTERNAL_REF_KEYS = /* @__PURE__ */ new Set(["run_version"]);
819
+ var EMBEDDED_REF_RE = /^(?:dbo=([0-9a-f]{32})|([0-9a-f]{32})_mvpschema)$/;
820
+ function refGuid(value, allowBare) {
821
+ const embedded = EMBEDDED_REF_RE.exec(value);
822
+ if (embedded) return embedded[1] ?? embedded[2];
823
+ return allowBare && GUID_RE.test(value) ? value : null;
824
+ }
825
+ function collectDanglingRefs(node, path, owner, known, out, embeddedOnly = false) {
826
+ const stack = [{ node, path, embeddedOnly }];
827
+ while (stack.length > 0) {
828
+ const frame = stack.pop();
829
+ if ("text" in frame) {
830
+ const guid = refGuid(frame.text, frame.allowBare);
831
+ if (guid && !known.has(guid)) out.push({ owner, path: frame.path, guid });
832
+ continue;
833
+ }
834
+ const current = frame.node;
835
+ if (Array.isArray(current)) {
836
+ for (let i = current.length - 1; i >= 0; i--) {
837
+ const item = current[i];
838
+ const itemPath = `${frame.path}[${i}]`;
839
+ stack.push(
840
+ typeof item === "string" ? { text: item, path: itemPath, allowBare: false } : { node: item, path: itemPath, embeddedOnly: frame.embeddedOnly }
841
+ );
842
+ }
843
+ continue;
844
+ }
845
+ if (!current || typeof current !== "object") continue;
846
+ const entries = Object.entries(current);
847
+ for (let i = entries.length - 1; i >= 0; i--) {
848
+ const [key, value] = entries[i];
849
+ if (EXTERNAL_REF_KEYS.has(key)) continue;
850
+ const inValue = frame.embeddedOnly || VALUE_KEYS.has(key);
851
+ const valuePath = `${frame.path}.${key}`;
852
+ stack.push(
853
+ typeof value === "string" ? { text: value, path: valuePath, allowBare: !inValue } : { node: value, path: valuePath, embeddedOnly: inValue }
854
+ );
855
+ }
856
+ }
857
+ }
858
+ function checkReferences(bundleType, sections, workspaceGuid, bag) {
859
+ if (bundleType !== "workspace") return;
860
+ const known = /* @__PURE__ */ new Set();
861
+ if (typeof workspaceGuid === "string") known.add(workspaceGuid);
862
+ for (const arr of Object.values(sections)) {
863
+ for (const obj of arr ?? []) {
864
+ const guid = obj?.guid;
865
+ if (typeof guid === "string") known.add(guid);
866
+ }
867
+ }
868
+ const dangling = [];
869
+ for (const [payloadKey, arr] of Object.entries(sections)) {
870
+ for (const obj of arr ?? []) {
871
+ if (!obj || typeof obj !== "object") continue;
872
+ const name = obj.name;
873
+ const owner = `${payloadKey} "${typeof name === "string" ? name : "?"}"`;
874
+ collectDanglingRefs(obj, "$", owner, known, dangling);
875
+ }
876
+ }
877
+ const byGuid = /* @__PURE__ */ new Map();
878
+ for (const ref of dangling) {
879
+ const bucket = byGuid.get(ref.guid);
880
+ if (bucket) bucket.push(ref);
881
+ else byGuid.set(ref.guid, [ref]);
882
+ }
883
+ for (const [guid, refs] of byGuid) {
884
+ const owners = [...new Set(refs.map((r) => r.owner))];
885
+ const from = owners.length === 1 ? owners[0] : `${owners.slice(0, 3).join(", ")}${owners.length > 3 ? `, +${owners.length - 3} more` : ""}`;
886
+ const seed = guidSeedHint(guid);
887
+ const target = seed ? `${seed.slice(0, seed.indexOf(":"))} "${seed.slice(seed.indexOf(":") + 1)}" (guid ${guid})` : `an object with guid ${guid}`;
888
+ bag.error(
889
+ "reference.unresolved",
890
+ `${from} references ${target}, which is not registered on this workspace. A reference is resolved to a guid from the target's NAME with no registry lookup, so a mistyped name produces a valid-looking guid that only fails at deploy \u2014 "Invalid <kind> reference. Try importing: ${guid}" \u2014 after the import has begun. Check the name for a typo and register the target; pass the def handle rather than a bare name so a rename cannot silently break the reference. Reference found at ${refs[0].path}.`
891
+ );
892
+ }
893
+ }
894
+ var LOOP_CONTROL_STATEMENTS = /* @__PURE__ */ new Map([
895
+ ["mvp:foreach_break", "s.foreach_break()"],
896
+ ["mvp:foreach_continue", "s.foreach_continue()"],
897
+ ["mvp:foreach_remove", "s.foreach_remove()"]
898
+ ]);
899
+ var LOOP_STATEMENTS = /* @__PURE__ */ new Set(["mvp:for", "mvp:foreach", "mvp:while"]);
900
+ var REQUEST_ONLY_TIME_FILTERS = new Set(
901
+ FILTER_NAMES.filter((n) => n.startsWith("epochms_") && !isQueryExpressionFilter(n))
902
+ );
903
+ function operandFilterNames(operand, out) {
904
+ if (!operand || typeof operand !== "object") return;
905
+ const filters = operand.filters;
906
+ if (!Array.isArray(filters)) return;
907
+ for (const f of filters) {
908
+ if (f && typeof f === "object" && typeof f.name === "string") {
909
+ out.push(f.name);
910
+ }
911
+ }
912
+ }
913
+ function checkSearchOperandFilters(sections, bag) {
914
+ if (REQUEST_ONLY_TIME_FILTERS.size === 0) return;
915
+ for (const [payloadKey, arr] of Object.entries(sections)) {
916
+ for (const obj of arr ?? []) {
917
+ if (!obj || typeof obj !== "object") continue;
918
+ const name = obj.name;
919
+ const owner = `${payloadKey} "${typeof name === "string" ? name : "?"}"`;
920
+ walkNodes(obj, (record) => {
921
+ const context = record.context;
922
+ if (!context || typeof context.search !== "object" || context.search === null) return;
923
+ const found = [];
924
+ walkNodes(context.search, (node) => {
925
+ const statement = node.statement;
926
+ if (!statement || typeof statement !== "object") return;
927
+ operandFilterNames(statement.left, found);
928
+ operandFilterNames(statement.right, found);
929
+ });
930
+ const offending = [...new Set(found)].filter((n) => REQUEST_ONLY_TIME_FILTERS.has(n));
931
+ for (const filter of offending) {
932
+ bag.warn(
933
+ "search.request-only-filter",
934
+ `${owner} pipes a search operand through \`${filter}\`, which evaluates only in the request. A \`where\` is compiled into SQL, where that filter has no form \u2014 the endpoint deploys clean and then fails the request with a bare fatal naming nothing. Either use the SQL-side spelling (\`epochms_add_day\`, \`epochms_sub_month\`, and the rest of that family) or compute the value in an earlier \`s.set_var\` and reference it with \`ref()\` in the \`where\`.`
935
+ );
936
+ }
937
+ });
938
+ }
939
+ }
940
+ }
941
+ function checkSwitchFallthrough(sections, bag) {
942
+ for (const [payloadKey, arr] of Object.entries(sections)) {
943
+ for (const obj of arr ?? []) {
944
+ if (!obj || typeof obj !== "object") continue;
945
+ const name = obj.name;
946
+ const owner = `${payloadKey} "${typeof name === "string" ? name : "?"}"`;
947
+ walkNodes(obj, (node) => {
948
+ if (node.name !== "mvp:switch") return;
949
+ const context = node.context;
950
+ const cases = context?.elif?.run;
951
+ if (!Array.isArray(cases)) return;
952
+ const hasDefault = Array.isArray(context?.else?.run) && context.else.run.length > 0;
953
+ cases.forEach((entry, i) => {
954
+ if (!entry || typeof entry !== "object") return;
955
+ const record = entry;
956
+ if (record.disabled === true) return;
957
+ const caseContext = record.context;
958
+ if (caseContext?.break === true) return;
959
+ const fallsInto = i < cases.length - 1 || hasDefault;
960
+ if (!fallsInto) return;
961
+ const target = i < cases.length - 1 ? `case ${i + 2} of ${cases.length}` : "the `default` block";
962
+ bag.warn(
963
+ "switch.missing-break",
964
+ `${owner} has a \`s.switch\` whose case ${i + 1} omits \`break: true\`, so a match there FALLS THROUGH and also runs ${target}. The engine's default is fallthrough, not stop \u2014 every statement in the later bodies runs as well, which duplicates whatever they write. Add \`break: true\` to that case. If the cascade IS deliberate there is no suppression: this stays a warning and never blocks a plain export, but \`--strict\` fails on it, so a strict build wants the cases restructured (or a \`s.conditional\` chain) rather than the fallthrough.`
965
+ );
966
+ });
967
+ });
968
+ }
969
+ }
970
+ }
971
+ function checkLoopControl(sections, bag) {
972
+ for (const [payloadKey, arr] of Object.entries(sections)) {
973
+ for (const obj of arr ?? []) {
974
+ if (!obj || typeof obj !== "object") continue;
975
+ const name = obj.name;
976
+ const owner = `${payloadKey} "${typeof name === "string" ? name : "?"}"`;
977
+ walkLoopDepth(obj, 0, owner, bag);
978
+ }
979
+ }
980
+ }
981
+ function walkLoopDepth(root, rootDepth, owner, bag) {
982
+ const stack = [{ node: root, depth: rootDepth }];
983
+ while (stack.length > 0) {
984
+ const { node, depth } = stack.pop();
985
+ if (Array.isArray(node)) {
986
+ for (let i = node.length - 1; i >= 0; i--) stack.push({ node: node[i], depth });
987
+ continue;
988
+ }
989
+ if (!node || typeof node !== "object") continue;
990
+ const record = node;
991
+ const statement = typeof record.name === "string" ? record.name : void 0;
992
+ const disabled = record.disabled === true;
993
+ if (!disabled && statement !== void 0 && LOOP_CONTROL_STATEMENTS.has(statement) && depth === 0) {
994
+ const spelling = LOOP_CONTROL_STATEMENTS.get(statement);
995
+ bag.error(
996
+ "stack.loop-control-outside-loop",
997
+ `${owner} runs \`${spelling}\` outside any loop. Loop control is only defined inside the body of \`s.foreach\`, \`s.for\` or \`s.while\` \u2014 the engine receives a break with no loop to break out of, which is undefined behaviour rather than a no-op, and nothing reports it at deploy. Move it into a loop body, or delete it. (A conditional or group INSIDE a loop is still inside the loop; this fires only when no loop encloses it at all.)`
998
+ );
999
+ }
1000
+ if (statement !== void 0 && LOOP_STATEMENTS.has(statement)) {
1001
+ const context = record.context;
1002
+ const body = context?.run;
1003
+ stack.push({ node: body, depth: depth + 1 });
1004
+ if (context && typeof context === "object") {
1005
+ const inner = Object.entries(context);
1006
+ for (let i = inner.length - 1; i >= 0; i--) {
1007
+ const [key, value] = inner[i];
1008
+ if (key === "run") continue;
1009
+ stack.push({ node: value, depth });
1010
+ }
1011
+ }
1012
+ const own = Object.entries(record);
1013
+ for (let i = own.length - 1; i >= 0; i--) {
1014
+ const [key, value] = own[i];
1015
+ if (key === "context") continue;
1016
+ stack.push({ node: value, depth });
1017
+ }
1018
+ continue;
1019
+ }
1020
+ const values = Object.values(record);
1021
+ for (let i = values.length - 1; i >= 0; i--) stack.push({ node: values[i], depth });
1022
+ }
1023
+ }
1024
+ function checkMicroserviceSecrets(sections, bag) {
1025
+ const rows = sections.microservice;
1026
+ if (!Array.isArray(rows)) return;
1027
+ for (const row of rows) {
1028
+ if (!row || typeof row !== "object") continue;
1029
+ const record = row;
1030
+ const name = typeof record.name === "string" ? record.name : "(unnamed)";
1031
+ const carried = [];
1032
+ const auth = record["registry_auth"];
1033
+ if (typeof auth?.["dockerconfigjson"] === "string" && auth["dockerconfigjson"] !== "") {
1034
+ carried.push("`registryAuth.dockerconfigjson`");
1035
+ }
1036
+ const chart = record["chart"];
1037
+ if (typeof chart?.["values"] === "string" && chart["values"] !== "") {
1038
+ carried.push("`chart.values`");
1039
+ }
1040
+ if (carried.length === 0) continue;
1041
+ bag.notice(
1042
+ "microservice.secret-in-bundle",
1043
+ `microservice "${name}" writes ${carried.join(" and ")} into this bundle verbatim \u2014 the engine stores the value as given, so there is no deploy-time indirection and a \`process.env\` read here resolves at export, not on the tenant. Treat the bundle (and any tree pulled from it) as secret material: keep it out of git, or rotate the credential after it lands there. Workspace env vars are the surface that IS mapped \u2014 \`workspaceConfig({ env })\` sets them on the tenant and \`env("NAME")\` reads one back in a stack, without the value appearing in a stack literal.`
1044
+ );
1045
+ }
1046
+ }
1047
+ function checkMicroserviceBlocks(sections, bag) {
1048
+ const rows = sections.microservice;
1049
+ if (!Array.isArray(rows) || rows.length === 0) return;
1050
+ bag.notice(
1051
+ "microservice.early-surface",
1052
+ `this workspace declares ${rows.length === 1 ? "a microservice" : `${rows.length} microservices`} \u2014 an EARLY platform surface whose stored shape is expected to change. What deploys today is a \`builtin\` microservice's \`deployment\`/\`ingresses\` and a \`helm\` one's \`chart\`; \`configs\`/\`volumes\` are refused at export because the engine rejects an import carrying them. Pin \`@xanots/sdk\` for reproducible builds and re-read the microservice section on upgrade.`
1053
+ );
1054
+ for (const row of rows) {
1055
+ if (!row || typeof row !== "object") continue;
1056
+ const record = row;
1057
+ const name = typeof record.name === "string" ? record.name : "(unnamed)";
1058
+ const populated = ["configs", "volumes"].filter((key) => {
1059
+ const value = record[key];
1060
+ return Array.isArray(value) && value.length > 0;
1061
+ });
1062
+ if (populated.length === 0) continue;
1063
+ const fields = populated.map((key) => `\`${key}\``).join(" and ");
1064
+ const workaround = populated.map(
1065
+ (key) => key === "configs" ? "a value the workload reads goes in a container's `env` (`deployment.containers[].env`)" : "storage goes in a container's own `volumes` (`emptyDir`, `persistent`, or `config`)"
1066
+ ).join("; ");
1067
+ bag.error(
1068
+ "microservice.unsupported-block",
1069
+ `microservice "${name}" declares ${fields}, which the engine refuses on import \u2014 the workspace fatals mid-deploy, after provisioning has begun. Refused here instead, where nothing has been created yet. Workaround: ${workaround}. Both fields are typed and \`@deprecated\` because a pulled workspace can still carry them; neither can be deployed by this SDK today.`
1070
+ );
1071
+ }
1072
+ }
1073
+ var RUN_FAMILY = {
1074
+ "mvp:workspace_run_function": "s.function.call",
1075
+ "mvp:workspace_run_tool": "s.tool.call",
1076
+ "mvp:workspace_run_middleware": "s.middleware.call",
1077
+ "mvp:workspace_run_endpoint": "s.api.call",
1078
+ "mvp:workspace_run_task": "s.task.call",
1079
+ "mvp:workspace_run_trigger": "s.trigger.call",
1080
+ "mvp:workspace_run_workflow_test": "s.workflow_test.call",
1081
+ "mvp:workspace_run_addon": "s.addon.call"
1082
+ };
1083
+ var RUNS_OUTSIDE_A_WORKFLOW_TEST = /* @__PURE__ */ new Set([
1084
+ "mvp:workspace_run_function",
1085
+ "mvp:workspace_run_tool",
1086
+ "mvp:workspace_run_middleware",
1087
+ "mvp:workspace_run_addon"
1088
+ ]);
1089
+ function checkRunFamilyHosts(sections, bag) {
1090
+ for (const [payloadKey, arr] of Object.entries(sections)) {
1091
+ if (payloadKey === "workflow_test") continue;
1092
+ for (const obj of arr ?? []) {
1093
+ if (!obj || typeof obj !== "object") continue;
1094
+ const objName = obj.name;
1095
+ const owner = `${payloadKey} "${typeof objName === "string" ? objName : "?"}"`;
1096
+ const statements = [];
1097
+ collectStatements(obj, statements);
1098
+ const found = new Set(
1099
+ statements.map((s) => s.name).filter((name) => name in RUN_FAMILY && !RUNS_OUTSIDE_A_WORKFLOW_TEST.has(name))
1100
+ );
1101
+ for (const name of found) {
1102
+ const factory = RUN_FAMILY[name];
1103
+ bag.error(
1104
+ "statement.run-family-host",
1105
+ `${owner} contains \`${factory}\`, which only runs inside a workflow test. Outside one the engine resolves this step against a registry holding only functions, tools and middleware, so it deploys clean, passes from the builder (whose runner loads the whole workspace), and then fails the first real request with \`ERROR_FATAL: \u2026 does not exist\`. Move it into a \`workflowTest({ \u2026 })\`, where the whole family is available. From an ordinary stack use \`s.function.run\`, \`s.function.call\`, \`s.tool.call\` or \`s.middleware.call\`.`
1106
+ );
1107
+ }
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ // src/workspace/xano.ts
1113
+ var XANO_BRAND = /* @__PURE__ */ Symbol.for("xanots.Xano");
1114
+ var Xano = class {
1115
+ /** @internal cross-realm identity brand — see {@link Xano.isXano}. */
1116
+ [XANO_BRAND] = true;
1117
+ /** Encoded objects keyed by `packageExport` payload key. */
1118
+ sections = /* @__PURE__ */ new Map();
1119
+ /** Table defs, encoded lazily at {@link export} so each can inherit the workspace `use_xdo`. */
1120
+ tableDefs = [];
1121
+ workspaceConfig = {};
1122
+ bundleType = "workspace";
1123
+ /** True for any `Xano` registry, even one created by a different module instance. */
1124
+ static isXano(value) {
1125
+ return typeof value === "object" && value !== null && value[XANO_BRAND] === true;
1126
+ }
1127
+ /** Encode one def and stamp its deterministic guid (the engine's sync/reference anchor). */
1128
+ encodeOne(kind, def) {
1129
+ const encoded = kind.encode(def);
1130
+ assertMockKeysResolved(encoded, kind.name);
1131
+ if (REFERENCEABLE_KINDS.has(kind.name) && encoded && typeof encoded === "object") {
1132
+ const obj = encoded;
1133
+ const explicit = def.guid;
1134
+ if (obj.guid === void 0 && typeof obj.name === "string") {
1135
+ obj.guid = explicit ?? kind.guidOf?.(def) ?? deriveGuid(kind.payloadKey, obj.name);
1136
+ }
1137
+ }
1138
+ return encoded;
1139
+ }
1140
+ /** Register one or more authoring defs of a given kind. */
1141
+ register(kindName, defOrDefs) {
1142
+ const kind = getKind(kindName);
1143
+ const defs = Array.isArray(defOrDefs) ? defOrDefs : [defOrDefs];
1144
+ for (const def of defs) this.assertNotAlreadyRegistered(kind.name, def);
1145
+ if (kind.name === "table") {
1146
+ for (const def of defs) this.tableDefs.push(def);
1147
+ return this;
1148
+ }
1149
+ const bucket = this.sections.get(kind.payloadKey) ?? [];
1150
+ for (const def of defs) bucket.push(this.encodeOne(kind, def));
1151
+ this.sections.set(kind.payloadKey, bucket);
1152
+ return this;
1153
+ }
1154
+ /**
1155
+ * Every def object handed to {@link register}, by identity, so the SAME one
1156
+ * registered twice is caught at the call that did it (issue #54).
1157
+ *
1158
+ * Identity, not structure. Two separately constructed defs sharing a name are
1159
+ * a genuine collision, and the guid check at export already diagnoses that
1160
+ * correctly — it explains that identity derives from `(type, name)` and names
1161
+ * both objects. This guard must not shadow it.
1162
+ */
1163
+ registered = /* @__PURE__ */ new Set();
1164
+ /**
1165
+ * Refuse the same def object twice.
1166
+ *
1167
+ * The failure this front-runs: a helper registers a def on your behalf and
1168
+ * you then register it again yourself — `registerAuth(ws).registerTables([userTable])`
1169
+ * is the reported shape. Nothing objected at the second call; it surfaced much
1170
+ * later at `emitBundle` as `Duplicate object guid (efb55…) shared by "dbo/user"
1171
+ * and "dbo/user"`, a message that names a guid and the same label twice and
1172
+ * points at neither line.
1173
+ */
1174
+ assertNotAlreadyRegistered(kindName, def) {
1175
+ if (def === null || typeof def !== "object") return;
1176
+ if (!this.registered.has(def)) {
1177
+ this.registered.add(def);
1178
+ return;
1179
+ }
1180
+ const name = def.name;
1181
+ const label = typeof name === "string" ? `"${name}"` : "it";
1182
+ throw new Error(
1183
+ `register: the same ${kindName} def ${label} was registered twice on this workspace. The second registration is always a mistake \u2014 the bundle would carry the object twice under one guid, which fails the import. A helper that registers on your behalf is the usual cause (\`registerAuth(ws)\` registers its own tables, so a following \`registerTables([userTable])\` is the duplicate): drop the manual call. Two DIFFERENT defs that share a name are a separate problem, and export reports that one by guid.`
1184
+ );
1185
+ }
1186
+ /**
1187
+ * Register the workspace settings object (singleton).
1188
+ *
1189
+ * A config with no `name` inherits the one already on the registry — which is
1190
+ * what `workspace("my-app")` set — so the natural chain
1191
+ * `workspace("my-app").registerWorkspace(workspaceConfig({ history }))` no
1192
+ * longer makes an author restate a name this registry has held since its
1193
+ * first call (#228). An explicit `name` still wins, and a rename this way is
1194
+ * a rename of the workspace.
1195
+ */
1196
+ registerWorkspace(def) {
1197
+ const authored = def ?? {};
1198
+ const inherited = this.workspaceConfig.name;
1199
+ const withName = authored.name === void 0 && typeof inherited === "string" ? { ...authored, name: inherited } : authored;
1200
+ this.workspaceConfig = encodeObject("workspace", withName);
1201
+ return this;
1202
+ }
1203
+ /** Set the bundle `type` (defaults to "workspace"). */
1204
+ setBundleType(type) {
1205
+ this.bundleType = type;
1206
+ return this;
1207
+ }
1208
+ /**
1209
+ * The registered table defs (with their authored `seed`), for the Node deploy
1210
+ * path to build `content/` seed entries. Deliberately NOT reached from
1211
+ * `export()` — seed values are resolved only in the deploy pipeline, so they
1212
+ * never enter the browser-safe bundle. See {@link import("./seed.js")}.
1213
+ */
1214
+ tables() {
1215
+ return [...this.tableDefs];
1216
+ }
1217
+ registerFunctions(defs) {
1218
+ return this.register("function", defs);
1219
+ }
1220
+ registerTriggers(defs) {
1221
+ return this.register("trigger", defs);
1222
+ }
1223
+ registerTools(defs) {
1224
+ return this.register("tool", defs);
1225
+ }
1226
+ registerMcpServers(defs) {
1227
+ return this.register("mcp_server", defs);
1228
+ }
1229
+ registerAgents(defs) {
1230
+ return this.register("agent", defs);
1231
+ }
1232
+ registerTables(defs) {
1233
+ return this.register("table", defs);
1234
+ }
1235
+ registerQueries(defs) {
1236
+ return this.register("query", defs);
1237
+ }
1238
+ registerApiGroups(defs) {
1239
+ return this.register("api_group", defs);
1240
+ }
1241
+ registerTasks(defs) {
1242
+ return this.register("task", defs);
1243
+ }
1244
+ registerMiddleware(defs) {
1245
+ return this.register("middleware", defs);
1246
+ }
1247
+ registerAddons(defs) {
1248
+ return this.register("addon", defs);
1249
+ }
1250
+ registerMicroservices(defs) {
1251
+ return this.register("microservice", defs);
1252
+ }
1253
+ registerRealtimeServers(defs) {
1254
+ return this.register("realtime_server", defs);
1255
+ }
1256
+ registerRealtimeChannels(defs) {
1257
+ return this.register("channel", defs);
1258
+ }
1259
+ registerRealtimeMessages(defs) {
1260
+ return this.register("message", defs);
1261
+ }
1262
+ registerWorkflowTests(defs) {
1263
+ return this.register("workflow_test", defs);
1264
+ }
1265
+ /**
1266
+ * Assemble the signed aggregate `packageExport` bundle.
1267
+ *
1268
+ * With `options.lock` (a {@link LockExportContext}), the export participates
1269
+ * in identity locking: empty api-group/toolset canonicals are filled from the
1270
+ * lock — minted fresh on first sight — and every identity the bundle emits is
1271
+ * reported back through `ctx.observed` (MUTATING the passed context), which
1272
+ * the caller merges into the lock file. All lock work happens before the
1273
+ * bundle is signed. Without options the output is byte-identical to before.
1274
+ *
1275
+ * With `options.strict`, every build WARNING fails the export instead of
1276
+ * printing — the shapes that deploy clean and then lose data or return the
1277
+ * wrong rows. Nothing about the emitted bundle changes; it either exports or
1278
+ * it does not (issue #15).
1279
+ */
1280
+ export(options = {}) {
1281
+ const lockCtx = options.lock;
1282
+ const sections = {};
1283
+ for (const [key, arr] of this.sections) {
1284
+ sections[key] = arr;
1285
+ }
1286
+ if (this.tableDefs.length > 0) {
1287
+ const tableKind = getKind("table");
1288
+ const wsUseXdo = this.workspaceConfig.use_xdo === true;
1289
+ sections[tableKind.payloadKey] = this.tableDefs.map((def) => {
1290
+ const encoded = this.encodeOne(
1291
+ tableKind,
1292
+ def.useXdo === void 0 ? { ...def, useXdo: wsUseXdo } : def
1293
+ );
1294
+ return { ...encoded, import: { mode: "standard" } };
1295
+ });
1296
+ }
1297
+ const bag = new DiagnosticBag(options.strict === true);
1298
+ this.validateQueryAuth(sections, bag);
1299
+ this.validateMiddlewareAuth(sections, bag);
1300
+ checkMicroserviceSecrets(sections, bag);
1301
+ checkMicroserviceBlocks(sections, bag);
1302
+ checkStacks(this.tableDefs, sections, bag);
1303
+ checkExpressionOperands(sections, bag);
1304
+ checkReferences(this.bundleType, sections, this.workspaceConfig.guid, bag);
1305
+ checkDecodeOnlyStatements(sections, bag);
1306
+ checkRunFamilyHosts(sections, bag);
1307
+ checkLoopControl(sections, bag);
1308
+ checkSwitchFallthrough(sections, bag);
1309
+ checkSearchOperandFilters(sections, bag);
1310
+ checkRealtimeGates(sections, bag);
1311
+ checkRealtimeDeliver(this.bundleType, sections, bag);
1312
+ checkTriggerBindings(sections, bag);
1313
+ checkUnboundVarRefs(sections, bag);
1314
+ checkSafeRefMatchArgs(sections, bag);
1315
+ checkNullMatchArgs(sections, bag);
1316
+ checkSameNameSiblings(sections, bag);
1317
+ checkSeed(this.tableDefs, bag);
1318
+ checkRealtimePublish(this.bundleType, sections, bag);
1319
+ bag.flush();
1320
+ if (lockCtx) this.applyLock(lockCtx, sections);
1321
+ if (this.workspaceConfig.guid === void 0) {
1322
+ const wsName = typeof this.workspaceConfig.name === "string" ? this.workspaceConfig.name : "workspace";
1323
+ this.workspaceConfig.guid = deriveGuid("workspace", wsName);
1324
+ }
1325
+ return buildBundle({
1326
+ type: this.bundleType,
1327
+ workspace: this.workspaceConfig,
1328
+ sections,
1329
+ lock: lockCtx?.lock
1330
+ });
1331
+ }
1332
+ /**
1333
+ * Cross-check every query's resolved `auth` against the registered auth tables.
1334
+ *
1335
+ * `resolveAuth` (in `encodeQuery`) turns a table ref into a guid with no
1336
+ * registry visibility, so a bare-name typo produces a valid-looking guid that
1337
+ * only fails at deploy with an opaque engine error. Here we have the registry,
1338
+ * so we catch it at export and name the offending query.
1339
+ *
1340
+ * A registered table that is not `table({ auth: true })` only WARNS: the
1341
+ * engine reads that flag nowhere at request time (it compares the token's
1342
+ * `dbo` to the endpoint's by name, and mints tokens for any table by name), so
1343
+ * refusing the combination blocked a real workspace's round trip. A numeric
1344
+ * `auth` (raw `dbo.id` escape hatch) references a table by id xanots never
1345
+ * sees, so it's left as-is; `false` is a public endpoint. A table that pins an
1346
+ * explicit `guid` referenced by bare name lands in the "not registered" branch
1347
+ * (its name-derived guid diverges from the pinned one) — pass the def instead.
1348
+ */
1349
+ validateQueryAuth(sections, bag) {
1350
+ const queries = sections["query"];
1351
+ if (!Array.isArray(queries) || queries.length === 0) return;
1352
+ const tableNameByGuid = /* @__PURE__ */ new Map();
1353
+ const authGuids = /* @__PURE__ */ new Set();
1354
+ for (const def of this.tableDefs) {
1355
+ const guid = resolveRef("dbo", def);
1356
+ tableNameByGuid.set(guid, def.name);
1357
+ if (def.auth === true) authGuids.add(guid);
1358
+ }
1359
+ for (const q of queries) {
1360
+ if (!q || typeof q !== "object") continue;
1361
+ const auth = q.auth;
1362
+ if (typeof auth !== "string" || authGuids.has(auth)) continue;
1363
+ const name = String(q.name ?? "?");
1364
+ const known = tableNameByGuid.get(auth);
1365
+ if (known) {
1366
+ bag.warn(
1367
+ "query.auth-table-unflagged",
1368
+ `query "${name}" requires auth against table "${known}", which is not marked \`table({ auth: true })\`. The engine allows it \u2014 a token minted for that table is accepted \u2014 but the editor will not offer the table as an auth source. Mark it if that is what you meant.`
1369
+ );
1370
+ continue;
1371
+ }
1372
+ bag.error(
1373
+ "query.auth-table-unregistered",
1374
+ `query "${name}": \`auth\` references a table that isn't registered on this workspace. Check for a typo, or register the auth table; if it pins an explicit \`guid\`, pass the table def rather than its name.`
1375
+ );
1376
+ }
1377
+ }
1378
+ /**
1379
+ * Warn about an `auth()`-keyed middleware **directly attached** to a host where
1380
+ * `auth()` may resolve to `null` (issue #81).
1381
+ *
1382
+ * The footgun: a rate limiter keyed by `auth("id")` is the canonical middleware,
1383
+ * but attach it to a host with no authenticated caller and `auth()` silently
1384
+ * resolves to `null` — every caller collapses into one shared bucket, with no
1385
+ * signal at author, export, or runtime. This surfaces it at export, where the
1386
+ * middleware registry is known (an attachment entry only carries the target's
1387
+ * guid; we resolve it back to the encoded `run` to inspect for `auth()`).
1388
+ *
1389
+ * It **warns, never throws** — a bare `auth()` reference is not proof of a
1390
+ * collapse (an IP-disambiguated key or a personalize-if-logged-in middleware
1391
+ * uses `auth()` where `null` is fine), so blocking the export would produce
1392
+ * false positives on legitimate use. The warning names the host and reason so
1393
+ * the author can confirm intent, vary the key, or move to an authenticated host.
1394
+ *
1395
+ * Scope: the host's EFFECTIVE chain, across all three tiers (issue #43). The
1396
+ * guard used to see only a host's own `middleware.pre`/`post`, so an author who
1397
+ * DRY'd a per-user limiter up to `apiGroup({ middleware })` or to the workspace
1398
+ * tier silently reintroduced the shared bucket — the same collapse, attached
1399
+ * one level up, with the warning switched off. Since the limiter is more
1400
+ * naturally written once at the group than repeated on every endpoint, the
1401
+ * tier that most needed the warning was the one that never got it.
1402
+ *
1403
+ * The cascade is the engine's own: a phase whose `_customize` flag is set is
1404
+ * answered by that tier (including `clear()`, which customizes with an empty
1405
+ * list and therefore stops inheritance); an un-customized phase falls through
1406
+ * to the API group, then to the workspace's `{objType}_{phase}` map. A query
1407
+ * that clears a phase is NOT warned about a group middleware it does not run.
1408
+ *
1409
+ * An authenticated `query` (its own `auth` table set) resolves an identity, so
1410
+ * it is skipped at every tier.
1411
+ */
1412
+ validateMiddlewareAuth(sections, bag) {
1413
+ const middlewares = sections["middleware"];
1414
+ if (!Array.isArray(middlewares) || middlewares.length === 0) return;
1415
+ const byGuid = /* @__PURE__ */ new Map();
1416
+ for (const mw of middlewares) {
1417
+ if (!mw || typeof mw !== "object") continue;
1418
+ const guid = mw.guid;
1419
+ if (typeof guid === "string") byGuid.set(guid, mw);
1420
+ }
1421
+ const authCache = /* @__PURE__ */ new Map();
1422
+ const referencesAuth = (guid) => {
1423
+ let hit = authCache.get(guid);
1424
+ if (hit === void 0) {
1425
+ const run = byGuid.get(guid)?.run;
1426
+ hit = stackReferencesAuth(Array.isArray(run) ? run : void 0);
1427
+ authCache.set(guid, hit);
1428
+ }
1429
+ return hit;
1430
+ };
1431
+ const hostKinds = [
1432
+ { key: "query", label: "query", reason: "this endpoint has no auth table" },
1433
+ { key: "task", label: "task", reason: "a task is scheduled/background and never has a request identity" },
1434
+ { key: "function", label: "function", reason: "auth() is null unless an authenticated caller invokes it" },
1435
+ { key: "tool", label: "tool", reason: "auth() is null unless an authenticated caller invokes it" }
1436
+ ];
1437
+ const groupByGuid = /* @__PURE__ */ new Map();
1438
+ for (const group of sections["app"] ?? []) {
1439
+ if (!group || typeof group !== "object") continue;
1440
+ const guid = group.guid;
1441
+ if (typeof guid === "string") groupByGuid.set(guid, group);
1442
+ }
1443
+ const workspaceTier = this.workspaceConfig.middleware ?? {};
1444
+ const list = (x) => Array.isArray(x) ? x : [];
1445
+ const customizes = (block, phase) => !!block && typeof block === "object" && block[`${phase}_customize`] === true;
1446
+ for (const { key, label, reason } of hostKinds) {
1447
+ const hosts = sections[key];
1448
+ if (!Array.isArray(hosts)) continue;
1449
+ for (const host of hosts) {
1450
+ if (!host || typeof host !== "object") continue;
1451
+ if (label === "query" && host.auth) continue;
1452
+ const block = host.middleware;
1453
+ const hostName = String(host.name ?? "?");
1454
+ const groupId = label === "query" ? host.app?.id : void 0;
1455
+ const group = typeof groupId === "string" ? groupByGuid.get(groupId) : void 0;
1456
+ for (const phase of ["pre", "post"]) {
1457
+ let entries;
1458
+ let tier;
1459
+ if (customizes(block, phase)) {
1460
+ entries = list(block[phase]);
1461
+ tier = "";
1462
+ } else if (group && customizes(group.middleware, phase)) {
1463
+ entries = list(group.middleware[phase]);
1464
+ tier = ` (inherited from apiGroup "${String(group.name ?? "?")}")`;
1465
+ } else {
1466
+ entries = list(workspaceTier[`${label}_${phase}`]);
1467
+ tier = " (inherited from the workspace tier)";
1468
+ }
1469
+ for (const entry of entries) {
1470
+ if (entry?.disabled === true) continue;
1471
+ const guid = middlewareEntryGuid(entry);
1472
+ if (!guid || !referencesAuth(guid)) continue;
1473
+ const mwName = String(byGuid.get(guid)?.name ?? guid);
1474
+ bag.warn(
1475
+ "middleware.auth-null-host",
1476
+ `middleware "${mwName}" references auth() and is attached to ${label} "${hostName}"${tier}, where ${reason}. A null auth() collapses all callers into one shared key \u2014 attach it to an authenticated host, vary the key, or remove auth().`
1477
+ );
1478
+ }
1479
+ }
1480
+ }
1481
+ }
1482
+ }
1483
+ /**
1484
+ * Lock participation (see {@link export}): canonical fill + identity report.
1485
+ * Runs after all sections are assembled and before signing.
1486
+ */
1487
+ applyLock(ctx, sections) {
1488
+ const usedCanonicals = /* @__PURE__ */ new Set();
1489
+ for (const entry of Object.values(ctx.lock.objects)) {
1490
+ if (entry.canonical) usedCanonicals.add(entry.canonical);
1491
+ }
1492
+ for (const arr of Object.values(sections)) {
1493
+ for (const obj of arr ?? []) {
1494
+ const canonical = obj.canonical;
1495
+ if (typeof canonical === "string" && canonical !== "") usedCanonicals.add(canonical);
1496
+ }
1497
+ }
1498
+ const mintUnique = () => {
1499
+ let token = mintCanonical();
1500
+ while (usedCanonicals.has(token)) token = mintCanonical();
1501
+ usedCanonicals.add(token);
1502
+ return token;
1503
+ };
1504
+ const canonicalByGuid = (guid) => {
1505
+ if (typeof guid !== "string") return void 0;
1506
+ for (const entry of Object.values(ctx.lock.objects)) {
1507
+ if (entry.guid === guid && entry.canonical !== void 0) return entry.canonical;
1508
+ }
1509
+ return void 0;
1510
+ };
1511
+ for (const key of CANONICAL_PAYLOAD_KEYS) {
1512
+ for (const obj of sections[key] ?? []) {
1513
+ if (!obj || typeof obj !== "object") continue;
1514
+ const o = obj;
1515
+ if (typeof o.name !== "string" || o.canonical !== "") continue;
1516
+ o.canonical = ctx.lock.objects[lockKey(key, o.name)]?.canonical ?? canonicalByGuid(o.guid) ?? mintUnique();
1517
+ }
1518
+ }
1519
+ const ws = this.workspaceConfig;
1520
+ if (ws.canonical === "") {
1521
+ ws.canonical = ctx.lock.objects[WORKSPACE_KEY]?.canonical ?? "";
1522
+ }
1523
+ if (typeof ws.canonical === "string" && ws.canonical !== "") {
1524
+ recordObserved(ctx, WORKSPACE_KEY, { canonical: ws.canonical });
1525
+ }
1526
+ for (const [payloadKey, arr] of Object.entries(sections)) {
1527
+ for (const obj of arr ?? []) {
1528
+ if (!obj || typeof obj !== "object") continue;
1529
+ const o = obj;
1530
+ if (typeof o.name !== "string" || typeof o.guid !== "string") continue;
1531
+ const identity = { guid: o.guid };
1532
+ if (typeof o.canonical === "string" && o.canonical !== "") {
1533
+ identity.canonical = o.canonical;
1534
+ }
1535
+ recordObserved(ctx, lockKey(payloadKey, o.name), identity);
1536
+ }
1537
+ }
1538
+ }
1539
+ };
1540
+ function workspace(name) {
1541
+ return new Xano().registerWorkspace({ name });
1542
+ }
1543
+
1544
+ export {
1545
+ coerceSeedRowValues,
1546
+ assertSeedIds,
1547
+ Xano,
1548
+ workspace
1549
+ };
1550
+ //# sourceMappingURL=chunk-3DUAGYGP.js.map