@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.
- package/LICENSE +21 -0
- package/README.md +384 -0
- package/dist/agent-file-refresh-7XCACU34.js +95 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +184 -0
- package/dist/capture-HUV5BNTC.js +32 -0
- package/dist/chunk-26CNBZE2.js +963 -0
- package/dist/chunk-33ND6LKO.js +78 -0
- package/dist/chunk-3DUAGYGP.js +1550 -0
- package/dist/chunk-3IGNIP6R.js +89 -0
- package/dist/chunk-47WDWMBJ.js +14 -0
- package/dist/chunk-4BXJGVZ3.js +318 -0
- package/dist/chunk-4HT3BNZ7.js +675 -0
- package/dist/chunk-4YMD2OOZ.js +23 -0
- package/dist/chunk-5WGEURVI.js +156 -0
- package/dist/chunk-5YTBTF25.js +29 -0
- package/dist/chunk-6DHBYBTO.js +166 -0
- package/dist/chunk-76QBEIGO.js +66 -0
- package/dist/chunk-7DKX2SPN.js +100 -0
- package/dist/chunk-7REDODS2.js +468 -0
- package/dist/chunk-C56BC2FY.js +254 -0
- package/dist/chunk-DGSF2Q5H.js +60 -0
- package/dist/chunk-EMCAWMHC.js +3982 -0
- package/dist/chunk-EZG76F7R.js +159 -0
- package/dist/chunk-FJ6ZS5KB.js +3607 -0
- package/dist/chunk-GNPVYOPB.js +17 -0
- package/dist/chunk-HJPTWBLH.js +38 -0
- package/dist/chunk-IZW6MK3U.js +248 -0
- package/dist/chunk-LH6Z6KFV.js +247 -0
- package/dist/chunk-MZUI3JNX.js +93 -0
- package/dist/chunk-NDZFBZHC.js +167 -0
- package/dist/chunk-NOO35BQ2.js +37 -0
- package/dist/chunk-NUQCEOKA.js +13 -0
- package/dist/chunk-QTNO2WD6.js +56 -0
- package/dist/chunk-RVB7ITKN.js +49 -0
- package/dist/chunk-TRU5OHQ5.js +6904 -0
- package/dist/chunk-VKFQJEMW.js +814 -0
- package/dist/chunk-VTIL47DT.js +125 -0
- package/dist/chunk-WHOJWOSV.js +112 -0
- package/dist/chunk-WKP4D5MO.js +1692 -0
- package/dist/chunk-X4DVXBFY.js +38 -0
- package/dist/chunk-XQ22GLYS.js +118 -0
- package/dist/chunk-YDZYMZBE.js +670 -0
- package/dist/chunk-YGPIY3WT.js +1146 -0
- package/dist/chunk-YYRDLFSJ.js +602 -0
- package/dist/chunk-ZUTSMMAG.js +46 -0
- package/dist/cli.d.ts +331 -0
- package/dist/cli.js +33 -0
- package/dist/codegen-command-OOKK6AZK.js +40 -0
- package/dist/codegen.d.ts +114 -0
- package/dist/codegen.js +71 -0
- package/dist/completion-TIPPZN7H.js +298 -0
- package/dist/config-NL33PN4D.js +84 -0
- package/dist/deploy-command-4EQ7XX5G.js +451 -0
- package/dist/env-target-IFJ5QDHC.js +16 -0
- package/dist/ephemeral-command-3X3I4ULC.js +242 -0
- package/dist/index.d.ts +4477 -0
- package/dist/index.js +300 -0
- package/dist/init-command-R6H7ZXW5.js +27 -0
- package/dist/internal.d.ts +74 -0
- package/dist/internal.js +2065 -0
- package/dist/io-AMIKRLPC.js +11 -0
- package/dist/live-diff-Y3AKETHE.js +196 -0
- package/dist/lock-3CVKALKT.js +37 -0
- package/dist/lock-commands-XTR2YKLY.js +232 -0
- package/dist/login-command-ACJF6KWQ.js +167 -0
- package/dist/logout-command-MX3MJS5U.js +68 -0
- package/dist/loop-OM3P2Z6O.js +93 -0
- package/dist/marketplace-command-RATXTKXN.js +379 -0
- package/dist/meta-client-OW5WKWW7.js +170 -0
- package/dist/node.d.ts +59 -0
- package/dist/node.js +442 -0
- package/dist/profile-command-SWJ3SPKR.js +77 -0
- package/dist/release-command-VV5UVZKL.js +427 -0
- package/dist/response-CVAE2kMj.d.ts +1915 -0
- package/dist/routes-manifest-SP3ZXLMR.js +244 -0
- package/dist/runtime-V4C3AC3A.js +13 -0
- package/dist/sandbox-details-command-HJE5SPVG.js +18 -0
- package/dist/sandbox-export-command-QCJY4GMV.js +24 -0
- package/dist/static-host-D6KS7X45.js +138 -0
- package/dist/store-BQ97nKIF.d.ts +7760 -0
- package/dist/test-command-TSPBQHBM.js +383 -0
- package/dist/validate-command-4ERHJSCO.js +122 -0
- package/dist/verify-rollout-FAVQ6EJY.js +40 -0
- package/dist/workspace-K72NP7SX.js +49 -0
- package/dist/workspace-command-OF23DPNI.js +156 -0
- package/dist/workspace-export-AJMGN3CQ.js +10 -0
- package/llms/fields.md +104 -0
- package/llms/filters.md +183 -0
- package/llms/kinds-agent-mcp.md +11 -0
- package/llms/kinds-core.md +68 -0
- package/llms/kinds-realtime.md +76 -0
- package/llms/lambda.md +111 -0
- package/llms/legacy.md +43 -0
- package/llms/object-kinds.md +31 -0
- package/llms/statements-calls.md +23 -0
- package/llms/statements-catalog.md +309 -0
- package/llms/statements-data.md +58 -0
- package/llms/statements-runtime.md +48 -0
- package/llms/tests.md +58 -0
- package/llms/triggers.md +22 -0
- package/llms/values.md +29 -0
- package/llms-full.txt +1672 -0
- package/llms.txt +483 -0
- package/manifest.json +9592 -0
- package/package.json +110 -0
|
@@ -0,0 +1,3982 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertPathParamInputs,
|
|
3
|
+
fillPathParams,
|
|
4
|
+
parsePathParams
|
|
5
|
+
} from "./chunk-WHOJWOSV.js";
|
|
6
|
+
import {
|
|
7
|
+
COLUMN_CONTEXT,
|
|
8
|
+
INPUT_CONTEXT,
|
|
9
|
+
RAW_ENVELOPE,
|
|
10
|
+
annotate,
|
|
11
|
+
encodeField,
|
|
12
|
+
encodeStatement,
|
|
13
|
+
rawResponseItems,
|
|
14
|
+
registerStatement
|
|
15
|
+
} from "./chunk-LH6Z6KFV.js";
|
|
16
|
+
import {
|
|
17
|
+
deriveGuid,
|
|
18
|
+
getLockedCanonical,
|
|
19
|
+
lockKey,
|
|
20
|
+
resolveRef,
|
|
21
|
+
sha1Bytes
|
|
22
|
+
} from "./chunk-4HT3BNZ7.js";
|
|
23
|
+
|
|
24
|
+
// src/types/xdo.ts
|
|
25
|
+
var TAGS = [
|
|
26
|
+
"const",
|
|
27
|
+
"const:int",
|
|
28
|
+
"const:decimal",
|
|
29
|
+
"const:bool",
|
|
30
|
+
"const:array",
|
|
31
|
+
"const:obj",
|
|
32
|
+
"const:null",
|
|
33
|
+
// The engine's native epoch-milliseconds constant (`value:"now"`) — what
|
|
34
|
+
// `c.now()` emits. Distinct from `const` + a `to_epoch_ms` filter: both
|
|
35
|
+
// evaluate to the same number (live-verified), but this one needs no filter.
|
|
36
|
+
"const:epochms",
|
|
37
|
+
"const:expr",
|
|
38
|
+
"const:expr2",
|
|
39
|
+
"var",
|
|
40
|
+
"input",
|
|
41
|
+
"auth",
|
|
42
|
+
"env",
|
|
43
|
+
"setting",
|
|
44
|
+
"col",
|
|
45
|
+
"output",
|
|
46
|
+
"response",
|
|
47
|
+
"trycatch",
|
|
48
|
+
"toolset"
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// src/values/expression-arg.ts
|
|
52
|
+
var EXPRESSION_ARG_FILTERS = {
|
|
53
|
+
transform: 0
|
|
54
|
+
};
|
|
55
|
+
var UNBOUND_HERE = ["$this", "$parent", "$index", "$result"];
|
|
56
|
+
function withoutStringLiterals(source) {
|
|
57
|
+
return source.replace(/(['"])(?:\\.|(?!\1)[^\\])*\1?/g, (m) => {
|
|
58
|
+
const closed = m.length > 1 && m.at(-1) === m[0];
|
|
59
|
+
return m[0] + " ".repeat(m.length - (closed ? 2 : 1)) + (closed ? m[0] : "");
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function assertExpressionFilterArgs(name, args) {
|
|
63
|
+
const slot = EXPRESSION_ARG_FILTERS[name];
|
|
64
|
+
if (slot === void 0) return;
|
|
65
|
+
const arg = args[slot];
|
|
66
|
+
if (!isInspectableSource(arg) || arg.value.trim() === "") return;
|
|
67
|
+
assertExpressionArg(arg.value, `fl.${name}`);
|
|
68
|
+
}
|
|
69
|
+
function isInspectableSource(arg) {
|
|
70
|
+
return typeof arg === "object" && arg !== null && arg.tag === "const" && typeof arg.value === "string" && (arg.filters?.length ?? 0) === 0;
|
|
71
|
+
}
|
|
72
|
+
function assertExpressionArg(source, fn2) {
|
|
73
|
+
const scannable = withoutStringLiterals(source);
|
|
74
|
+
const unbound = UNBOUND_HERE.find((b) => new RegExp(`\\${b}\\b`).test(scannable));
|
|
75
|
+
if (unbound !== void 0) {
|
|
76
|
+
const bare = unbound.slice(1);
|
|
77
|
+
throw new Error(
|
|
78
|
+
`${fn2}: \`${unbound}\` is not bound in an expression \u2014 it resolves to null, and the filter still returns HTTP 200 with a wrong answer. ${fn2} takes Xano Expression Engine source, and the piped value arrives in the POSITIONAL slot: write \`$0\` (or \`$$\`) where you wrote \`${unbound}\`. ${unbound === "$this" ? "The engine's own filter description says `$this`; it is wrong. " : ""}\`$var\`, \`$input\`, \`$env\` and \`$auth\` DO resolve. (If a stack variable is genuinely named \`${bare}\`, spell it \`$var.${bare}\`.) (issue #245)`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const js = /(?<![$\w.])return\b/.test(scannable) && "a `return` statement" || /=>/.test(scannable) && "an arrow function" || /;/.test(scannable) && "a statement separator (`;`)";
|
|
82
|
+
if (js) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`${fn2}: the expression contains ${js}, so it is being written as a JavaScript body. ${fn2} is NOT a lambda \u2014 it takes Xano Expression Engine source, which is a single expression with no \`return\`. The engine parses it either way: \`return $0 * 2\` throws "Not numeric.", and \`const x = $0; return x\` returns the text "const x" with HTTP 200. Write the expression alone \u2014 \`$0 * 2\` \u2014 using \`$0\` (or \`$$\`) for the piped value. For real JavaScript use \`fl.lambda\`, whose body DOES bind \`$this\`. (issue #245)`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/values/enum-arg.ts
|
|
90
|
+
var ENUM_ARG_FILTERS = {
|
|
91
|
+
fsort: {
|
|
92
|
+
slot: 1,
|
|
93
|
+
arg: "type",
|
|
94
|
+
members: ["text", "itext", "natural", "inatural", "number"],
|
|
95
|
+
why: 'The engine accepts any spelling here and validates none of them: an unrecognized comparator falls through to "itext" and sorts numbers as TEXT, so the array comes back in the wrong order with no error. Only "number" compares numerically.'
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
function assertEnumFilterArgs(name, args) {
|
|
99
|
+
const spec = ENUM_ARG_FILTERS[name];
|
|
100
|
+
if (spec === void 0) return;
|
|
101
|
+
const arg = args[spec.slot];
|
|
102
|
+
if (!isInspectableConst(arg) || arg.value === "") return;
|
|
103
|
+
if (spec.members.includes(arg.value)) return;
|
|
104
|
+
throw new Error(
|
|
105
|
+
`fl.${name}: \`${spec.arg}\` is ${JSON.stringify(arg.value)}, which is not one of ${spec.members.map((m) => JSON.stringify(m)).join(", ")}. ${spec.why} (issue #198)`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
function isInspectableConst(arg) {
|
|
109
|
+
return typeof arg === "object" && arg !== null && arg.tag === "const" && typeof arg.value === "string" && (arg.filters?.length ?? 0) === 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/workspace/diagnostics.ts
|
|
113
|
+
var consoleSink = (diagnostic) => {
|
|
114
|
+
console.warn(formatDiagnostic(diagnostic));
|
|
115
|
+
};
|
|
116
|
+
var sink = consoleSink;
|
|
117
|
+
function formatDiagnostic(diagnostic) {
|
|
118
|
+
return `xanots: ${diagnostic.message}`;
|
|
119
|
+
}
|
|
120
|
+
function emitDiagnostic(diagnostic) {
|
|
121
|
+
if (diagnostic.severity === "error") throw new Error(formatDiagnostic(diagnostic));
|
|
122
|
+
sink(diagnostic);
|
|
123
|
+
}
|
|
124
|
+
var DiagnosticBag = class {
|
|
125
|
+
/**
|
|
126
|
+
* `strict`: report every warning as an error, so the build fails instead of
|
|
127
|
+
* printing.
|
|
128
|
+
*
|
|
129
|
+
* Warnings here are the shapes that deploy clean and then do the wrong thing —
|
|
130
|
+
* a `bulk.update` zero-filling the columns an item omits, an `ignoreEmpty`
|
|
131
|
+
* that returns the whole table. They stay warnings by default because each has
|
|
132
|
+
* a legitimate use, and the author who means it should not have to work around
|
|
133
|
+
* a guard. But the audience for this SDK is largely an agent that never reads
|
|
134
|
+
* stderr, and CI has no way to notice a message nobody fails on. `strict`
|
|
135
|
+
* gives a pipeline one switch that turns "printed something" into "did not
|
|
136
|
+
* ship" (issue #15).
|
|
137
|
+
*
|
|
138
|
+
* Scope: the diagnostics an EXPORT collects. A guard that runs while a
|
|
139
|
+
* statement is being built ({@link emitDiagnostic}) has already had its say by
|
|
140
|
+
* then — those throw or warn on the spot, at the line that caused them.
|
|
141
|
+
*/
|
|
142
|
+
constructor(strict = false) {
|
|
143
|
+
this.strict = strict;
|
|
144
|
+
}
|
|
145
|
+
strict;
|
|
146
|
+
items = [];
|
|
147
|
+
add(diagnostic) {
|
|
148
|
+
this.items.push(diagnostic);
|
|
149
|
+
}
|
|
150
|
+
error(code, message) {
|
|
151
|
+
this.add({ severity: "error", code, message });
|
|
152
|
+
}
|
|
153
|
+
warn(code, message) {
|
|
154
|
+
this.add({ severity: "warning", code, message });
|
|
155
|
+
}
|
|
156
|
+
/** Report something the author must know, which is not a defect. See the module note. */
|
|
157
|
+
notice(code, message) {
|
|
158
|
+
this.add({ severity: "notice", code, message });
|
|
159
|
+
}
|
|
160
|
+
/** Everything recorded so far, in the order it was found. */
|
|
161
|
+
all() {
|
|
162
|
+
return [...this.items];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Emit the warnings, then throw if anything was an error. Warnings are
|
|
166
|
+
* emitted first and unconditionally: an author fixing the error still wants
|
|
167
|
+
* to see the rest of what the build found. Under `strict` a warning IS an
|
|
168
|
+
* error — it is still printed, and it also fails the export. A notice is
|
|
169
|
+
* printed at both settings and fails at neither.
|
|
170
|
+
*/
|
|
171
|
+
flush() {
|
|
172
|
+
for (const item of this.items) {
|
|
173
|
+
if (item.severity !== "error") sink(item);
|
|
174
|
+
}
|
|
175
|
+
const errors = this.items.filter(
|
|
176
|
+
(item) => item.severity === "error" || this.strict && item.severity === "warning"
|
|
177
|
+
);
|
|
178
|
+
if (errors.length === 0) return;
|
|
179
|
+
const strictNote = this.strict ? " (`strict`: warnings are errors)" : "";
|
|
180
|
+
if (errors.length === 1) {
|
|
181
|
+
throw new Error(`${formatDiagnostic(errors[0])}${strictNote}`);
|
|
182
|
+
}
|
|
183
|
+
throw new Error(
|
|
184
|
+
`xanots: export failed with ${errors.length} errors${strictNote}.
|
|
185
|
+
` + errors.map((error, i) => ` ${i + 1}. ${error.message}`).join("\n")
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/values/value.ts
|
|
191
|
+
var REJECT_TAGGED_VALUE = "c.obj/c.array embed a plain JSON constant and cannot contain a tagged value (inp/ref/auth/col/env/c.int/c.text/c.bool/\u2026) \u2014 those serialize as internal representation the engine can't decode. For a computed object response, use a record of values \u2014 `response: { key: value }` \u2014 not `c.obj({ key: value })`. For a request body (`s.api.request({ params })`) a tagged value is fine at the TOP LEVEL of a plain object; nested, wrap the whole body in `obj({...})`, which encodes any depth. (issue #42)";
|
|
192
|
+
function isTaggedValue(x) {
|
|
193
|
+
return (
|
|
194
|
+
// A trigger field accessor (`t.new`) is a *callable* Value — a function
|
|
195
|
+
// carrying the `{value,tag,filters}` props — so `typeof` is "function", not
|
|
196
|
+
// "object". Accept both, or a bare `t.new` slips past this check, falls
|
|
197
|
+
// through coerceObj's record path, and trips the #42 c.obj guard (issue #78).
|
|
198
|
+
(typeof x === "object" || typeof x === "function") && x !== null && "value" in x && "filters" in x && Array.isArray(x.filters) && TAGS.includes(x.tag)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
function assertPlainJson(root) {
|
|
202
|
+
const stack = [root];
|
|
203
|
+
while (stack.length > 0) {
|
|
204
|
+
const x = stack.pop();
|
|
205
|
+
if (isTaggedValue(x)) throw new Error(REJECT_TAGGED_VALUE);
|
|
206
|
+
if (Array.isArray(x)) {
|
|
207
|
+
for (let i = x.length - 1; i >= 0; i--) stack.push(x[i]);
|
|
208
|
+
} else if (typeof x === "object" && x !== null) {
|
|
209
|
+
const values = Object.values(x);
|
|
210
|
+
for (let i = values.length - 1; i >= 0; i--) stack.push(values[i]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function val(value, tag, filters = []) {
|
|
215
|
+
return { value, tag, filters };
|
|
216
|
+
}
|
|
217
|
+
function isPlainRecord(x) {
|
|
218
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
219
|
+
}
|
|
220
|
+
var BARE_SET_PATH = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
221
|
+
function setPath(key) {
|
|
222
|
+
if (BARE_SET_PATH.test(key)) return key;
|
|
223
|
+
return `["${key.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`;
|
|
224
|
+
}
|
|
225
|
+
function objMember(m) {
|
|
226
|
+
if (isPlainRecord(m)) return val("{}", "const:obj", objSetFilters(m));
|
|
227
|
+
if (Array.isArray(m)) return val(JSON.stringify(m), "const:array");
|
|
228
|
+
if (m === null) return val("null", "const:null");
|
|
229
|
+
if (typeof m === "boolean") return val(m ? "true" : "false", "const:bool");
|
|
230
|
+
if (typeof m === "number") {
|
|
231
|
+
if (!Number.isFinite(m)) return val("null", "const:null");
|
|
232
|
+
return Number.isInteger(m) && Number.isSafeInteger(m) ? val(String(m), "const:int") : val(String(m), "const:decimal");
|
|
233
|
+
}
|
|
234
|
+
return val(String(m), "const");
|
|
235
|
+
}
|
|
236
|
+
function warnZeroBasedNumericKeys(o) {
|
|
237
|
+
const keys = Object.keys(o);
|
|
238
|
+
if (keys.length === 0) return;
|
|
239
|
+
if (!keys.every((k) => /^(0|[1-9][0-9]*)$/.test(k))) return;
|
|
240
|
+
const indices = new Set(keys.map(Number));
|
|
241
|
+
for (let i = 0; i < keys.length; i++) if (!indices.has(i)) return;
|
|
242
|
+
emitDiagnostic({
|
|
243
|
+
severity: "warning",
|
|
244
|
+
code: "value.obj-zero-based-numeric-keys",
|
|
245
|
+
message: `c.obj({${keys.map((k) => JSON.stringify(k)).join(", ")}: \u2026}) has zero-based numeric keys, so the engine evaluates it as a LIST \u2014 ${JSON.stringify(keys.map((k) => o[k]))} \u2014 not an object. A numeric key IS an index in that data model, which is not something this encoding introduces. If you want the list, write c.array([\u2026]) and the intent is on the page; if you want an object, prefix the keys so they are not an index run (e.g. "k0"). A non-zero-based numeric key ({"2": \u2026}) survives as a key.`
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function objSetFilters(o) {
|
|
249
|
+
return Object.entries(o).filter(([, m]) => m !== void 0).map(([key, m]) => filter("set", val(setPath(key), "const"), objMember(m)));
|
|
250
|
+
}
|
|
251
|
+
var PCRE_JS_FLAGS = "imsxu";
|
|
252
|
+
function escapeRegexSlashes(body) {
|
|
253
|
+
let out2 = "";
|
|
254
|
+
for (let i = 0; i < body.length; i++) {
|
|
255
|
+
const ch = body[i];
|
|
256
|
+
if (ch === "\\") {
|
|
257
|
+
out2 += ch + (body[i + 1] ?? "");
|
|
258
|
+
i++;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
out2 += ch === "/" ? "\\/" : ch;
|
|
262
|
+
}
|
|
263
|
+
return out2;
|
|
264
|
+
}
|
|
265
|
+
function isValidPcreLiteral(s) {
|
|
266
|
+
if (s.length < 2) return false;
|
|
267
|
+
const d = s[0];
|
|
268
|
+
if (/[a-zA-Z0-9\\\s]/.test(d)) return false;
|
|
269
|
+
const close = { "(": ")", "[": "]", "{": "}", "<": ">" }[d] ?? d;
|
|
270
|
+
let i = 1;
|
|
271
|
+
for (; i < s.length; i++) {
|
|
272
|
+
if (s[i] === "\\") {
|
|
273
|
+
i++;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (s[i] === close) break;
|
|
277
|
+
}
|
|
278
|
+
if (i >= s.length) return false;
|
|
279
|
+
return /^[a-zA-Z]*$/.test(s.slice(i + 1));
|
|
280
|
+
}
|
|
281
|
+
function barePatternRefusal(value) {
|
|
282
|
+
if (value.filters.length > 0 || value.tag !== "const") return null;
|
|
283
|
+
if (isValidPcreLiteral(value.value)) return null;
|
|
284
|
+
return `${JSON.stringify(value.value)} is a BARE regex pattern. PHP \`preg_*\` reads the first character as the delimiter, so the engine matches nothing for every input \u2014 the assertion or precondition built on it fails (or passes) against text that plainly should not. Build it with c.regex(${JSON.stringify(value.value)}) instead of c.text(...), which wraps and escapes it into a valid PCRE literal`;
|
|
285
|
+
}
|
|
286
|
+
var REGEX_PATTERN_FILTERS = /* @__PURE__ */ new Set([
|
|
287
|
+
"regex_test",
|
|
288
|
+
"regex_match",
|
|
289
|
+
"regex_match_all",
|
|
290
|
+
"regex_matches",
|
|
291
|
+
"regex_replace",
|
|
292
|
+
"regex_get_all_matches",
|
|
293
|
+
"regex_get_first_match"
|
|
294
|
+
]);
|
|
295
|
+
function intLiteral(n) {
|
|
296
|
+
if (typeof n === "bigint") return String(n);
|
|
297
|
+
if (typeof n === "string") {
|
|
298
|
+
if (!/^[+-]?\d+$/.test(n)) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`c.int() takes an integer literal as a string (digits, optional sign), got ${JSON.stringify(n)}. For a fractional number use c.decimal(), and for the editor's empty value box use c.blank("const:int").`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return n;
|
|
304
|
+
}
|
|
305
|
+
if (!Number.isInteger(n)) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`c.int() takes an integer, got ${n}. Use c.decimal(${n}) for a fractional number.`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
if (!Number.isSafeInteger(n)) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
`c.int() was given ${n}, which is past Number.MAX_SAFE_INTEGER \u2014 the literal has already lost precision before reaching here. Pass it as a string to store it exactly: c.int("${BigInt(n).toString()}") if that is the number you meant.`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
return String(n);
|
|
316
|
+
}
|
|
317
|
+
var c = {
|
|
318
|
+
/**
|
|
319
|
+
* Plain string constant → `tag:"const"`.
|
|
320
|
+
*
|
|
321
|
+
* `null` is accepted alongside a string because the engine stores both: 47
|
|
322
|
+
* values in the sweep are a bare `const` holding `null` rather than `""`,
|
|
323
|
+
* mostly ignored statement-input entries the engine never reads. They are
|
|
324
|
+
* distinct bytes — `normalize` keeps them apart, unlike the `const:obj` blanks
|
|
325
|
+
* it does canonicalize — so a pull has to be able to spell the null form, and
|
|
326
|
+
* `c.text(null)` did not type-check.
|
|
327
|
+
*
|
|
328
|
+
* Write `c.text("")` for an empty string. The null form is here so a pulled
|
|
329
|
+
* workspace round-trips, not to be authored.
|
|
330
|
+
*/
|
|
331
|
+
text(s) {
|
|
332
|
+
return val(s, "const");
|
|
333
|
+
},
|
|
334
|
+
/**
|
|
335
|
+
* Integer constant → `tag:"const:int"`, value stringified (e.g. `"123"`).
|
|
336
|
+
*
|
|
337
|
+
* Pass a **string** or `bigint` for an integer outside JavaScript's safe
|
|
338
|
+
* range. The engine stores integers as strings and has no such limit, so
|
|
339
|
+
* `c.int("18446744073709551615")` — 2^64 − 1, what a bitwise NOT returns —
|
|
340
|
+
* is exact, where the number literal for it silently rounds to `…616`.
|
|
341
|
+
*
|
|
342
|
+
* A `number` that is not an integer, or is past `Number.MAX_SAFE_INTEGER`,
|
|
343
|
+
* **throws**: by the time such a literal reaches here its precision is already
|
|
344
|
+
* gone, so encoding it would write a value the caller did not type. The throw
|
|
345
|
+
* names the string form. (issue #153)
|
|
346
|
+
*/
|
|
347
|
+
int(n) {
|
|
348
|
+
return val(intLiteral(n), "const:int");
|
|
349
|
+
},
|
|
350
|
+
/**
|
|
351
|
+
* Decimal constant → `tag:"const:decimal"`.
|
|
352
|
+
*
|
|
353
|
+
* Pass a **string** to preserve a stored spelling a number literal cannot
|
|
354
|
+
* reproduce — `c.decimal("10.00")` keeps its trailing zeros, where
|
|
355
|
+
* `c.decimal(10)` writes `"10"`. The engine stores decimals as strings either
|
|
356
|
+
* way, so this is exactness rather than a workaround; prefer the number form
|
|
357
|
+
* whenever it reproduces the value you want.
|
|
358
|
+
*/
|
|
359
|
+
decimal(n) {
|
|
360
|
+
return val(String(n), "const:decimal");
|
|
361
|
+
},
|
|
362
|
+
/**
|
|
363
|
+
* The editor's **unconfigured value box** — a value cell added and never
|
|
364
|
+
* filled in, stored as `{value: "", tag}`.
|
|
365
|
+
*
|
|
366
|
+
* This is not a zero, an empty string, or an empty collection. The engine
|
|
367
|
+
* reads `""` and `"0"` differently, so `c.blank("const:int")` and `c.int(0)`
|
|
368
|
+
* are different stored values and the SDK will not canonicalize one into the
|
|
369
|
+
* other. It exists because 13 real values in the survey corpus are in this
|
|
370
|
+
* state and had no authoring form, so a pull emitted them as annotated
|
|
371
|
+
* literals with a warning attached — describing a workspace that was fine.
|
|
372
|
+
*
|
|
373
|
+
* Constant tags only. A blank `var` or `input` is an unbound REFERENCE, which
|
|
374
|
+
* is a different defect with a different fix, and is deliberately not
|
|
375
|
+
* spellable here. `const` and `const:obj` are excluded too: they already have
|
|
376
|
+
* exact blank forms in `c.text("")` and `c.obj(null)`, and a second spelling
|
|
377
|
+
* for the same bytes is how two constructors start disagreeing.
|
|
378
|
+
*/
|
|
379
|
+
blank(tag) {
|
|
380
|
+
return val("", tag);
|
|
381
|
+
},
|
|
382
|
+
/** Boolean constant → `"true"`/`"false"` with `tag:"const:bool"`. */
|
|
383
|
+
bool(b) {
|
|
384
|
+
return val(b ? "true" : "false", "const:bool");
|
|
385
|
+
},
|
|
386
|
+
/**
|
|
387
|
+
* Null constant → value `"null"` (per engine fixture).
|
|
388
|
+
*
|
|
389
|
+
* Bare `c.null()` is the plain `const:null`. Pass a tag for the **object-typed
|
|
390
|
+
* null** the engine writes into a `db.*` statement's `@meta` slot —
|
|
391
|
+
* `c.null("const:obj")` → `{tag:"const:obj", value:"null"}`, 16 of them across
|
|
392
|
+
* eight ordinary queries in the survey corpus, which had no spelling and came
|
|
393
|
+
* back as `rawValue` (issue #151).
|
|
394
|
+
*
|
|
395
|
+
* ⚠ Not the same bytes as `c.obj(null)`, which is the BLANK object
|
|
396
|
+
* (`value: ""`). Both evaluate to null — the engine JSON-decodes the stored
|
|
397
|
+
* string and `""` and `"null"` both decode to it — but they are two stored
|
|
398
|
+
* spellings, and the SDK writes back the one it was given rather than
|
|
399
|
+
* re-pointing a pulled workspace at the other.
|
|
400
|
+
*/
|
|
401
|
+
null(tag = "const:null") {
|
|
402
|
+
return val("null", tag);
|
|
403
|
+
},
|
|
404
|
+
/**
|
|
405
|
+
* Build a **regex pattern value** for the pattern-piped regex filters
|
|
406
|
+
* (`fl.regex_test`/`regex_match`/`regex_replace`/…). Xano runs PHP `preg_*`, so
|
|
407
|
+
* the pattern MUST be delimiter-wrapped — a bare `c.text("^…$")` is an invalid
|
|
408
|
+
* PCRE and the filter then matches *nothing* for every input, so a precondition
|
|
409
|
+
* built on it silently rejects all values (issue #128). This wraps the raw
|
|
410
|
+
* pattern in `/…/`, escapes any interior `/`, and appends `flags`, so the result
|
|
411
|
+
* is always valid; `withFilters` rejects a bare `c.text` pattern and points here.
|
|
412
|
+
*
|
|
413
|
+
* Pass a JS `RegExp` (source + flags used directly, minus JS-only `g`/`y`/`d`):
|
|
414
|
+
* `c.regex(/^[^@\s]+@[^@\s]+\.[^@\s]+$/i)`, or a raw body + optional PCRE flags:
|
|
415
|
+
* `c.regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", "i")`. The pattern is the piped
|
|
416
|
+
* value; the filter's `subject` arg is the text tested against it.
|
|
417
|
+
*/
|
|
418
|
+
regex(pattern, flags) {
|
|
419
|
+
const body = typeof pattern === "string" ? pattern : pattern.source;
|
|
420
|
+
const raw2 = flags ?? (typeof pattern === "string" ? "" : [...pattern.flags].filter((ch) => PCRE_JS_FLAGS.includes(ch)).join(""));
|
|
421
|
+
if (!/^[a-zA-Z]*$/.test(raw2)) {
|
|
422
|
+
throw new Error(`c.regex: flags must be letters (e.g. "i", "im"), got ${JSON.stringify(raw2)}`);
|
|
423
|
+
}
|
|
424
|
+
return val(`/${escapeRegexSlashes(body)}/${raw2}`, "const");
|
|
425
|
+
},
|
|
426
|
+
/**
|
|
427
|
+
* Current time as an **epoch-milliseconds** value — the engine's native
|
|
428
|
+
* `const:epochms` constant. Xano has no `$now` *setting*, so this lives on
|
|
429
|
+
* `c.*` (a current-time literal) rather than {@link sys} (which emits
|
|
430
|
+
* `setting("$…")` and would reference a dead `$now` var).
|
|
431
|
+
*
|
|
432
|
+
* Emits `{tag:"const:epochms", value:"now"}` — an UNFILTERED value, valid
|
|
433
|
+
* inline as a `where`/`cmp` operand. It previously emitted the equivalent
|
|
434
|
+
* `text("now") |to_epoch_ms` chain; both evaluate to the same epoch-ms number
|
|
435
|
+
* on a live engine, and both persist verbatim, but the native tag is what the
|
|
436
|
+
* editor writes and needs no filter to get there. Chain math onto it as usual:
|
|
437
|
+
* `withFilters(c.now(), fl.epochms_add_ms(c.int(-maxAgeMs)))`. (issues #120,
|
|
438
|
+
* #145)
|
|
439
|
+
*/
|
|
440
|
+
now() {
|
|
441
|
+
return val("now", "const:epochms");
|
|
442
|
+
},
|
|
443
|
+
/**
|
|
444
|
+
* Object constant → JSON-string value with `tag:"const:obj"`. Takes **plain
|
|
445
|
+
* JSON literals only**: nesting a tagged value (`inp`/`ref`/`auth`/`c.*`) is a
|
|
446
|
+
* compile error and throws at runtime — it would serialize as internal
|
|
447
|
+
* representation the engine can't decode. For a computed/multi-key object
|
|
448
|
+
* response use a record of values (`response: { key: value }`), not `c.obj`.
|
|
449
|
+
* See issue #42.
|
|
450
|
+
*
|
|
451
|
+
* Called with no argument it is the **empty object**, `{}` — the same default
|
|
452
|
+
* the editor gives a new object variable, and the only empty form current
|
|
453
|
+
* editors write.
|
|
454
|
+
*
|
|
455
|
+
* Called with an explicit `null` it is the **blank** form (stored `value: ""`),
|
|
456
|
+
* which the engine evaluates to `null` rather than `{}` — it JSON-decodes the
|
|
457
|
+
* stored string, and decoding `""` yields null. That is a real difference in
|
|
458
|
+
* what the statement sees, so the two are separate spellings rather than one
|
|
459
|
+
* "empty object": `c.obj()` is `{}`, `c.obj(null)` is null.
|
|
460
|
+
*
|
|
461
|
+
* ⚠ **Prefer `c.obj()`.** The blank form is legacy — no current editor path
|
|
462
|
+
* writes it — and it exists here so a pulled workspace round-trips to the
|
|
463
|
+
* same bytes instead of being quietly re-pointed at `{}`.
|
|
464
|
+
*
|
|
465
|
+
* A **populated** object is stored the way the editor stores one: an empty
|
|
466
|
+
* `{}` base carrying one `set` filter per key (issue #248). It is NOT a
|
|
467
|
+
* populated JSON string — that form fails the request at runtime, see
|
|
468
|
+
* {@link objSetFilters}.
|
|
469
|
+
*
|
|
470
|
+
* ⚠ **Zero-based numeric keys come back as a LIST**, not an object:
|
|
471
|
+
* `c.obj({ "0": "a" })` evaluates to `["a"]`. That is the engine's data model
|
|
472
|
+
* — a numeric key IS an index there, and the same object decoded from JSON
|
|
473
|
+
* anywhere else behaves identically — not something this encoding introduces.
|
|
474
|
+
* A non-zero-based numeric key (`{ "2": … }`) survives as a key. Live-verified.
|
|
475
|
+
*/
|
|
476
|
+
obj(o) {
|
|
477
|
+
if (o === null) return val("", "const:obj");
|
|
478
|
+
const j = o ?? {};
|
|
479
|
+
assertPlainJson(j);
|
|
480
|
+
if (!isPlainRecord(j)) return val(JSON.stringify(j), "const:obj");
|
|
481
|
+
warnZeroBasedNumericKeys(j);
|
|
482
|
+
return val("{}", "const:obj", objSetFilters(j));
|
|
483
|
+
},
|
|
484
|
+
/**
|
|
485
|
+
* Array constant → JSON-string value with `tag:"const:array"`. Takes **plain
|
|
486
|
+
* JSON literals only** — like {@link obj}, a nested tagged value is a compile
|
|
487
|
+
* error and throws at runtime. See issue #42.
|
|
488
|
+
*/
|
|
489
|
+
array(a) {
|
|
490
|
+
assertPlainJson(a);
|
|
491
|
+
return val(JSON.stringify(a), "const:array");
|
|
492
|
+
},
|
|
493
|
+
/**
|
|
494
|
+
* A **Xano Expression Engine** expression, passed through verbatim →
|
|
495
|
+
* `tag:"const:expr2"`. The string IS the expression, exactly as it would be
|
|
496
|
+
* typed into the expression editor:
|
|
497
|
+
*
|
|
498
|
+
* c.expression('"Hello, " ~ $input.name')
|
|
499
|
+
* c.expression("$var.price * $var.qty")
|
|
500
|
+
* c.expression("{ id: $var.user.id, tier: $var.plan }")
|
|
501
|
+
*
|
|
502
|
+
* ⚠️ **THE STRING IS NOT VALIDATED.** XanoTS does not parse it, does not
|
|
503
|
+
* type-check it, and cannot tell a working expression from a typo — the whole
|
|
504
|
+
* string is handed to the engine as-is. Nothing here participates in
|
|
505
|
+
* `InferResponse`, so a var referenced inside it is invisible to the type
|
|
506
|
+
* system, and a rename that updates every typed `ref()` will NOT update this.
|
|
507
|
+
* A malformed expression surfaces at RUNTIME, and one that is merely wrong
|
|
508
|
+
* (`$var.tota1`) surfaces as a wrong answer, not an error. Play at your own
|
|
509
|
+
* risk until validation exists.
|
|
510
|
+
*
|
|
511
|
+
* Prefer the typed surfaces whenever they cover the case: `ref`/`inp`/`col`
|
|
512
|
+
* for references, `withFilters(..., fl.*)` for transforms, {@link obj} for a
|
|
513
|
+
* dynamic object (it BUILDS a checked expression for you). Reach for this only
|
|
514
|
+
* for expression-engine syntax the typed surfaces cannot express — string
|
|
515
|
+
* concatenation with `~`, inline arithmetic, conditionals.
|
|
516
|
+
*
|
|
517
|
+
* NOT the `expr()` condition builder. `expr(col("id"), "=", inp("id"))` builds
|
|
518
|
+
* a comparison for a `where`; this builds a VALUE from raw expression source.
|
|
519
|
+
*/
|
|
520
|
+
expression(source) {
|
|
521
|
+
assertExpressionSource(source, "c.expression");
|
|
522
|
+
return val(source, "const:expr2");
|
|
523
|
+
},
|
|
524
|
+
/**
|
|
525
|
+
* The **older** expression form → `tag:"const:expr"`, kept because real
|
|
526
|
+
* workspaces still hold values stored that way and codegen has to bring them
|
|
527
|
+
* back as something readable. New code wants {@link expression}, which is what
|
|
528
|
+
* the expression editor writes today.
|
|
529
|
+
*
|
|
530
|
+
* ⚠️ Unvalidated passthrough, exactly like {@link expression} — see the
|
|
531
|
+
* warning there. This one additionally uses the older syntax generation, so an
|
|
532
|
+
* expression copied out of the current editor may not mean the same thing
|
|
533
|
+
* here.
|
|
534
|
+
*/
|
|
535
|
+
expressionLegacy(source) {
|
|
536
|
+
assertExpressionSource(source, "c.expressionLegacy");
|
|
537
|
+
return val(source, "const:expr");
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
function assertExpressionSource(source, fn2) {
|
|
541
|
+
if (typeof source !== "string") {
|
|
542
|
+
const tagged = isTaggedValue(source);
|
|
543
|
+
throw new Error(
|
|
544
|
+
`${fn2}() takes the expression SOURCE as a string, got ${tagged ? "a Value" : typeof source}. ` + (tagged ? `To reference a value inside an expression, write its path into the string (e.g. ${fn2}("$var.total * 2")). For a comparison in a where/condition use expr(left, op, right) or cmp(...), not ${fn2}().` : `Pass expression-engine source, e.g. ${fn2}('"Hi, " ~ $input.name').`)
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
if (source === "") {
|
|
548
|
+
throw new Error(
|
|
549
|
+
`${fn2}() was given an empty string, which the engine cannot evaluate. Pass expression source, or use c.text("") for an empty string constant.`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function ref(name, opts) {
|
|
554
|
+
const dot = name.indexOf(".");
|
|
555
|
+
if (opts?.safe && dot !== -1) {
|
|
556
|
+
const base = name.slice(0, dot);
|
|
557
|
+
const path = name.slice(dot + 1);
|
|
558
|
+
return withFilters(val(base, "var"), filter("get", c.text(path), c.null()));
|
|
559
|
+
}
|
|
560
|
+
return val(name, "var");
|
|
561
|
+
}
|
|
562
|
+
function assertNoRefOptions(creator, opts) {
|
|
563
|
+
if (opts === void 0) return;
|
|
564
|
+
const safe = typeof opts === "object" && opts !== null && "safe" in opts;
|
|
565
|
+
throw new Error(
|
|
566
|
+
`${creator}() takes only a name \u2014 ${safe ? "`safe`" : "a second argument"} is a ref() option and was silently dropped here before this guard (issue #80). Null-safe drilling into a dotted path is implemented on ref() alone: \`ref("owner.id", { safe: true })\` compiles to \`$owner|get:"id"\`, which yields null instead of 500ing when the base is null. To drill safely from ${creator}(), bind it to a variable first (\`s.set_var\`) and ref() that.`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
function inp(name, opts) {
|
|
570
|
+
assertNoRefOptions("inp", opts);
|
|
571
|
+
return val(name, "input");
|
|
572
|
+
}
|
|
573
|
+
function col(name, opts) {
|
|
574
|
+
assertNoRefOptions("col", opts);
|
|
575
|
+
return val(name, "col");
|
|
576
|
+
}
|
|
577
|
+
function auth(path = "", opts) {
|
|
578
|
+
assertNoRefOptions("auth", opts);
|
|
579
|
+
return val(path, "auth");
|
|
580
|
+
}
|
|
581
|
+
function drill(base, path) {
|
|
582
|
+
if (path === "") return base;
|
|
583
|
+
return path.split(".").reduce((out2, seg) => BARE_SET_PATH.test(seg) ? `${out2}.${seg}` : out2 + setPath(seg), base);
|
|
584
|
+
}
|
|
585
|
+
function resp(path = "", opts) {
|
|
586
|
+
assertNoRefOptions("resp", opts);
|
|
587
|
+
return val(drill("response", path), "response");
|
|
588
|
+
}
|
|
589
|
+
function caught(path = "", opts) {
|
|
590
|
+
assertNoRefOptions("caught", opts);
|
|
591
|
+
return val(path, "trycatch");
|
|
592
|
+
}
|
|
593
|
+
function isToolsetPath(value) {
|
|
594
|
+
return value === "token" || value === "params" || value.startsWith("params.");
|
|
595
|
+
}
|
|
596
|
+
function toolset(path, opts) {
|
|
597
|
+
assertNoRefOptions("toolset", opts);
|
|
598
|
+
return val(path, "toolset");
|
|
599
|
+
}
|
|
600
|
+
function env(name, opts) {
|
|
601
|
+
assertNoRefOptions("env", opts);
|
|
602
|
+
return val(name, "setting");
|
|
603
|
+
}
|
|
604
|
+
function setting(name, opts) {
|
|
605
|
+
assertNoRefOptions("setting", opts);
|
|
606
|
+
return val(name, "setting");
|
|
607
|
+
}
|
|
608
|
+
var sys = {
|
|
609
|
+
/** Client IP address (`$remote_ip`, text). Best public-endpoint rate-limit key. */
|
|
610
|
+
remoteIp: () => val("$remote_ip", "setting"),
|
|
611
|
+
/** HTTP method of the request — `GET`, `POST`, … (`$request_method`, text). */
|
|
612
|
+
requestMethod: () => val("$request_method", "setting"),
|
|
613
|
+
/** Full request URI/path (`$request_uri`, text). */
|
|
614
|
+
requestUri: () => val("$request_uri", "setting"),
|
|
615
|
+
/** Raw query-string portion of the URL (`$request_querystring`, text). */
|
|
616
|
+
requestQueryString: () => val("$request_querystring", "setting"),
|
|
617
|
+
/** Request headers as an object/map (`$http_headers`, object). */
|
|
618
|
+
httpHeaders: () => val("$http_headers", "setting"),
|
|
619
|
+
/** The caller's `Authorization` bearer token, if present (`$request_auth_token`, text). */
|
|
620
|
+
requestAuthToken: () => val("$request_auth_token", "setting"),
|
|
621
|
+
/** API base URL for the request (`$api_baseurl`, text). */
|
|
622
|
+
apiBaseUrl: () => val("$api_baseurl", "setting"),
|
|
623
|
+
/** Active data source name — e.g. `live` or a branch source (`$datasource`, text). */
|
|
624
|
+
datasource: () => val("$datasource", "setting"),
|
|
625
|
+
/** Active branch name (`$branch`, text). */
|
|
626
|
+
branch: () => val("$branch", "setting"),
|
|
627
|
+
/** Tenant identifier for multi-tenant instances (`$tenant`, text). */
|
|
628
|
+
tenant: () => val("$tenant", "setting"),
|
|
629
|
+
/** Current release number (`$release`, int). */
|
|
630
|
+
release: () => val("$release", "setting"),
|
|
631
|
+
/** Platform identifier (`$platform`, int). */
|
|
632
|
+
platform: () => val("$platform", "setting"),
|
|
633
|
+
/** `true` when the request is running under the debugger (`$debugger`, bool). */
|
|
634
|
+
isDebugger: () => val("$debugger", "setting")
|
|
635
|
+
};
|
|
636
|
+
function out(name, opts) {
|
|
637
|
+
assertNoRefOptions("out", opts);
|
|
638
|
+
return val(name, "output");
|
|
639
|
+
}
|
|
640
|
+
function filter(name, ...args) {
|
|
641
|
+
const last = args.reduce((acc, a, i) => a !== void 0 ? i : acc, -1);
|
|
642
|
+
const hole = args.slice(0, last).findIndex((a) => a === void 0);
|
|
643
|
+
if (hole !== -1) {
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Filter \`${name}\`: argument ${hole + 1} is omitted but argument ${last + 1} is supplied. Filter arguments are positional, so an omitted one in the middle would shift every argument after it into the wrong slot. Pass a value for argument ${hole + 1}, or use the named form \u2014 \`fl.${name}({ \u2026 })\` \u2014 which cannot mis-slot. (issue #221)`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
assertLambdaFilterArgs(name, args);
|
|
649
|
+
assertExpressionFilterArgs(name, args);
|
|
650
|
+
assertEnumFilterArgs(name, args);
|
|
651
|
+
return { name, disabled: false, arg: args.filter((a) => a !== void 0) };
|
|
652
|
+
}
|
|
653
|
+
function withFilters(value, ...filters) {
|
|
654
|
+
const added = filters.flat(Infinity);
|
|
655
|
+
const malformed = added.findIndex(
|
|
656
|
+
(f2) => f2 === null || typeof f2 !== "object" || typeof f2.name !== "string"
|
|
657
|
+
);
|
|
658
|
+
if (malformed !== -1) {
|
|
659
|
+
const got = added[malformed];
|
|
660
|
+
throw new Error(
|
|
661
|
+
`withFilters: argument ${malformed} of the filter chain is not a filter \u2014 got ${JSON.stringify(got) ?? String(got)}. Each entry must come from an \`fl.*\` factory (or \`filter(name, ...)\`), which returns \`{name, arg, disabled}\`. A tagged value is not a filter: to pipe one value through another, pass it as a filter ARGUMENT (\`fl.concat(other)\`), not as a chain member.`
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
const pipedIsBarePattern = barePatternRefusal(value) !== null;
|
|
665
|
+
const offending = pipedIsBarePattern ? added.find((filter2) => REGEX_PATTERN_FILTERS.has(filter2.name)) : void 0;
|
|
666
|
+
if (offending) {
|
|
667
|
+
const escapeHatch = added[0] === offending ? "" : ` If this chain BUILDS the pattern rather than normalizing it, apply the prefix in its own \`withFilters(...)\` call \u2014 the guard only inspects an unfiltered const, so a filtered base passes through untouched.`;
|
|
668
|
+
throw new Error(
|
|
669
|
+
`Regex filter \`${offending.name}\` is pattern-piped: the value it filters is the regex PATTERN, which PHP \`preg_*\` requires to be delimiter-wrapped. ${JSON.stringify(value.value)} is a bare pattern, so the engine matches nothing for every input (a precondition on it silently rejects all values). Build it with c.regex(${JSON.stringify(value.value)}) instead of c.text(...).${escapeHatch} (issue #128)`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
return { ...value, filters: [...value.filters, ...added] };
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/values/lambda.ts
|
|
676
|
+
var LAMBDA_BINDINGS = {
|
|
677
|
+
// Runs once, over no element and no piped value.
|
|
678
|
+
"s.lambda": ["$env", "$input", "$var", "$auth"],
|
|
679
|
+
// Runs once, over the piped value — which it binds as `$this` (NOT `$parent`).
|
|
680
|
+
"fl.lambda": ["$env", "$input", "$var", "$auth", "$this"],
|
|
681
|
+
// The array-iterating filters: one run per element.
|
|
682
|
+
map: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
683
|
+
filter: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
684
|
+
some: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
685
|
+
every: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
686
|
+
find: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
687
|
+
findIndex: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
688
|
+
// …plus the accumulator, which is `$result`. This is issue #221.
|
|
689
|
+
reduce: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent", "$result"]
|
|
690
|
+
};
|
|
691
|
+
var LAMBDA_GLOBALS = ["console", "crypto"];
|
|
692
|
+
var LAMBDA_MODULE_GLOBALS = [
|
|
693
|
+
"_",
|
|
694
|
+
"aws4",
|
|
695
|
+
"axios",
|
|
696
|
+
"cryptojs",
|
|
697
|
+
"DateTime",
|
|
698
|
+
"ethers",
|
|
699
|
+
"fastXmlParser",
|
|
700
|
+
"jose",
|
|
701
|
+
"luxon",
|
|
702
|
+
"mailparser",
|
|
703
|
+
"math",
|
|
704
|
+
"moment",
|
|
705
|
+
"nodemailer",
|
|
706
|
+
"socks",
|
|
707
|
+
"uuid",
|
|
708
|
+
"utils"
|
|
709
|
+
];
|
|
710
|
+
var LAMBDA_CODE_FILTERS = {
|
|
711
|
+
lambda: { slot: 0, surface: "fl.lambda" },
|
|
712
|
+
map: { slot: 0, surface: "map" },
|
|
713
|
+
filter: { slot: 0, surface: "filter" },
|
|
714
|
+
some: { slot: 0, surface: "some" },
|
|
715
|
+
every: { slot: 0, surface: "every" },
|
|
716
|
+
find: { slot: 0, surface: "find" },
|
|
717
|
+
findIndex: { slot: 0, surface: "findIndex" },
|
|
718
|
+
reduce: { slot: 1, surface: "reduce" }
|
|
719
|
+
};
|
|
720
|
+
function assertLambdaFilterArgs(name, args) {
|
|
721
|
+
const site = LAMBDA_CODE_FILTERS[name];
|
|
722
|
+
if (site === void 0) return;
|
|
723
|
+
const code = args[site.slot];
|
|
724
|
+
if (!isInspectableBody(code) || code.value.trim() === "") return;
|
|
725
|
+
assertLambdaBody(code.value, site.surface, `fl.${name}`);
|
|
726
|
+
}
|
|
727
|
+
var LAMBDA_STATEMENTS = {
|
|
728
|
+
"mvp:lambda": { field: "code", surface: "s.lambda" }
|
|
729
|
+
};
|
|
730
|
+
function coerceLambdaFields(storedName, authored) {
|
|
731
|
+
const site = LAMBDA_STATEMENTS[storedName];
|
|
732
|
+
if (site === void 0 || typeof authored[site.field] !== "function") return authored;
|
|
733
|
+
return { ...authored, [site.field]: toLambdaValue(authored[site.field], site.surface, storedName === "mvp:lambda" ? "s.lambda" : storedName) };
|
|
734
|
+
}
|
|
735
|
+
function assertLambdaStatement(storedName, authored) {
|
|
736
|
+
const site = LAMBDA_STATEMENTS[storedName];
|
|
737
|
+
if (site === void 0) return;
|
|
738
|
+
const code = authored[site.field];
|
|
739
|
+
if (!isInspectableBody(code) || code.value.trim() === "") return;
|
|
740
|
+
assertLambdaBody(code.value, site.surface, "s.lambda");
|
|
741
|
+
}
|
|
742
|
+
function isInspectableBody(code) {
|
|
743
|
+
return typeof code === "object" && code !== null && code.tag === "const" && typeof code.value === "string" && (code.filters?.length ?? 0) === 0;
|
|
744
|
+
}
|
|
745
|
+
function toLambdaValue(x, surface, source) {
|
|
746
|
+
if (typeof x !== "function") return x;
|
|
747
|
+
return lambdaValue(extractFunctionBody(String(x), source), { surface }, source);
|
|
748
|
+
}
|
|
749
|
+
var PREFIX = "lam";
|
|
750
|
+
function extractFunctionBody(source, caller) {
|
|
751
|
+
const src = source.trim();
|
|
752
|
+
if (src.includes("[native code]")) {
|
|
753
|
+
throw new Error(
|
|
754
|
+
`${caller}: this function has no readable source (it is native, bound, or otherwise not authored here), so there is no body to send to the engine. Write the body inline as an arrow function, or use lam.raw(...) with the code as text. (issue #221)`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
const mask = maskNonCode(src);
|
|
758
|
+
const kw = /^(?:async\s+)?function\b/.exec(src);
|
|
759
|
+
if (kw) {
|
|
760
|
+
const open = mask.indexOf("{", closingParen(mask, kw[0].length));
|
|
761
|
+
const close = mask.lastIndexOf("}");
|
|
762
|
+
if (open === -1 || close <= open) {
|
|
763
|
+
throw new Error(`${caller}: could not find the function body in ${JSON.stringify(src.slice(0, 80))}.`);
|
|
764
|
+
}
|
|
765
|
+
assertDestructuredParams(src.slice(kw[0].length, open), mask.slice(open + 1, close), caller);
|
|
766
|
+
return stripKeepNames(dedent(src.slice(open + 1, close).trim()));
|
|
767
|
+
}
|
|
768
|
+
const arrow = topLevelArrow(mask);
|
|
769
|
+
if (arrow === -1) {
|
|
770
|
+
throw new Error(
|
|
771
|
+
`${caller}: expected an arrow function or a function expression, got ${JSON.stringify(src.slice(0, 80))}. A class method or an object shorthand method does not extract; write it as an arrow function.`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
assertDestructuredParams(src.slice(0, arrow), mask.slice(arrow + 2), caller);
|
|
775
|
+
const rest = src.slice(arrow + 2).trim();
|
|
776
|
+
const restMask = mask.slice(arrow + 2).trim();
|
|
777
|
+
if (restMask.startsWith("{")) {
|
|
778
|
+
const close = restMask.lastIndexOf("}");
|
|
779
|
+
if (close === -1) throw new Error(`${caller}: unbalanced braces in the function body.`);
|
|
780
|
+
return stripKeepNames(dedent(rest.slice(1, close).trim()));
|
|
781
|
+
}
|
|
782
|
+
return stripKeepNames(`return ${rest.replace(/[;,]\s*$/, "")};`);
|
|
783
|
+
}
|
|
784
|
+
function stripKeepNames(body) {
|
|
785
|
+
let out2 = body;
|
|
786
|
+
for (; ; ) {
|
|
787
|
+
const mask = maskNonCode(out2);
|
|
788
|
+
const call = /(^|[^A-Za-z0-9_$.])__name\s*\(/.exec(mask);
|
|
789
|
+
if (call === null) break;
|
|
790
|
+
const start = call.index + (call[1] ?? "").length;
|
|
791
|
+
const open = mask.indexOf("(", start);
|
|
792
|
+
const close = closingParen(mask, start) - 1;
|
|
793
|
+
if (close <= open) break;
|
|
794
|
+
const args = out2.slice(open + 1, close);
|
|
795
|
+
const comma = splitTopLevel(maskNonCode(args))[0]?.length ?? args.length;
|
|
796
|
+
const first = args.slice(0, comma).trim();
|
|
797
|
+
const before = mask.slice(0, start);
|
|
798
|
+
const isStatement = /(^|[;{}])\s*$/.test(before) && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(first);
|
|
799
|
+
const trailing = isStatement ? /^\s*;?/.exec(out2.slice(close + 1))?.[0].length ?? 0 : 0;
|
|
800
|
+
out2 = out2.slice(0, start) + (isStatement ? "" : first) + out2.slice(close + 1 + trailing);
|
|
801
|
+
}
|
|
802
|
+
return out2.trim();
|
|
803
|
+
}
|
|
804
|
+
function dedent(body) {
|
|
805
|
+
if (body.includes("`")) return body;
|
|
806
|
+
const lines = body.split("\n");
|
|
807
|
+
const indents = lines.slice(1).filter((l) => l.trim() !== "").map((l) => /^[ \t]*/.exec(l)?.[0].length ?? 0);
|
|
808
|
+
const common = indents.length ? Math.min(...indents) : 0;
|
|
809
|
+
if (common === 0) return body;
|
|
810
|
+
return [lines[0], ...lines.slice(1).map((l) => l.slice(common))].join("\n");
|
|
811
|
+
}
|
|
812
|
+
function assertDestructuredParams(params, bodyMask, caller) {
|
|
813
|
+
const inner = /\(([\s\S]*)\)/.exec(params)?.[1] ?? params;
|
|
814
|
+
for (const part of splitTopLevel(inner)) {
|
|
815
|
+
const name = /^\s*(?:\.\.\.)?\s*([A-Za-z_$][A-Za-z0-9_$]*)/.exec(part)?.[1];
|
|
816
|
+
if (name === void 0) continue;
|
|
817
|
+
const referenced = new RegExp(`(^|[^A-Za-z0-9_$.])${name.replace(/\$/g, "\\$")}\\b`).test(bodyMask);
|
|
818
|
+
if (!referenced) continue;
|
|
819
|
+
throw new Error(
|
|
820
|
+
`${caller}: the body reads \`${name}\`, but a lambda's parameters are not real \u2014 only the BODY is sent, and the engine injects the bindings as free identifiers. DESTRUCTURE them instead: \`({ ${name === "b" ? "$this" : name} }) => \u2026\` rather than \`(${name}) => \u2026 ${name}.$this \u2026\`. As written, \`${name}\` is undefined at runtime and the engine returns that failure as text in the value slot. (issue #221)`
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
function splitTopLevel(text) {
|
|
825
|
+
const parts = [];
|
|
826
|
+
let depth = 0;
|
|
827
|
+
let start = 0;
|
|
828
|
+
for (let i = 0; i < text.length; i++) {
|
|
829
|
+
const ch = text[i];
|
|
830
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
831
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
832
|
+
else if (ch === "," && depth === 0) {
|
|
833
|
+
parts.push(text.slice(start, i));
|
|
834
|
+
start = i + 1;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
parts.push(text.slice(start));
|
|
838
|
+
return parts;
|
|
839
|
+
}
|
|
840
|
+
function closingParen(mask, from) {
|
|
841
|
+
const open = mask.indexOf("(", from);
|
|
842
|
+
if (open === -1) return from;
|
|
843
|
+
let depth = 0;
|
|
844
|
+
for (let i = open; i < mask.length; i++) {
|
|
845
|
+
if (mask[i] === "(") depth++;
|
|
846
|
+
else if (mask[i] === ")" && --depth === 0) return i + 1;
|
|
847
|
+
}
|
|
848
|
+
return from;
|
|
849
|
+
}
|
|
850
|
+
function topLevelArrow(mask) {
|
|
851
|
+
let depth = 0;
|
|
852
|
+
for (let i = 0; i < mask.length - 1; i++) {
|
|
853
|
+
const ch = mask[i] ?? "";
|
|
854
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
855
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
856
|
+
else if (depth === 0 && ch === "=" && mask[i + 1] === ">") return i;
|
|
857
|
+
}
|
|
858
|
+
return -1;
|
|
859
|
+
}
|
|
860
|
+
function maskNonCode(src) {
|
|
861
|
+
const out2 = [];
|
|
862
|
+
const templates = [];
|
|
863
|
+
let i = 0;
|
|
864
|
+
let prevSignificant = "";
|
|
865
|
+
let prevWord = "";
|
|
866
|
+
const at = (k) => src[k] ?? "";
|
|
867
|
+
const top = () => templates[templates.length - 1] ?? 0;
|
|
868
|
+
const setTop = (v2) => {
|
|
869
|
+
templates[templates.length - 1] = v2;
|
|
870
|
+
};
|
|
871
|
+
const push = (ch, masked = false) => {
|
|
872
|
+
out2.push(masked ? ch === "\n" ? "\n" : " " : ch);
|
|
873
|
+
if (/\s/.test(ch)) return;
|
|
874
|
+
prevSignificant = ch;
|
|
875
|
+
if (!masked && /[A-Za-z0-9_$]/.test(ch)) prevWord += ch;
|
|
876
|
+
else prevWord = "";
|
|
877
|
+
};
|
|
878
|
+
const REGEX_PRECEDING_KEYWORDS = /* @__PURE__ */ new Set([
|
|
879
|
+
"return",
|
|
880
|
+
"typeof",
|
|
881
|
+
"case",
|
|
882
|
+
"in",
|
|
883
|
+
"of",
|
|
884
|
+
"new",
|
|
885
|
+
"delete",
|
|
886
|
+
"void",
|
|
887
|
+
"do",
|
|
888
|
+
"else",
|
|
889
|
+
"yield",
|
|
890
|
+
"await"
|
|
891
|
+
]);
|
|
892
|
+
while (i < src.length) {
|
|
893
|
+
const ch = at(i);
|
|
894
|
+
const next = at(i + 1);
|
|
895
|
+
if (templates.length > 0 && top() >= 0) {
|
|
896
|
+
if (ch === "{") setTop(top() + 1);
|
|
897
|
+
else if (ch === "}") {
|
|
898
|
+
setTop(top() - 1);
|
|
899
|
+
if (top() === 0) {
|
|
900
|
+
setTop(-1);
|
|
901
|
+
push(ch, true);
|
|
902
|
+
i++;
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (templates.length > 0 && top() === -1) {
|
|
908
|
+
if (ch === "\\") {
|
|
909
|
+
push(ch, true);
|
|
910
|
+
push(at(i + 1), true);
|
|
911
|
+
i += 2;
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
if (ch === "`") {
|
|
915
|
+
templates.pop();
|
|
916
|
+
push(ch, true);
|
|
917
|
+
i++;
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
if (ch === "$" && next === "{") {
|
|
921
|
+
setTop(1);
|
|
922
|
+
push(ch, true);
|
|
923
|
+
push("{", true);
|
|
924
|
+
i += 2;
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
push(ch, true);
|
|
928
|
+
i++;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
if (ch === "/" && next === "/") {
|
|
932
|
+
while (i < src.length && at(i) !== "\n") push(at(i++), true);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (ch === "/" && next === "*") {
|
|
936
|
+
push(at(i++), true);
|
|
937
|
+
push(at(i++), true);
|
|
938
|
+
while (i < src.length && !(at(i) === "*" && at(i + 1) === "/")) push(at(i++), true);
|
|
939
|
+
if (i < src.length) {
|
|
940
|
+
push(at(i++), true);
|
|
941
|
+
push(at(i++), true);
|
|
942
|
+
}
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
if (ch === '"' || ch === "'") {
|
|
946
|
+
push(ch, true);
|
|
947
|
+
i++;
|
|
948
|
+
while (i < src.length && at(i) !== ch) {
|
|
949
|
+
if (at(i) === "\\") push(at(i++), true);
|
|
950
|
+
if (i < src.length) push(at(i++), true);
|
|
951
|
+
}
|
|
952
|
+
if (i < src.length) push(at(i++), true);
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (ch === "`") {
|
|
956
|
+
templates.push(-1);
|
|
957
|
+
push(ch, true);
|
|
958
|
+
i++;
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (ch === "/" && (prevSignificant === "" || REGEX_PRECEDING_KEYWORDS.has(prevWord) || prevWord === "" && "(,=:[!&|?{};+-*%~^<>".includes(prevSignificant))) {
|
|
962
|
+
push(ch, true);
|
|
963
|
+
i++;
|
|
964
|
+
let closed = false;
|
|
965
|
+
while (i < src.length && at(i) !== "\n") {
|
|
966
|
+
if (at(i) === "\\") {
|
|
967
|
+
push(at(i++), true);
|
|
968
|
+
if (i < src.length) push(at(i++), true);
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
if (at(i) === "[") {
|
|
972
|
+
while (i < src.length && at(i) !== "]" && at(i) !== "\n") push(at(i++), true);
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
if (at(i) === "/") {
|
|
976
|
+
push(at(i++), true);
|
|
977
|
+
closed = true;
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
push(at(i++), true);
|
|
981
|
+
}
|
|
982
|
+
if (!closed) {
|
|
983
|
+
}
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
push(ch);
|
|
987
|
+
i++;
|
|
988
|
+
}
|
|
989
|
+
return out2.join("");
|
|
990
|
+
}
|
|
991
|
+
function declaredLocals(mask, pattern = /\$[A-Za-z_][A-Za-z0-9_]*/g) {
|
|
992
|
+
const declared = /* @__PURE__ */ new Set();
|
|
993
|
+
const add = (text) => {
|
|
994
|
+
for (const m of (text ?? "").matchAll(pattern)) declared.add(m[0]);
|
|
995
|
+
};
|
|
996
|
+
for (const m of mask.matchAll(/\b(?:const|let|var)\s+([^;\n]+)/g)) {
|
|
997
|
+
for (const declarator of (m[1] ?? "").split(",")) add(declarator.split("=")[0]);
|
|
998
|
+
}
|
|
999
|
+
for (const m of mask.matchAll(/\b(?:function|class)\s+(\$[A-Za-z0-9_]*)/g)) add(m[1]);
|
|
1000
|
+
for (const m of mask.matchAll(/\bcatch\s*\(([^)]*)\)/g)) add(m[1]);
|
|
1001
|
+
for (const m of mask.matchAll(/(\([^()]*\)|[A-Za-z_$][A-Za-z0-9_$]*)\s*=>/g)) add(m[1]);
|
|
1002
|
+
for (const m of mask.matchAll(/\bfunction\b[^(]*\(([^)]*)\)/g)) add(m[1]);
|
|
1003
|
+
return declared;
|
|
1004
|
+
}
|
|
1005
|
+
function dollarTokens(mask) {
|
|
1006
|
+
return tokens(mask, /(^|[^A-Za-z0-9_$.])(\$[A-Za-z_][A-Za-z0-9_]*)/g);
|
|
1007
|
+
}
|
|
1008
|
+
function tokens(mask, pattern, skipKeys = false) {
|
|
1009
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1010
|
+
const out2 = [];
|
|
1011
|
+
for (const m of mask.matchAll(pattern)) {
|
|
1012
|
+
const name = m[2] ?? "";
|
|
1013
|
+
if (name === "" || seen.has(name)) continue;
|
|
1014
|
+
if (skipKeys) {
|
|
1015
|
+
const before = mask.slice(0, m.index + (m[1] ?? "").length).trimEnd().slice(-1);
|
|
1016
|
+
const after = mask.slice(m.index + m[0].length).trimStart().slice(0, 1);
|
|
1017
|
+
if (after === ":" && (before === "{" || before === ",")) continue;
|
|
1018
|
+
}
|
|
1019
|
+
seen.add(name);
|
|
1020
|
+
out2.push(name);
|
|
1021
|
+
}
|
|
1022
|
+
return out2;
|
|
1023
|
+
}
|
|
1024
|
+
var BUNDLER_HELPERS = {
|
|
1025
|
+
__name: "a named inner function",
|
|
1026
|
+
__publicField: "a class field",
|
|
1027
|
+
__privateGet: "a #private field",
|
|
1028
|
+
__privateSet: "a #private field",
|
|
1029
|
+
__privateAdd: "a #private field",
|
|
1030
|
+
__privateMethod: "a #private method",
|
|
1031
|
+
__decorateClass: "a decorator",
|
|
1032
|
+
__decorateParam: "a parameter decorator",
|
|
1033
|
+
__async: "async/await",
|
|
1034
|
+
__generator: "a generator",
|
|
1035
|
+
__await: "await",
|
|
1036
|
+
__forAwait: "for await",
|
|
1037
|
+
__yieldStar: "yield*",
|
|
1038
|
+
__spreadValues: "an object spread",
|
|
1039
|
+
__spreadProps: "an object spread",
|
|
1040
|
+
__objRest: "a rest destructuring",
|
|
1041
|
+
__restKey: "a rest destructuring",
|
|
1042
|
+
__pow: "the `**` operator",
|
|
1043
|
+
__using: "`using`",
|
|
1044
|
+
__callDispose: "`using`",
|
|
1045
|
+
__toESM: "an import",
|
|
1046
|
+
__toCommonJS: "an export",
|
|
1047
|
+
__require: "a require",
|
|
1048
|
+
__awaiter: "async/await",
|
|
1049
|
+
__assign: "an object spread",
|
|
1050
|
+
__rest: "a rest destructuring",
|
|
1051
|
+
__decorate: "a decorator",
|
|
1052
|
+
__extends: "a class",
|
|
1053
|
+
__spreadArray: "an array spread"
|
|
1054
|
+
};
|
|
1055
|
+
var HELPER_LOOKALIKES = /* @__PURE__ */ new Set(["__proto__", "__dirname", "__filename"]);
|
|
1056
|
+
function suggestion(token, surface) {
|
|
1057
|
+
const ACCUMULATOR_GUESSES = ["$acc", "$accumulator", "$carry", "$memo", "$total", "$prev", "$previous"];
|
|
1058
|
+
if (ACCUMULATOR_GUESSES.includes(token) && LAMBDA_BINDINGS[surface].includes("$result")) {
|
|
1059
|
+
return `\`$result\` is the accumulator at this surface`;
|
|
1060
|
+
}
|
|
1061
|
+
if (ACCUMULATOR_GUESSES.includes(token)) {
|
|
1062
|
+
return `\`$result\` is the accumulator, and only \`reduce\` binds it`;
|
|
1063
|
+
}
|
|
1064
|
+
const elsewhere = Object.keys(LAMBDA_BINDINGS).filter(
|
|
1065
|
+
(s) => LAMBDA_BINDINGS[s].includes(token)
|
|
1066
|
+
);
|
|
1067
|
+
if (elsewhere.length) return `\`${token}\` is bound at ${elsewhere.join(", ")}, but not here`;
|
|
1068
|
+
if (token === "$parent" || token === "$this") {
|
|
1069
|
+
return `the value a filter is applied to is \`$this\` in \`fl.lambda\`, and \`$parent\` in the array-iterating filters`;
|
|
1070
|
+
}
|
|
1071
|
+
return void 0;
|
|
1072
|
+
}
|
|
1073
|
+
function assertLambdaBody(body, surface, source = `${PREFIX}.fn`) {
|
|
1074
|
+
try {
|
|
1075
|
+
assertBody(body, surface, source);
|
|
1076
|
+
} catch (err) {
|
|
1077
|
+
if (collector === null) throw err;
|
|
1078
|
+
collector(err instanceof Error ? err.message : String(err));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
var collector = null;
|
|
1082
|
+
function collectLambdaBodyProblems(onProblem, fn2) {
|
|
1083
|
+
const previous = collector;
|
|
1084
|
+
collector = onProblem;
|
|
1085
|
+
try {
|
|
1086
|
+
return fn2();
|
|
1087
|
+
} finally {
|
|
1088
|
+
collector = previous;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
function assertBody(body, surface, source) {
|
|
1092
|
+
if (body.trim() === "") {
|
|
1093
|
+
throw new Error(
|
|
1094
|
+
`${source}: the lambda body is empty. A lambda must return a value \u2014 the engine refuses a statement with no code. (issue #221)`
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
const mask = maskNonCode(body);
|
|
1098
|
+
const legal = LAMBDA_BINDINGS[surface];
|
|
1099
|
+
const locals = declaredLocals(mask);
|
|
1100
|
+
for (const token of dollarTokens(mask)) {
|
|
1101
|
+
if (legal.includes(token) || locals.has(token)) continue;
|
|
1102
|
+
const hint = suggestion(token, surface);
|
|
1103
|
+
throw new Error(
|
|
1104
|
+
`${source}: \`${token}\` is not a binding in a \`${surface}\` lambda body${hint ? ` \u2014 ${hint}` : ""}. It reads as undefined at runtime and raises NOTHING: the body runs, \`typeof\` reports "undefined", the value comes back null with HTTP 200, and a defensive \`typeof x !== "undefined"\` guard takes the wrong branch. Author time is the only place this is catchable. Bound here: ${legal.join(", ")} (plus the ${LAMBDA_GLOBALS.join(" / ")} globals). A stack variable is reached as \`$var.name\`, never as \`$name\`. (issue #221)`
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
assertNoBundlerHelpers(mask, source);
|
|
1108
|
+
const moduleSyntax = /(^|[;{}\n])\s*(import|export)\b(?![\s]*[.(])/.exec(mask);
|
|
1109
|
+
if (moduleSyntax) {
|
|
1110
|
+
const kw = moduleSyntax[2];
|
|
1111
|
+
throw new Error(
|
|
1112
|
+
`${source}: a top-level \`${kw}\` is a syntax error in a lambda body \u2014 the body is a function body, not a module, so it must \`return\` its value and cannot declare module syntax. Reach a dependency through the PRELOADED globals, which need no specifier: ${LAMBDA_MODULE_GLOBALS.join(", ")} (plus ${LAMBDA_GLOBALS.join(" / ")}, fetch, Buffer, TextEncoder). A dynamic \`import("...")\` or \`require("...")\` with a literal specifier is NOT portable: on an instance that bundles the body before running it, every literal specifier is resolved ahead of time and none of them exist, so the call comes back as the text \`Could not resolve "..."\` with HTTP 200. (issues #221, #265)`
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function assertNoBundlerHelpers(mask, source) {
|
|
1117
|
+
const declared = declaredLocals(mask, /__[A-Za-z0-9_$]*/g);
|
|
1118
|
+
for (const token of tokens(mask, /(^|[^A-Za-z0-9_$.])(__[A-Za-z][A-Za-z0-9_$]*)/g, true)) {
|
|
1119
|
+
if (declared.has(token) || HELPER_LOOKALIKES.has(token)) continue;
|
|
1120
|
+
const wrote = BUNDLER_HELPERS[token];
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
`${source}: \`${token}\` is a bundler helper, not something this body can call \u2014 the body was TRANSPILED before it could be read back${wrote ? `, because it writes ${wrote}` : ""}. An inline body is recovered from the live function with \`toString()\`, and a \`.ts\` loader (tsx/esbuild, which is how a TypeScript entry is evaluated) returns rewritten source. The helper is defined at MODULE scope and only the body travels, so \`${token}\` is undefined at runtime: the body throws, and the engine returns that failure as TEXT in the value slot with HTTP 200 \u2014 a wrong value, not an error. Move the body to its own module and load it with \`lam.file(...)\`, or write it as text with \`lam.raw(...)\`; neither is transpiled. (issue #256)`
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
var UNREPRESENTABLE_CLASSES = [
|
|
1127
|
+
{
|
|
1128
|
+
ctor: Date,
|
|
1129
|
+
name: "Date",
|
|
1130
|
+
fix: "capture the epoch number (`d.getTime()`) or the ISO string and rebuild it in the body (`new Date(d)`)"
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
ctor: Map,
|
|
1134
|
+
name: "Map",
|
|
1135
|
+
fix: "capture a plain object (`Object.fromEntries(m)`) \u2014 or the entries array, and rebuild with `new Map(entries)` in the body"
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
ctor: Set,
|
|
1139
|
+
name: "Set",
|
|
1140
|
+
fix: "capture an array (`[...s]`) \u2014 and rebuild with `new Set(arr)` in the body if you need set semantics"
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
ctor: RegExp,
|
|
1144
|
+
name: "RegExp",
|
|
1145
|
+
fix: "capture the source and flags as strings and rebuild with `new RegExp(src, flags)` in the body"
|
|
1146
|
+
}
|
|
1147
|
+
];
|
|
1148
|
+
function article(word) {
|
|
1149
|
+
return /^[aeiou]/i.test(word) ? "an" : "a";
|
|
1150
|
+
}
|
|
1151
|
+
function assertJsonCapture(name, value, source) {
|
|
1152
|
+
const type = typeof value;
|
|
1153
|
+
if (type === "function" || type === "symbol" || type === "bigint" || type === "undefined") {
|
|
1154
|
+
throw new Error(
|
|
1155
|
+
`${source}: capture \`${name}\` is ${article(type)} ${type}, which cannot cross into the engine \u2014 the body is sent as TEXT and runs in a different process. Capture JSON data (string, number, boolean, null, object, array); for behaviour, inline it in the body. (issue #221)`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
assertNoUnrepresentableClass(name, value, source, /* @__PURE__ */ new Set());
|
|
1159
|
+
}
|
|
1160
|
+
function assertNoUnrepresentableClass(path, value, source, seen) {
|
|
1161
|
+
if (value === null || typeof value !== "object") return;
|
|
1162
|
+
for (const entry of UNREPRESENTABLE_CLASSES) {
|
|
1163
|
+
if (!(value instanceof entry.ctor)) continue;
|
|
1164
|
+
throw new Error(
|
|
1165
|
+
`${source}: capture \`${path}\` is ${article(entry.name)} ${entry.name}, which cannot cross into the engine \u2014 the body is sent as TEXT, and JSON-encoding ${article(entry.name)} ${entry.name} yields ${entry.name === "Date" ? "a string" : "`{}`"}, not ${article(entry.name)} ${entry.name}. The body would read a different type than the one TypeScript gave it, and fail INSIDE the engine \u2014 which returns that failure as TEXT with HTTP 200, a wrong value rather than an error. Instead, ${entry.fix}. (issue #246)`
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
if (seen.has(value)) return;
|
|
1169
|
+
seen.add(value);
|
|
1170
|
+
if (Array.isArray(value)) {
|
|
1171
|
+
value.forEach((item, i) => assertNoUnrepresentableClass(`${path}[${i}]`, item, source, seen));
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1175
|
+
assertNoUnrepresentableClass(`${path}.${key}`, item, source, seen);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
function capturePrelude(capture, source) {
|
|
1179
|
+
if (capture === void 0) return "";
|
|
1180
|
+
const lines = [];
|
|
1181
|
+
for (const [name, value] of Object.entries(capture)) {
|
|
1182
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
|
|
1183
|
+
throw new Error(`${source}: capture key ${JSON.stringify(name)} is not a valid JavaScript identifier.`);
|
|
1184
|
+
}
|
|
1185
|
+
assertJsonCapture(name, value, source);
|
|
1186
|
+
lines.push(`const ${name} = ${JSON.stringify(value)};`);
|
|
1187
|
+
}
|
|
1188
|
+
return lines.length ? lines.join("\n") + "\n" : "";
|
|
1189
|
+
}
|
|
1190
|
+
function assertCaptureNotRenamed(body, capture, source) {
|
|
1191
|
+
if (capture === void 0) return;
|
|
1192
|
+
const keys = Object.keys(capture);
|
|
1193
|
+
if (keys.length === 0) return;
|
|
1194
|
+
const mask = maskNonCode(body);
|
|
1195
|
+
const declared = declaredLocals(mask, /[A-Za-z_$][A-Za-z0-9_$]*/g);
|
|
1196
|
+
for (const token of tokens(mask, /(^|[^A-Za-z0-9_$.])([A-Za-z_$][A-Za-z0-9_$]*[0-9])/g, true)) {
|
|
1197
|
+
if (declared.has(token)) continue;
|
|
1198
|
+
const key = keys.find((k) => new RegExp(`^${k.replace(/\$/g, "\\$")}[0-9]+$`).test(token));
|
|
1199
|
+
if (key === void 0) continue;
|
|
1200
|
+
throw new Error(
|
|
1201
|
+
`${source}: capture key \`${key}\` collides with a module-scope binding of the same name, so the body was rewritten to read \`${token}\` \u2014 which the capture prelude does not declare. An inline body is recovered from the live function with \`toString()\`, and a \`.ts\` loader (tsx/esbuild) renames one of two same-named bindings; the prelude is written afterwards, under the ORIGINAL name. As it stands \`${token}\` is undefined at runtime: the body throws, and the engine returns that failure as TEXT in the value slot with HTTP 200 \u2014 a wrong value, not an error. Give the capture a key that nothing at module scope shares \u2014 it does not have to keep the name of what it carries (\`capture: { <newName>: ${key} }\`, destructured as \`(_, { <newName> }) => \u2026\`) \u2014 or move the body to its own module with \`lam.file(...)\`. (issue #256)`
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function lambdaValue(body, opts, source) {
|
|
1206
|
+
const code = capturePrelude(opts?.capture, source) + body;
|
|
1207
|
+
assertCaptureNotRenamed(body, opts?.capture, source);
|
|
1208
|
+
if (opts?.surface !== void 0) assertLambdaBody(code, opts.surface, source);
|
|
1209
|
+
return c.text(code);
|
|
1210
|
+
}
|
|
1211
|
+
function fn(body, opts) {
|
|
1212
|
+
return lambdaValue(extractFunctionBody(String(body), `${PREFIX}.fn`), opts, `${PREFIX}.fn`);
|
|
1213
|
+
}
|
|
1214
|
+
function raw(code, opts) {
|
|
1215
|
+
return lambdaValue(code, opts, `${PREFIX}.raw`);
|
|
1216
|
+
}
|
|
1217
|
+
function fileOnNodeEntryOnly() {
|
|
1218
|
+
throw new Error(
|
|
1219
|
+
`${PREFIX}.file: this entry has no filesystem. It ships on the Node entry only \u2014 \`import { lam } from "@xanots/sdk/node"\` (that \`lam\` carries \`fn\` and \`raw\` too). In a browser bundle, use \`${PREFIX}.raw(code, { surface })\` with the body as text. (issue #257)`
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
var lam = Object.assign({ fn, raw }, { file: fileOnNodeEntryOnly });
|
|
1223
|
+
|
|
1224
|
+
// src/fields/catalog.ts
|
|
1225
|
+
var DEFAULTABLE_TYPES = /* @__PURE__ */ new Set([
|
|
1226
|
+
"text",
|
|
1227
|
+
"int",
|
|
1228
|
+
"decimal",
|
|
1229
|
+
"enum",
|
|
1230
|
+
"bool",
|
|
1231
|
+
"email",
|
|
1232
|
+
"json",
|
|
1233
|
+
"date",
|
|
1234
|
+
"epochms"
|
|
1235
|
+
]);
|
|
1236
|
+
var ACCESS_VALUES = /* @__PURE__ */ new Set(["public", "private", "internal"]);
|
|
1237
|
+
var FORMAT_VALUES = /* @__PURE__ */ new Set(["", "plaintext", "yaml", "html", "xml", "markdown"]);
|
|
1238
|
+
function descriptor(type, options) {
|
|
1239
|
+
if (options.format && type !== "text") {
|
|
1240
|
+
throw new Error(`f: \`format\` is only valid on text fields, not "${type}".`);
|
|
1241
|
+
}
|
|
1242
|
+
if (options.format !== void 0 && !FORMAT_VALUES.has(options.format)) {
|
|
1243
|
+
throw new Error(
|
|
1244
|
+
`f: invalid text \`format\` "${options.format}"; valid: plaintext/yaml/html/xml/markdown.`
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
if (options.access !== void 0 && !ACCESS_VALUES.has(options.access)) {
|
|
1248
|
+
throw new Error(
|
|
1249
|
+
`f: invalid \`access\` "${options.access}"; valid: public/private/internal.`
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
if (options.default !== void 0 && options.default !== "" && !DEFAULTABLE_TYPES.has(type)) {
|
|
1253
|
+
throw new Error(
|
|
1254
|
+
`f: \`default\` is not supported on "${type}" fields (the engine drops it); valid on text/int/decimal/enum/bool/email/json/date/timestamp.`
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
if (options.noDefault === true && options.default !== void 0) {
|
|
1258
|
+
throw new Error(
|
|
1259
|
+
`f: \`noDefault\` and \`default\` are mutually exclusive on "${type}" fields \u2014 \`noDefault\` omits the key entirely, so there is no value to set.`
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
return { type, options };
|
|
1263
|
+
}
|
|
1264
|
+
function make(type, defaults) {
|
|
1265
|
+
return (options = {}) => descriptor(type, { ...defaults, ...options });
|
|
1266
|
+
}
|
|
1267
|
+
function targetKeyType(table2) {
|
|
1268
|
+
if (typeof table2 === "string") return void 0;
|
|
1269
|
+
const def = table2;
|
|
1270
|
+
if (def.idType === "int" || def.idType === "uuid") return def.idType;
|
|
1271
|
+
if (def.schema === void 0 || def.schema === null) return void 0;
|
|
1272
|
+
const idType = Array.isArray(def.schema) ? def.schema.find((col2) => col2?.name === "id")?.type : def.schema.id?.type;
|
|
1273
|
+
if (idType === "uuid") return "uuid";
|
|
1274
|
+
return idType === void 0 || idType === "int" ? "int" : void 0;
|
|
1275
|
+
}
|
|
1276
|
+
function assertTableRefKeyType(table2, columnType) {
|
|
1277
|
+
const idType = targetKeyType(table2);
|
|
1278
|
+
if (idType === void 0 || idType === columnType) return;
|
|
1279
|
+
const name = (typeof table2 === "string" ? table2 : table2.name) || "<unnamed>";
|
|
1280
|
+
throw new Error(
|
|
1281
|
+
`f.tableRef("${name}"): the column is "${columnType}" but that table's primary key is "${idType}". The engine requires a reference column to match the target's key type, so this fails at import. Use f.tableRef(${name}, { type: "${idType}" }).`
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
function toNestedFields(map) {
|
|
1285
|
+
return Object.entries(map).map(([name, d]) => ({ name, type: d.type, ...d.options }));
|
|
1286
|
+
}
|
|
1287
|
+
var f = {
|
|
1288
|
+
// --- scalars ---
|
|
1289
|
+
text: make("text"),
|
|
1290
|
+
int: make("int"),
|
|
1291
|
+
decimal: make("decimal"),
|
|
1292
|
+
bool: make("bool"),
|
|
1293
|
+
uuid: make("uuid"),
|
|
1294
|
+
date: make("date"),
|
|
1295
|
+
email: make("email"),
|
|
1296
|
+
/** Password field; defaults to `access:"internal"` (the engine's stored default). */
|
|
1297
|
+
password: make("password", { access: "internal" }),
|
|
1298
|
+
/**
|
|
1299
|
+
* JSON column. Accepts an optional nested `children` schema — the structure the
|
|
1300
|
+
* editor shows when a `json` field is expanded, and what a pulled workspace
|
|
1301
|
+
* carries.
|
|
1302
|
+
*
|
|
1303
|
+
* `children` is spelled as an ARRAY of named, typed fields rather than the
|
|
1304
|
+
* `FieldMap` {@link FieldCatalog.object} takes positionally. The two are not
|
|
1305
|
+
* interchangeable: an object column's children are the column's own schema,
|
|
1306
|
+
* while a json column's are a description of the shape stored inside it, which
|
|
1307
|
+
* the engine persists in the order given. That order is part of the stored
|
|
1308
|
+
* bytes, and a map does not preserve it.
|
|
1309
|
+
*
|
|
1310
|
+
* Without this there is no way to author a structured JSON column at all
|
|
1311
|
+
* (issue #135).
|
|
1312
|
+
*/
|
|
1313
|
+
json(options = {}) {
|
|
1314
|
+
return descriptor("json", { ...options });
|
|
1315
|
+
},
|
|
1316
|
+
/** Epoch-millisecond timestamp (authored as `timestamp`). */
|
|
1317
|
+
timestamp: make("epochms"),
|
|
1318
|
+
// --- file resources (authoring name → blob_* stored name) ---
|
|
1319
|
+
/** Image file resource (stored `blob_img`). */
|
|
1320
|
+
image: make("blob_img"),
|
|
1321
|
+
/** Video file resource (stored `blob_video`). */
|
|
1322
|
+
video: make("blob_video"),
|
|
1323
|
+
/** Audio file resource (stored `blob_audio`). */
|
|
1324
|
+
audio: make("blob_audio"),
|
|
1325
|
+
/** Generic file attachment (stored `blob`). */
|
|
1326
|
+
attachment: make("blob"),
|
|
1327
|
+
// --- geo ---
|
|
1328
|
+
geo: {
|
|
1329
|
+
point: make("geo_point"),
|
|
1330
|
+
multipoint: make("geo_multipoint"),
|
|
1331
|
+
linestring: make("geo_linestring"),
|
|
1332
|
+
multilinestring: make("geo_multilinestring"),
|
|
1333
|
+
polygon: make("geo_polygon"),
|
|
1334
|
+
multipolygon: make("geo_multipolygon")
|
|
1335
|
+
},
|
|
1336
|
+
// --- composite (positional payload + options) ---
|
|
1337
|
+
/**
|
|
1338
|
+
* Enum field. `values` may be empty — that is an enum column added in the
|
|
1339
|
+
* editor and not yet given its options, which the engine stores and which
|
|
1340
|
+
* appears in the survey corpus. Refusing it made the SDK stricter than the
|
|
1341
|
+
* engine and cost a real table its readable form.
|
|
1342
|
+
*
|
|
1343
|
+
* An empty `values` brands the column `never` — an enum permitting nothing can
|
|
1344
|
+
* hold nothing. Note what that becomes downstream: `InferRow` surfaces the
|
|
1345
|
+
* column as `undefined`, not `never`, so a read-time narrow written against
|
|
1346
|
+
* `never` (`if (row.e === "x")`) is dead differently than it looks.
|
|
1347
|
+
*/
|
|
1348
|
+
enum(values, options = {}) {
|
|
1349
|
+
return descriptor("enum", { ...options, values: [...values] });
|
|
1350
|
+
},
|
|
1351
|
+
/** Vector field; `size` (>= 1) is the embedding dimensionality. */
|
|
1352
|
+
vector(size, options = {}) {
|
|
1353
|
+
if (!Number.isInteger(size) || size < 1) {
|
|
1354
|
+
throw new Error(`f.vector: size must be an integer >= 1, got ${size}.`);
|
|
1355
|
+
}
|
|
1356
|
+
return descriptor("vector", { ...options, vector: { size } });
|
|
1357
|
+
},
|
|
1358
|
+
/** Nested object field (stored `obj`); `children` is a named field map. */
|
|
1359
|
+
object(children, options = {}) {
|
|
1360
|
+
return descriptor("obj", { ...options, children: toNestedFields(children) });
|
|
1361
|
+
},
|
|
1362
|
+
/**
|
|
1363
|
+
* Table-reference (foreign-key) field — the column holds the referenced
|
|
1364
|
+
* table's primary key. The engine persists the link as a trailing `@` method
|
|
1365
|
+
* carrying the target table's id (`{name:"@", arg:["dbo=<guid>"]}`); on import
|
|
1366
|
+
* it parses that back into the column's `tableref_id`. The reference resolves to the
|
|
1367
|
+
* table's deterministic guid via the shared cross-object resolver, so it
|
|
1368
|
+
* agrees with the target table's payload `guid` with no shared registry.
|
|
1369
|
+
*
|
|
1370
|
+
* Defaults to an `int` column (matching an `int` primary key); pass
|
|
1371
|
+
* `{ type: "uuid" }` to reference a uuid-keyed table. A reference column may
|
|
1372
|
+
* only be `int` or `uuid` — the two valid primary-key types.
|
|
1373
|
+
*
|
|
1374
|
+
* @param table The referenced table (a `table()` def handle or its bare name).
|
|
1375
|
+
* For a **self-reference** (e.g. `tweets.reply_to → tweets`), the table's
|
|
1376
|
+
* `const` binding isn't assigned yet inside its own initializer — using the
|
|
1377
|
+
* handle throws a "used before declaration" error. Pass the **bare name**
|
|
1378
|
+
* instead: `f.tableRef("tweets", { type: "int" })`. Identity guids derive
|
|
1379
|
+
* from `(type, name)`, so the name form resolves to the same guid.
|
|
1380
|
+
*
|
|
1381
|
+
* Byte-verified whole-object against a persisted table readback
|
|
1382
|
+
* (`test/fixtures/tables/ex_field_table_ref.json`, asserted in
|
|
1383
|
+
* `test/conformance/kinds-corpus.test.ts`): the `@` method carries
|
|
1384
|
+
* `disabled:false`, sits last in `methods`, and its `dbo=` arg is the target
|
|
1385
|
+
* table's payload guid.
|
|
1386
|
+
*
|
|
1387
|
+
* The engine asserts the referenced table's primary-key type matches
|
|
1388
|
+
* (int↔int, uuid↔uuid). When the target is passed as a **def handle** its
|
|
1389
|
+
* `idType` is in hand, so a mismatch throws here instead of surfacing as an
|
|
1390
|
+
* import failure. The bare-name form (self-references) carries no schema —
|
|
1391
|
+
* there is nothing to check against, and it is not guessed at.
|
|
1392
|
+
*/
|
|
1393
|
+
tableRef(table2, options = {}) {
|
|
1394
|
+
const { type = "int", methods = [], ...rest } = options;
|
|
1395
|
+
assertTableRefKeyType(table2, type);
|
|
1396
|
+
const guid = resolveRef("dbo", table2);
|
|
1397
|
+
return descriptor(type, {
|
|
1398
|
+
...rest,
|
|
1399
|
+
methods: [...methods ?? [], { name: "@", arg: [`dbo=${guid}`] }]
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
|
|
1404
|
+
// src/inputs/input.ts
|
|
1405
|
+
function makeInput(type) {
|
|
1406
|
+
return (options = {}) => ({ type, options });
|
|
1407
|
+
}
|
|
1408
|
+
var input = {
|
|
1409
|
+
// --- scalars ---
|
|
1410
|
+
text: makeInput("text"),
|
|
1411
|
+
int: makeInput("int"),
|
|
1412
|
+
decimal: makeInput("decimal"),
|
|
1413
|
+
bool: makeInput("bool"),
|
|
1414
|
+
email: makeInput("email"),
|
|
1415
|
+
/**
|
|
1416
|
+
* Password input — hashes its value **when the input binds**, before your stack
|
|
1417
|
+
* runs. This breaks the natural signup/login shape and is the single most common
|
|
1418
|
+
* way to ship silently-broken auth:
|
|
1419
|
+
* - **Login:** `s.security.check_password` receives the *already-hashed* submission
|
|
1420
|
+
* (a fresh random salt each request), compares it against the stored hash, and
|
|
1421
|
+
* never matches — so login always fails with a false "invalid password".
|
|
1422
|
+
* - **Signup:** the value is hashed here at bind, so the `f.password` column then sees
|
|
1423
|
+
* an already-hashed `salt.hash` value and stores it as-is (the column's hash-on-write
|
|
1424
|
+
* skips a value already in that shape) — the plaintext never lands in the column.
|
|
1425
|
+
*
|
|
1426
|
+
* Recipe: use {@link input.text} (e.g. `input.text({ methods: ["min:6"] })`) for the
|
|
1427
|
+
* password on **both** signup and login, let the `f.password` *column* hash on write,
|
|
1428
|
+
* and pass the plaintext straight to `check_password`. See the auth recipe in the
|
|
1429
|
+
* README. Reach for `input.password` only when you specifically want bind-time hashing
|
|
1430
|
+
* and are not also comparing it with `check_password` (issue #109).
|
|
1431
|
+
*/
|
|
1432
|
+
password: makeInput("password"),
|
|
1433
|
+
/**
|
|
1434
|
+
* URL input — a `text` field that names the intent "this holds a URL". There
|
|
1435
|
+
* is no native engine `url` type, and this does **not** by itself enforce an
|
|
1436
|
+
* http(s) scheme: a `javascript:`/`data:` URL still type-checks and imports.
|
|
1437
|
+
* When the value is security-relevant (e.g. a link that gets navigated to),
|
|
1438
|
+
* reject bad input at the boundary in the stack with `s.precondition` — see
|
|
1439
|
+
* the "validate input at the boundary" recipe in the README. Carries the same
|
|
1440
|
+
* `TextMethod` options as {@link input.text}.
|
|
1441
|
+
*/
|
|
1442
|
+
url: makeInput("text"),
|
|
1443
|
+
uuid: makeInput("uuid"),
|
|
1444
|
+
date: makeInput("date"),
|
|
1445
|
+
/** Epoch-millisecond timestamp (stored `epochms`). */
|
|
1446
|
+
timestamp: makeInput("epochms"),
|
|
1447
|
+
json: makeInput("json"),
|
|
1448
|
+
/**
|
|
1449
|
+
* Raw file **upload** (stored `file`) — the bytes as they arrive on the
|
|
1450
|
+
* request (multipart, base64, or a fetched URI). Input-only: there is no
|
|
1451
|
+
* `f.file` column, because an upload is not something a table holds.
|
|
1452
|
+
*
|
|
1453
|
+
* It is NOT a stored file resource and cannot be written to a file column
|
|
1454
|
+
* directly. Store it first and write what you get back:
|
|
1455
|
+
* `s.storage.create_image({ as: "img", value: ref("input.avatar") })`, then
|
|
1456
|
+
* write `ref("img")` to an `f.image()` column. Use {@link input.image} and
|
|
1457
|
+
* friends only when the caller already sends a stored file resource.
|
|
1458
|
+
*/
|
|
1459
|
+
file: makeInput("file"),
|
|
1460
|
+
/**
|
|
1461
|
+
* **Database link** — one input that EXPANDS into one input per column of the
|
|
1462
|
+
* linked table (stored `<tableGuid>_mvpschema`; XanoScript `dblink`). A table
|
|
1463
|
+
* with three columns turns this single entry into three request inputs, so
|
|
1464
|
+
* read them by their own column names: `inp("email")`, not `inp("user__")`.
|
|
1465
|
+
* The expansion is live — adding a column to the table adds an input here.
|
|
1466
|
+
*
|
|
1467
|
+
* `hidden` is how you drop columns from that expansion (`["created_at", "id"]`)
|
|
1468
|
+
* and is the common case in the wild, not an edge case. `merge: true` is what
|
|
1469
|
+
* makes the engine expand rather than nest, so it is forced and cannot be
|
|
1470
|
+
* unset — a dblink that does not merge is not a dblink.
|
|
1471
|
+
*
|
|
1472
|
+
* `customize` overrides individual expanded columns — `{ email: { required:
|
|
1473
|
+
* true, methods: ["lower"] } }` — where `hidden` drops them wholesale. Both
|
|
1474
|
+
* are read per column by the engine's expansion, and a column named in
|
|
1475
|
+
* `customize` with `hidden: true` is dropped exactly as the outer list does it.
|
|
1476
|
+
*
|
|
1477
|
+
* Input-only: `excludedTypesForDatabase` rules it out as a column, and a
|
|
1478
|
+
* column linking a whole table would be a foreign key — use
|
|
1479
|
+
* {@link f.tableRef} for that.
|
|
1480
|
+
*
|
|
1481
|
+
* By convention the editor names the entry after the table with a trailing
|
|
1482
|
+
* `__` (`user__`), but the name is just the key in your `input:` map and any
|
|
1483
|
+
* name round-trips.
|
|
1484
|
+
*/
|
|
1485
|
+
dbLink(table2, options = {}) {
|
|
1486
|
+
return {
|
|
1487
|
+
type: `${resolveRef("dbo", table2)}_mvpschema`,
|
|
1488
|
+
options: { ...options, merge: true }
|
|
1489
|
+
};
|
|
1490
|
+
},
|
|
1491
|
+
// --- file resources / geo / vector / table refs (shared with the `f.*` catalog) ---
|
|
1492
|
+
/** Image file input (stored `blob_img`). */
|
|
1493
|
+
image: f.image,
|
|
1494
|
+
/** Video file input (stored `blob_video`). */
|
|
1495
|
+
video: f.video,
|
|
1496
|
+
/** Audio file input (stored `blob_audio`). */
|
|
1497
|
+
audio: f.audio,
|
|
1498
|
+
/** Generic file-attachment input (stored `blob`). */
|
|
1499
|
+
attachment: f.attachment,
|
|
1500
|
+
/** Geo inputs: `input.geo.point()`, `.polygon()`, … (six geometry types). */
|
|
1501
|
+
geo: f.geo,
|
|
1502
|
+
/** Vector input; `size` (>= 1) is the embedding dimensionality. */
|
|
1503
|
+
vector: f.vector,
|
|
1504
|
+
/** Table-reference input — see {@link f.tableRef}; pass the table handle or bare name. */
|
|
1505
|
+
tableRef: f.tableRef,
|
|
1506
|
+
/**
|
|
1507
|
+
* Enum input. `values` may be empty, mirroring {@link f.enum} — the two
|
|
1508
|
+
* surfaces describe the same engine type, and one accepting what the other
|
|
1509
|
+
* rejects is how they start disagreeing about what a workspace can hold.
|
|
1510
|
+
*/
|
|
1511
|
+
enum(values, options = {}) {
|
|
1512
|
+
return { type: "enum", options: { ...options, values: [...values] } };
|
|
1513
|
+
},
|
|
1514
|
+
/**
|
|
1515
|
+
* Object input (stored `obj`) with typed, named children. `children` is a
|
|
1516
|
+
* field map built from the same `f.*` catalog used for columns, e.g.
|
|
1517
|
+
* `input.object({ name: f.text(), age: f.int() })`.
|
|
1518
|
+
*/
|
|
1519
|
+
object(children, options = {}) {
|
|
1520
|
+
return {
|
|
1521
|
+
type: "obj",
|
|
1522
|
+
options: { ...options, children: toNestedFields(children) }
|
|
1523
|
+
};
|
|
1524
|
+
},
|
|
1525
|
+
/**
|
|
1526
|
+
* List (array) input — wraps an element constructor, mirroring `Array<T>`:
|
|
1527
|
+
* `input.list(input.text())`, `input.list(input.object({ id: f.int() }))`.
|
|
1528
|
+
* The element's own options (e.g. its `methods`) are kept; list-level `options`
|
|
1529
|
+
* (`required`, `nullable`, `description`, …) apply to the list field and win
|
|
1530
|
+
* on conflict. The element's value type is preserved as the array element.
|
|
1531
|
+
*/
|
|
1532
|
+
list(element, options = {}) {
|
|
1533
|
+
if (!element?.type) {
|
|
1534
|
+
throw new Error("input.list: pass an element constructor, e.g. input.list(input.text()).");
|
|
1535
|
+
}
|
|
1536
|
+
return {
|
|
1537
|
+
type: element.type,
|
|
1538
|
+
options: { ...element.options, ...options, array: true }
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
function encodeInput(name, descriptor2) {
|
|
1543
|
+
if (isTaggedValue(descriptor2)) {
|
|
1544
|
+
throw new Error(
|
|
1545
|
+
`input "${name}": got a value ref (inp("${descriptor2.value}"), tag "${descriptor2.tag}") where an input descriptor is required. An "input:" map wants a descriptor factory like input.text()/input.int()/input.enum([...]) \u2014 not the value ref inp(...). Reach for inp("${name}") only to READ this input inside a where/stack/value slot.`
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
return encodeField(name, descriptor2.type, descriptor2.options, INPUT_CONTEXT);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// src/values/obj.ts
|
|
1552
|
+
var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
1553
|
+
function serializeValue(v2, path) {
|
|
1554
|
+
return serializeAtom(v2, path) + serializeFilters(v2, path);
|
|
1555
|
+
}
|
|
1556
|
+
function serializeFilters(v2, path) {
|
|
1557
|
+
let out2 = "";
|
|
1558
|
+
for (const f2 of v2.filters) {
|
|
1559
|
+
if (f2.disabled) {
|
|
1560
|
+
throw new Error(
|
|
1561
|
+
`obj(): the value at \`${path}\` carries a DISABLED \`${f2.name}\` filter. An object literal is stored as one expression string, which has no spelling for a disabled filter \u2014 drop it, or build the value in a prior step (e.g. \`s.set_var\`) and reference it with \`ref\`.`
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
const args = f2.arg.map((a, i) => {
|
|
1565
|
+
if (a.filters.length > 0) {
|
|
1566
|
+
throw new Error(
|
|
1567
|
+
`obj(): the \`${f2.name}\` filter at \`${path}\` has an argument (#${i}) that carries its own filter chain. In an expression a trailing \`|\` binds to the whole value, not to one argument, so this cannot be written without changing what it means. Compute the argument in a prior step and reference it with \`ref\`.`
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
return serializeAtom(a, `${path}|${f2.name}[${i}]`);
|
|
1571
|
+
});
|
|
1572
|
+
out2 += `|${f2.name}${args.length > 0 ? `:${args.join(":")}` : ""}`;
|
|
1573
|
+
}
|
|
1574
|
+
return out2;
|
|
1575
|
+
}
|
|
1576
|
+
function serializeAtom(v2, path) {
|
|
1577
|
+
switch (v2.tag) {
|
|
1578
|
+
case "const":
|
|
1579
|
+
return JSON.stringify(v2.value);
|
|
1580
|
+
// double-quoted + escaped, valid XanoScript string
|
|
1581
|
+
case "const:int":
|
|
1582
|
+
case "const:decimal":
|
|
1583
|
+
return v2.value;
|
|
1584
|
+
// already the numeric literal (e.g. "123", "1.5")
|
|
1585
|
+
case "const:bool":
|
|
1586
|
+
return v2.value === "true" ? "true" : "false";
|
|
1587
|
+
case "const:null":
|
|
1588
|
+
return "null";
|
|
1589
|
+
case "input":
|
|
1590
|
+
return `$input.${v2.value}`;
|
|
1591
|
+
case "var":
|
|
1592
|
+
return `$var.${v2.value}`;
|
|
1593
|
+
case "auth":
|
|
1594
|
+
return v2.value ? `$auth.${v2.value}` : "$auth";
|
|
1595
|
+
case "col":
|
|
1596
|
+
return `$db.${v2.value}`;
|
|
1597
|
+
// A workspace env var and a built-in request var are the SAME tag; the
|
|
1598
|
+
// built-ins just carry a `$`-prefixed name. Both spell `$env.` + the name,
|
|
1599
|
+
// so `sys.remoteIp()` renders `$env.$remote_ip` and `env("STRIPE_KEY")`
|
|
1600
|
+
// renders `$env.STRIPE_KEY`.
|
|
1601
|
+
case "setting":
|
|
1602
|
+
return `$env.${v2.value}`;
|
|
1603
|
+
// The engine's native current-time constant. Only ever holds `"now"` — the
|
|
1604
|
+
// same narrowing the decoder makes — so anything else falls through to the
|
|
1605
|
+
// rejection rather than being rendered as a `now` it is not.
|
|
1606
|
+
case "const:epochms":
|
|
1607
|
+
if (v2.value === "now") return "now";
|
|
1608
|
+
break;
|
|
1609
|
+
}
|
|
1610
|
+
throw new Error(
|
|
1611
|
+
`obj(): value tag "${v2.tag}" at \`${path}\` isn't supported in a dynamic object literal yet. Supported: inp(), ref(), auth(), col(), env()/setting()/sys.*, c.now(), and c.text/int/decimal/bool/null \u2014 each optionally carrying a filter chain.`
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
function serializeMember(m, path) {
|
|
1615
|
+
if (typeof m === "string") return JSON.stringify(m);
|
|
1616
|
+
if (typeof m === "number") return String(m);
|
|
1617
|
+
if (typeof m === "boolean") return m ? "true" : "false";
|
|
1618
|
+
if (isTaggedValue(m)) return serializeValue(m, path);
|
|
1619
|
+
if (Array.isArray(m)) {
|
|
1620
|
+
return `[${m.map((el, i) => serializeMember(el, `${path}[${i}]`)).join(", ")}]`;
|
|
1621
|
+
}
|
|
1622
|
+
if (m !== null && typeof m === "object") return serializeRecord(m, path);
|
|
1623
|
+
throw new Error(
|
|
1624
|
+
`obj(): \`${path}\` must be a Value (inp/ref/auth/col/c.*), a scalar literal (string/number/boolean), a nested object, or an array (got ${typeof m}).`
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
function serializeRecord(rec, path) {
|
|
1628
|
+
const parts = Object.entries(rec).map(([key, member]) => {
|
|
1629
|
+
if (!IDENT.test(key)) {
|
|
1630
|
+
throw new Error(
|
|
1631
|
+
`obj(): key "${key}"${path ? ` at \`${path}\`` : ""} must be a bare identifier ([A-Za-z_][A-Za-z0-9_]*) \u2014 XanoScript object keys aren't quoted.`
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
return `${key}: ${serializeMember(member, path ? `${path}.${key}` : key)}`;
|
|
1635
|
+
});
|
|
1636
|
+
return parts.length === 0 ? "{}" : `{ ${parts.join(", ")} }`;
|
|
1637
|
+
}
|
|
1638
|
+
function obj(fields) {
|
|
1639
|
+
return {
|
|
1640
|
+
value: serializeRecord(fields, ""),
|
|
1641
|
+
tag: "const:expr2",
|
|
1642
|
+
filters: []
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// src/values/generated/filters.generated.ts
|
|
1647
|
+
var v = (x) => {
|
|
1648
|
+
if (typeof x === "string") return c.text(x);
|
|
1649
|
+
if (typeof x === "number") return Number.isInteger(x) ? c.int(x) : c.decimal(x);
|
|
1650
|
+
if (typeof x === "boolean") return c.bool(x);
|
|
1651
|
+
return x;
|
|
1652
|
+
};
|
|
1653
|
+
var slotted = (name, argNames, required, capped = true) => (...args) => {
|
|
1654
|
+
const surface = LAMBDA_CODE_FILTERS[name]?.surface;
|
|
1655
|
+
const body = (x) => surface === void 0 ? x : toLambdaValue(x, surface, `fl.${name}`);
|
|
1656
|
+
const first = args[0];
|
|
1657
|
+
const isNamed = args.length === 1 && typeof first === "object" && first !== null && !Array.isArray(first) && !isTaggedValue(first);
|
|
1658
|
+
if (!isNamed) {
|
|
1659
|
+
const supplied = args.map(body);
|
|
1660
|
+
const got = supplied.filter((a) => a !== void 0).length;
|
|
1661
|
+
assertArity(name, argNames, required, got);
|
|
1662
|
+
if (capped) assertMaxArity(name, argNames, got);
|
|
1663
|
+
return filter(name, ...supplied.map(v));
|
|
1664
|
+
}
|
|
1665
|
+
const named = Object.fromEntries(
|
|
1666
|
+
Object.entries(first).map(([k, x]) => [k, body(x)])
|
|
1667
|
+
);
|
|
1668
|
+
const unknownArg = Object.keys(named).find((k) => !argNames.includes(k));
|
|
1669
|
+
if (unknownArg !== void 0) {
|
|
1670
|
+
throw new Error(
|
|
1671
|
+
`fl.${name} has no argument \`${unknownArg}\` \u2014 it takes ${argNames.length === 0 ? "none" : argNames.join(", ")}. A name the filter does not declare is dropped rather than sent, so this would read as an omission instead of a typo. (issue #47)`
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
const last = argNames.reduce((acc, n, i) => named[n] !== void 0 ? i : acc, -1);
|
|
1675
|
+
const hole = argNames.slice(0, last).findIndex((n) => named[n] === void 0);
|
|
1676
|
+
if (hole !== -1) {
|
|
1677
|
+
throw new Error(
|
|
1678
|
+
`fl.${name}: \`${argNames[hole]}\` is omitted but \`${argNames[last]}\` is supplied. Filter arguments are positional even in this form \u2014 the engine reads them by slot, so one in the middle cannot be skipped. Supply \`${argNames[hole]}\` too. (issue #221)`
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
assertArity(name, argNames, required, last + 1);
|
|
1682
|
+
return filter(name, ...argNames.map((n) => v(named[n])));
|
|
1683
|
+
};
|
|
1684
|
+
var assertArity = (name, argNames, required, got) => {
|
|
1685
|
+
if (got >= required) return;
|
|
1686
|
+
throw new Error(
|
|
1687
|
+
`fl.${name} needs ${required} argument(s) \u2014 ${argNames.slice(0, required).join(", ")} \u2014 but got ${got}. Filter arguments are positional and the engine refuses a short call, so a missing one cannot be inferred \u2014 not even where the filter's own documentation calls it optional. Some filters accept omission from the END and some require every argument regardless; which is which is probed, not declared. (issues #221, #246)`
|
|
1688
|
+
);
|
|
1689
|
+
};
|
|
1690
|
+
var assertMaxArity = (name, argNames, got) => {
|
|
1691
|
+
if (got <= argNames.length) return;
|
|
1692
|
+
throw new Error(
|
|
1693
|
+
`fl.${name} takes ${argNames.length} argument(s) \u2014 ${argNames.join(", ")} \u2014 but got ${got}. The extra one is not sent anywhere useful: it rides into the filter's arg list, where the engine either ignores it or fails opaquely at runtime. Drop it, or \u2014 if this filter really does accept more than the catalog declares \u2014 write it untyped as \`filter("${name}", \u2026)\`. (issue #47)`
|
|
1694
|
+
);
|
|
1695
|
+
};
|
|
1696
|
+
var FILTER_NAMES = ["abs", "acos", "acosh", "add", "addslashes", "append", "array_diff", "array_diff_assoc", "array_entries", "array_fill", "array_fill_keys", "array_intersect", "array_intersect_assoc", "array_keys", "array_merge", "array_merge_recursive", "array_pop", "array_push", "array_remove", "array_shift", "array_shuffle", "array_slice", "array_unshift", "array_values", "asin", "asinh", "atan", "atanh", "avg", "base64_decode", "base64_decode_urlsafe", "base64_encode", "base64_encode_urlsafe", "base_convert", "bin2hex", "bindec", "bitwise_and", "bitwise_not", "bitwise_or", "bitwise_xor", "capitalize", "ceil", "concat", "contains", "convert_encoding", "cos", "count", "create_object", "create_object_from_entries", "crypto_jwe_decode", "crypto_jwe_encode", "crypto_jws_decode", "crypto_jws_encode", "csv_create", "csv_decode", "csv_encode", "csv_parse", "decbin", "dechex", "decoct", "decrypt", "deg2rad", "detect_encoding", "div", "empty", "encrypt", "ends_with", "epochms_add_ms", "epochms_add_secs", "epochms_date", "epochms_from_format", "epochms_transform", "eq", "escape", "even", "every", "exp", "filter", "filter_empty", "filter_empty_array", "filter_empty_object", "filter_empty_text", "filter_false", "filter_null", "filter_zero", "find", "findIndex", "first", "first_notempty", "first_notnull", "flatten", "floor", "from_utf8", "fsort", "get", "gt", "gte", "has", "hex2bin", "hexdec", "hmac_md5", "hmac_sha1", "hmac_sha256", "hmac_sha384", "hmac_sha512", "icontains", "iends_with", "in", "index_by", "is_array", "is_bool", "is_decimal", "is_int", "is_object", "is_text", "is_uuid", "istarts_with", "join", "json_decode", "json_encode", "jwe_decode", "jwe_encode", "lambda", "last", "list_encodings", "ln", "log", "log10", "lower", "lt", "lte", "ltrim", "map", "max", "md5", "min", "mod", "mul", "ne", "not", "null", "num_max", "num_min", "number_format", "octdec", "odd", "pick", "pow", "prepend", "product", "querystring_parse", "rad2deg", "range", "reduce", "regex_match", "regex_match_all", "regex_quote", "regex_replace", "regex_test", "reverse", "round", "rtrim", "safe_array", "secureid_decode", "secureid_encode", "set", "set_conditional", "set_ifnotempty", "set_ifnotnull", "sha1", "sha256", "sha384", "sha512", "sin", "some", "sort", "split", "sprintf", "sql_alias", "sql_esc", "sqrt", "starts_with", "string_replace", "strip_accents", "strip_tags", "stripos", "strlen", "strpos", "sub", "substr", "sum", "tan", "text_escape", "text_unescape", "to_bool", "to_decimal", "to_epoch_day", "to_epoch_hour", "to_epoch_minute", "to_epoch_ms", "to_epoch_sec", "to_epochms", "to_expr", "to_int", "to_text", "to_utf8", "transform", "trim", "uid", "unique", "unpick", "unset", "upper", "url_addarg", "url_decode", "url_decode_rfc3986", "url_delarg", "url_encode", "url_encode_rfc3986", "url_getarg", "url_hasarg", "url_parse", "uuid4", "xml_decode", "yaml_decode", "yaml_encode"];
|
|
1697
|
+
var FILTER_SPECS = { "deg2rad": { "result": "decimal", "group": "math", "description": "Convert degrees to radians" }, "rad2deg": { "result": "decimal", "group": "math", "description": "Convert radians to degrees" }, "number_format": { "args": [{ "name": "decimals", "type": "int" }, { "name": "decimal_separator", "type": "text" }, { "name": "thousands_separator", "type": "text" }], "result": "string", "group": "math", "description": "Format a number with flexible support over decimal places, thousands separator, and decimal separator." }, "sin": { "result": "decimal", "group": "math", "description": "Calculates the sine of the supplied value in radians" }, "asin": { "result": "decimal", "group": "math", "description": "Calculates the arc sine of the supplied value in radians" }, "asinh": { "result": "decimal", "group": "math", "description": "Calculates the inverse hyperbolic sine of the supplied value in radians" }, "cos": { "result": "decimal", "group": "math", "description": "Calculates the cosine of the supplied value in radians" }, "acos": { "result": "decimal", "group": "math", "description": "Calculates the arc cosine of the supplied value in radians" }, "acosh": { "result": "decimal", "group": "math", "description": "Calculates the inverse hyperbolic cosine of the supplied value in radians" }, "tan": { "result": "decimal", "group": "math", "description": "Calculates the tangent of the supplied value in radians" }, "atan": { "result": "decimal", "group": "math", "description": "Calculates the arc tangent of the supplied value in radians" }, "atanh": { "result": "decimal", "group": "math", "description": "Calculates the inverse hyperbolic tangent of the supplied value in radians" }, "floor": { "result": "int", "group": "math", "description": "Round a decimal down to its integer equivalent" }, "ceil": { "result": "int", "group": "math", "description": "Round a decimal up to its integer equivalent" }, "round": { "args": [{ "name": "precision", "type": "int", "optional": true }], "result": "decimal", "group": "math", "description": "Round a decimal with optional precision" }, "first": { "result": "<T>", "group": "array", "description": "Get the first entry of an array" }, "last": { "result": "<T>", "group": "array", "description": "Get the last entry of an array" }, "count": { "result": "int", "group": "array", "description": "Return the number of items in an object/array" }, "range": { "args": [{ "name": "start", "type": "int" }, { "name": "stop", "type": "int" }], "result": "int[]", "group": "array", "description": "Returns array of values between the specified start/stop." }, "reverse": { "result": "<T>[]", "group": "array", "description": "Returns values of an array in reverse order" }, "unique": { "args": [{ "name": "path", "type": "text", "optional": true }], "result": "<T>[]", "group": "array", "description": "Returns unique values of an array" }, "safe_array": { "result": "<T>[]", "group": "array", "description": "Always returns an array. Uses the existing value if it is an array or creates an array of one element." }, "flatten": { "result": "<T>[]", "group": "array", "description": "Flattens a multidimensional array into a single level array of values." }, "filter_empty": { "args": [{ "name": "path", "type": "text", "optional": true }], "result": "<T>[]", "group": "array", "description": 'Returns a new array with only entries that are not empty ("", null, 0, "0", false, [], {})' }, "filter_empty_text": { "args": [{ "name": "path", "type": "text", "optional": true }], "result": "<T>[]", "group": "array", "description": 'Returns a new array with only entries that are not empty ("", null, 0, "0", false, [], {})' }, "filter_null": { "args": [{ "name": "path", "type": "text", "optional": true }], "result": "<T>[]", "group": "array", "description": "Returns a new array with only entries that are not null" }, "filter_empty_array": { "args": [{ "name": "path", "type": "text", "optional": true }], "result": "<T>[]", "group": "array", "description": "Returns a new array with only entries that are not an empty array `[]`" }, "filter_empty_object": { "args": [{ "name": "path", "type": "text", "optional": true }], "result": "<T>[]", "group": "array", "description": "Returns a new array with only entries that are not an empty object `{}`" }, "filter_zero": { "args": [{ "name": "path", "type": "text", "optional": true }], "result": "<T>[]", "group": "array", "description": "Returns a new array with only entries that are not zero" }, "filter_false": { "args": [{ "name": "path", "type": "text", "optional": true }], "result": "<T>[]", "group": "array", "description": "Returns a new array with only entries that are not false" }, "sort": { "description": "Sort an array of elements with an optional path inside the element", "variadic": true }, "index_by": { "args": [{ "name": "path", "type": "text" }], "result": "{ [key: string]: <T>[] }", "group": "array", "description": "Groups the piped array into an OBJECT keyed by each item's path \u2014 every value is an ARRAY of the items sharing that key, even when only one item does. So a lookup reads `idx[key][0]`, not `idx[key]`; the singular spelling yields null rather than erroring. Items whose path is missing or non-scalar are dropped." }, "append": { "args": [{ "name": "value", "type": "<T>" }, { "name": "path", "type": "text" }], "result": "<T>[]", "group": "array", "description": "Push an element on to the end of an array within an object and return the updated object" }, "prepend": { "args": [{ "name": "value", "type": "<T>" }, { "name": "path", "type": "text" }], "result": "<T>[]", "group": "array", "description": "Push an element on to the beginning of an array within an object and return the updated object" }, "abs": { "result": "decimal", "group": "math", "description": "Returns the absolute value" }, "sqrt": { "result": "decimal", "group": "math", "description": "Returns the square root of the value" }, "exp": { "result": "decimal", "group": "math", "description": 'Returns the exponent of mathematical expression "e"' }, "log": { "args": [{ "name": "base", "type": "<T>" }], "result": "decimal", "group": "math", "description": "Returns the logarithm with a custom base" }, "log10": { "result": "decimal", "group": "math", "description": "Returns the Base-10 logarithm" }, "ln": { "result": "decimal", "group": "math", "description": "Returns the natural logarithm" }, "pow": { "args": [{ "name": "exp", "type": "<T>" }], "result": "decimal", "group": "math", "description": "Returns the value raised to the power of exp." }, "min": { "result": "decimal", "group": "math", "description": "Returns the min of the values of the array" }, "max": { "result": "decimal", "group": "math", "description": "Returns the max of the values of the array" }, "sum": { "result": "decimal", "group": "math", "description": "Returns the sum of the values of the array" }, "avg": { "result": "decimal", "group": "math", "description": "Returns the average of the values of the array" }, "product": { "result": "decimal", "group": "math", "description": "Returns the product of the values of the array" }, "odd": { "result": "bool", "group": "comparison", "description": "Returns whether or not the value is odd" }, "even": { "result": "bool", "group": "comparison", "description": "Returns whether or not the value is even" }, "in": { "args": [{ "name": "search", "type": "<T>" }], "result": "bool", "group": "comparison", "description": "Returns whether or not the value is in the array" }, "add": { "args": [{ "name": "value", "type": "decimal" }], "result": "decimal", "group": "math", "description": "Add 2 values together and return the answer" }, "bitwise_and": { "args": [{ "name": "value", "type": "int" }], "result": "int", "group": "math", "description": "Bitwise AND 2 values together and return the answer" }, "bitwise_or": { "args": [{ "name": "value", "type": "int" }], "result": "int", "group": "math", "description": "Bitwise OR 2 values together and return the answer" }, "bitwise_xor": { "args": [{ "name": "value", "type": "int" }], "result": "int", "group": "math", "description": "Bitwise XOR 2 values together and return the answer" }, "not": { "result": "bool", "group": "comparison", "description": "Returns the opposite of the existing value evaluated as a boolean" }, "bitwise_not": { "result": "bool", "group": "comparison", "description": "Returns the existing value with its bits flipped" }, "is_object": { "result": "bool", "group": "comparison", "description": "Returns whether or not the value is an object." }, "is_array": { "result": "bool", "group": "comparison", "description": "Returns whether or not the value is a numerical indexed array." }, "is_int": { "result": "bool", "group": "comparison", "description": "Returns whether or not the value is an integer." }, "is_decimal": { "result": "bool", "group": "comparison", "description": "Returns whether or not the value is a decimal value." }, "is_bool": { "result": "bool", "group": "comparison", "description": "Returns whether or not the value is a boolean." }, "is_text": { "result": "bool", "group": "comparison", "description": "Returns whether or not the value is text." }, "addslashes": { "result": "text", "group": "text", "description": "Adds a backslash to the following characters: single quote, double quote, backslash, and null character." }, "escape": { "result": "text", "group": "text", "description": "Converts special characters into their escaped variants. Ex: for tabs and" }, "list_encodings": { "result": "text[]", "group": "text", "description": "List support character encodings" }, "detect_encoding": { "args": [{ "name": "encodings", "type": "text", "optional": true }], "result": "text", "group": "text", "description": "Detect the character encoding of the supplied text" }, "to_utf8": { "result": "text", "group": "text", "description": "Convert the supplied text from its binary form (ISO-8859-1) to UTF-8." }, "from_utf8": { "result": "text", "group": "text", "description": "Convert the supplied text from UTF-8 to its binary form (ISO-8859-1)." }, "convert_encoding": { "args": [{ "name": "to", "type": "text" }, { "name": "from", "type": "text" }], "result": "text", "group": "text", "description": "Convert the character encoding of the supplied text" }, "trim": { "args": [{ "name": "mask", "type": "text", "optional": true }], "result": "text", "group": "text", "description": "Trim whitespace or other characters from both sides and return the result" }, "ltrim": { "args": [{ "name": "mask", "type": "text", "optional": true }], "result": "text", "group": "text", "description": "Trim whitespace or other characters from the left side and return the result" }, "rtrim": { "args": [{ "name": "mask", "type": "text", "optional": true }], "result": "text", "group": "text", "description": "Trim whitespace or other characters from the right return the result" }, "capitalize": { "result": "text", "group": "text", "description": "Converts the first letter of each word to a capital letter" }, "substr": { "args": [{ "name": "start", "type": "int" }, { "name": "length", "type": "int" }], "result": "text", "group": "text", "description": "Extracts a section of text" }, "split": { "args": [{ "name": "separator", "type": "text" }], "result": "text[]", "group": "text", "description": "Splits text into an array of text and returns the result" }, "join": { "args": [{ "name": "separator", "type": "text" }], "result": "text", "group": "array", "description": "Joins an array into a text string via the separator and returns the result" }, "strlen": { "result": "int", "group": "text", "description": "Returns the number of characters" }, "starts_with": { "args": [{ "name": "search", "type": "text" }], "result": "bool", "group": "text", "description": "Returns whether or not the expression is present at the beginning Direction: the piped value is the subject text; the argument is the substring searched for." }, "istarts_with": { "args": [{ "name": "search", "type": "text" }], "result": "bool", "group": "text", "description": "Returns whether or not the case-insensitive expression is present at the beginning Direction: the piped value is the subject text; the argument is the substring searched for." }, "ends_with": { "args": [{ "name": "search", "type": "text" }], "result": "bool", "group": "text", "description": "Returns whether or not the expression is present at the end Direction: the piped value is the subject text; the argument is the substring searched for." }, "iends_with": { "args": [{ "name": "search", "type": "text" }], "result": "bool", "group": "text", "description": "Returns whether or not the case-insensitive expression is present at the end Direction: the piped value is the subject text; the argument is the substring searched for." }, "contains": { "args": [{ "name": "search", "type": "text" }], "result": "bool", "group": "text", "description": "Returns whether or not the expression is found Direction: the piped value is the subject text; the argument is the substring searched for." }, "icontains": { "args": [{ "name": "search", "type": "text" }], "result": "bool", "group": "text", "description": "Returns whether or not the case-insensitive expression is found Direction: the piped value is the subject text; the argument is the substring searched for." }, "set": { "args": [{ "name": "path", "type": "text" }, { "name": "value", "type": "any" }], "result": "any", "group": "manipulation", "description": "Sets a value at the path within the object and returns the updated object" }, "set_conditional": { "args": [{ "name": "path", "type": "text" }, { "name": "value", "type": "any" }, { "name": "conditional", "type": "any" }], "result": "any", "group": "manipulation", "description": "Sets a value at the path within the object and returns the updated object, if the conditional expression is true" }, "set_ifnotempty": { "args": [{ "name": "path", "type": "text" }, { "name": "value", "type": "any" }], "result": "any", "group": "manipulation", "description": 'Sets a value (if it is not empty: "", null, 0, "0", false, [], {}) at the path within the object and returns the updated object' }, "set_ifnotnull": { "args": [{ "name": "path", "type": "text" }, { "name": "value", "type": "any" }], "result": "any", "group": "manipulation", "description": "Sets a value (if it is not null) at the path within the object and returns the updated object" }, "first_notnull": { "args": [{ "name": "value", "type": "any" }], "result": "any", "group": "manipulation", "description": "Returns the first value that is not null" }, "first_notempty": { "args": [{ "name": "value", "type": "any" }], "result": "any", "group": "manipulation", "description": 'Returns the first value that is not empty - i.e. not ("", null, 0, "0", false, [], {})' }, "unset": { "args": [{ "name": "path", "type": "text" }], "result": "any", "group": "manipulation", "description": "Removes a value at the path within the object and returns the updated object" }, "transform": { "args": [{ "name": "expression", "type": "text" }], "result": "any", "group": "manipulation", "description": 'Evaluates a Xano Expression Engine expression over the piped value. NOT a JavaScript body \u2014 there is no `return`, and the piped value binds POSITIONALLY as `$0` (or `$$`), NOT as `$this` (which resolves to null here, yielding a wrong answer with no error). `$var`, `$input`, `$env` and `$auth` also resolve, and filters may be piped inside the expression: `$0 * 2`, `$0|sort|join:","`, `{ id: $0.id, total: $0.qty * $0.price }`. PARENTHESIZE a pipe used inside an object/array literal \u2014 `{ s: ($0|sort|join:",") }` \u2014 or the filter argument\'s comma is read as the key separator, returning null and silently dropping every later key. For JavaScript use fl.lambda, whose body does bind `$this`.' }, "get": { "args": [{ "name": "path", "type": "text" }, { "name": "default", "type": "json", "optional": true }], "result": "any", "group": "manipulation", "description": "Returns the value of an object at the specified path" }, "has": { "args": [{ "name": "path", "type": "text" }], "result": "bool", "group": "manipulation", "description": "Returns the existence of whether or not something is present in the object at the specified path" }, "to_expr": { "result": "any", "group": "transform", "description": "Treats the PIPED TEXT as Xano Expression Engine source, evaluates it, and returns the result. Takes no argument. `$var`, `$input`, `$env` and `$auth` resolve inside it; there is no operand binding (`$0` is null) because the operand IS the source. For an expression OVER a value, use fl.transform." }, "to_text": { "result": "text", "group": "transform", "description": "Converts integer, decimal, or bool types to text and returns the result" }, "to_int": { "result": "int", "group": "transform", "description": "Converts text, decimal, or bool types to an integer and returns the result" }, "to_decimal": { "result": "decimal", "group": "transform", "description": "Converts text, integer, or bool types to a decimal and returns the result" }, "to_bool": { "result": "bool", "group": "transform", "description": "Converts text, integer, or decimal types to a bool and returns the result" }, "create_object": { "args": [{ "name": "values", "type": "<T>[]" }], "result": "any", "group": "transform", "description": "Creates an object based on a list of keys and a list of values" }, "create_object_from_entries": { "result": "any", "group": "transform", "description": "Creates an object based on an array of key/value pairs. (i.e. same result as the entries filter)" }, "json_decode": { "result": "any", "group": "transform", "description": "Decodes the value represented as json and returns the result" }, "json_encode": { "result": "text", "group": "transform", "description": "Encodes the value and returns the result as json text" }, "xml_decode": { "result": "any", "group": "transform", "description": "Decodes XML and returns the result" }, "csv_parse": { "args": [{ "name": "separator", "type": "text" }, { "name": "enclosure", "type": "text" }, { "name": "escape", "type": "text" }], "result": "any", "group": "transform", "description": "Parse the contents of a CSV file and convert it into an array of objects." }, "csv_decode": { "args": [{ "name": "separator", "type": "text" }, { "name": "enclosure", "type": "text" }, { "name": "escape", "type": "text" }], "result": "any", "group": "transform", "description": "Decodes the value represented in the CSV format and returns the result" }, "csv_encode": { "args": [{ "name": "separator", "type": "text" }, { "name": "enclosure", "type": "text" }, { "name": "escape", "type": "text" }], "result": "text", "group": "transform", "description": "Encodes the value and returns the result in CSV format Writes NO header row \u2014 values only. Each row contributes its own values in THAT row's key order, with no normalization across rows, so rows whose keys differ in order or count silently misalign columns rather than erroring. Non-scalar cells (nested objects/arrays) are JSON-encoded into the cell, and `false` writes as empty. If the piped array's elements are scalars rather than objects, the whole array is treated as ONE row and you get a single line. For a header, use `csv_create`." }, "csv_create": { "args": [{ "name": "rows", "type": "text[]" }, { "name": "separator", "type": "text" }, { "name": "enclosure", "type": "text" }, { "name": "escape", "type": "text" }], "result": "text", "group": "transform", "description": "Creates a CSV format data stream from a list of column names and their corresponding data rows. Direction: the piped value is the list of COLUMN NAMES (written as the header line); the `rows` argument carries the data rows. This is the header-writing counterpart to `csv_encode`, which emits values only." }, "url_decode": { "result": "text", "group": "transform", "description": "Decodes the value represented as a url encoded value" }, "url_decode_rfc3986": { "result": "text", "group": "transform", "description": "Decodes the value represented as a url encoded value using the RFC 3986 specification" }, "url_encode": { "result": "text", "group": "transform", "description": "Encodes the value and returns the result as a url encoded value" }, "url_encode_rfc3986": { "result": "text", "group": "transform", "description": "Encodes the value and returns the result as a url encoded value using the RFC 3986 specification" }, "url_addarg": { "args": [{ "name": "key", "type": "text" }, { "name": "value", "type": "text" }, { "name": "encoding_rfc3986", "type": "bool", "optional": true }], "result": "text", "group": "text", "description": "Parses a URL and returns an updated version with an encoded version of the supplied argument" }, "url_delarg": { "args": [{ "name": "key", "type": "text" }], "result": "text", "group": "text", "description": "Parses a URL and returns an updated version with the supplied argument removed" }, "url_hasarg": { "args": [{ "name": "key", "type": "text" }], "result": "text", "group": "text", "description": "Returns the existence of a argument in the URL" }, "url_getarg": { "args": [{ "name": "key", "type": "text" }, { "name": "default", "type": "text", "optional": true }], "result": "text", "group": "text", "description": "Gets the argument's value from a URL" }, "url_parse": { "result": "json", "group": "text", "description": "Parses a URL into its individual components." }, "querystring_parse": { "result": "json", "group": "text", "description": "Parses a query string from a URL into its individual key-value pairs." }, "yaml_decode": { "result": "obj", "group": "transform", "description": "Decodes the value represented as yaml and returns the result" }, "yaml_encode": { "result": "text", "group": "transform", "description": "Encodes the value and returns the result as yaml text" }, "hex2bin": { "result": "text", "group": "transform", "description": "Converts a hex value into its binary equivalent" }, "bin2hex": { "result": "text", "group": "transform", "description": "Converts a binary value into its hex equivalent" }, "dechex": { "result": "text", "group": "transform", "description": "Converts a decimal value into its hex equivalent" }, "hexdec": { "result": "text", "group": "transform", "description": "Converts a hex value into its decimal equivalent" }, "decbin": { "result": "text", "group": "transform", "description": "Converts a decimal value into its binary string (i.e. 01010) equivalent" }, "bindec": { "result": "text", "group": "transform", "description": "Converts a binary string (i.e. 01010) into its decimal equivalent" }, "decoct": { "result": "text", "group": "transform", "description": "Converts a decimal value into its octal equivalent" }, "octdec": { "result": "text", "group": "transform", "description": "Converts an octal value into its decimal equivalent" }, "base_convert": { "args": [{ "name": "from_base", "type": "any" }, { "name": "to_base", "type": "any" }], "result": "text", "group": "transform", "description": "Converts a value between two bases" }, "base64_decode": { "result": "text", "group": "transform", "description": "Decodes the value represented as base64 text and returns the result" }, "base64_encode": { "result": "text", "group": "transform", "description": "Encodes the value and returns the result as base64 text" }, "base64_decode_urlsafe": { "result": "text", "group": "transform", "description": "Decodes the value represented as base64 urlsafe text and returns the result" }, "base64_encode_urlsafe": { "result": "text", "group": "transform", "description": "Encodes the value and returns the result as base64 urlsafe text" }, "encrypt": { "args": [{ "name": "algorithm", "type": "enum" }, { "name": "key", "type": "text" }, { "name": "iv", "type": "text" }], "result": "text", "group": "security", "description": "Encrypts the value and returns the result in raw binary form." }, "decrypt": { "args": [{ "name": "algorithm", "type": "enum" }, { "name": "key", "type": "text" }, { "name": "iv", "type": "text" }], "result": "text", "group": "security", "description": "Decrypts the value and returns the result." }, "jwe_encode": { "description": "Encodes the value and return the result as a JWE token", "args": [{ "name": "arg1", "type": "any" }] }, "jwe_decode": { "description": "Decodes the JWE token and return the result", "args": [{ "name": "arg1", "type": "any" }] }, "concat": { "args": [{ "name": "value", "type": "any" }], "result": "text", "group": "text", "description": "Concatenates two values together" }, "sprintf": { "result": "text", "group": "text", "description": "formats text with variable substitution", "variadic": true }, "secureid_encode": { "args": [{ "name": "salt", "type": "text" }], "result": "text", "group": "security", "description": "Returns an encrypted version of the id" }, "secureid_decode": { "args": [{ "name": "salt", "type": "text" }], "result": "int", "group": "security", "description": "Returns the id of the original encode" }, "md5": { "args": [{ "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a MD5 signature representation of the value" }, "sha1": { "args": [{ "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a SHA1 signature representation of the value" }, "sha256": { "args": [{ "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a SHA256 signature representation of the value" }, "sha384": { "args": [{ "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a SHA384 signature representation of the value" }, "sha512": { "args": [{ "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a SHA512 signature representation of the value" }, "hmac_md5": { "args": [{ "name": "key", "type": "text" }, { "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a MD5 signature representation of the value using a shared secret via the HMAC method" }, "hmac_sha1": { "args": [{ "name": "key", "type": "text" }, { "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a SHA1 signature representation of the value using a shared secret via the HMAC method" }, "hmac_sha256": { "args": [{ "name": "key", "type": "text" }, { "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a SHA256 signature representation of the value using a shared secret via the HMAC method" }, "hmac_sha384": { "args": [{ "name": "key", "type": "text" }, { "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a SHA384 signature representation of the value using a shared secret via the HMAC method" }, "hmac_sha512": { "args": [{ "name": "key", "type": "text" }, { "name": "raw", "type": "bool", "optional": true }], "result": "text", "group": "security", "description": "Returns a SHA512 signature representation of the value using a shared secret via the HMAC method" }, "regex_quote": { "args": [{ "name": "delimiter", "type": "text", "optional": true }], "result": "text", "group": "text", "description": "Update the supplied text value to be properly escaped for regular expressions." }, "regex_replace": { "args": [{ "name": "replacement", "type": "text" }, { "name": "subject", "type": "text" }], "result": "text", "group": "text", "description": "Perform a regular expression search and replace on the supplied subject text. Direction: the piped value is the regex PATTERN; the `subject` argument is the text tested against it \u2014 reversed vs starts_with/contains. Build the pattern with c.regex(...) (delimiter-wrapped); a bare c.text(...) is rejected." }, "map": { "args": [{ "name": "code", "type": "text" }, { "name": "timeout", "type": "int", "optional": true }], "result": "any[]", "group": "array", "description": "Creates a new array with the results of calling a provided function on every element in the calling array." }, "filter": { "args": [{ "name": "code", "type": "text" }, { "name": "timeout", "type": "int", "optional": true }], "result": "any[]", "group": "array", "description": "Filters the elements of an array based on the code block returning true to keep the element or false to skip it." }, "some": { "args": [{ "name": "code", "type": "text" }, { "name": "timeout", "type": "int", "optional": true }], "result": "any[]", "group": "array", "description": "Checks if at least one element in the array passes the test implemented by the provided function." }, "every": { "args": [{ "name": "code", "type": "text" }, { "name": "timeout", "type": "int", "optional": true }], "result": "any[]", "group": "array", "description": "Checks if all elements in the array pass the test implemented by the provided function." }, "find": { "args": [{ "name": "code", "type": "text" }, { "name": "timeout", "type": "int", "optional": true }], "result": "any[]", "group": "array", "description": "Finds if all elements in the array pass the test implemented by the provided function." }, "findIndex": { "args": [{ "name": "code", "type": "text" }, { "name": "timeout", "type": "int", "optional": true }], "result": "any[]", "group": "array", "description": "Finds the index of the first element in the array that passes the test implemented by the provided function." }, "reduce": { "args": [{ "name": "initial_value", "type": "int" }, { "name": "code", "type": "text" }, { "name": "timeout", "type": "int", "optional": true }], "result": "any[]", "group": "array", "description": "Reduces the array to a single value using the code block to combine each element of the array." }, "pick": { "args": [{ "name": "keys", "type": "text" }], "result": "<T>", "group": "array", "description": "Pick keys from the object to create a new object of just those keys." }, "unpick": { "args": [{ "name": "keys", "type": "text" }], "result": "<T>", "group": "array", "description": "Remove keys from the object to create a new object of the remaining keys." }, "text_unescape": { "result": "text", "group": "text", "description": "Convert escaped sequences into their raw form. Ex: \\n becomes a newline, \\t becomes a tab." }, "text_escape": { "result": "text", "group": "text", "description": "Converts control characters into their escaped sequences. Ex: newlines as \\n, tabs as \\t" }, "sql_esc": { "result": "text", "group": "text", "description": "Wraps text in single quotes and escapes any single quotes within the text to prevent SQL injection attacks. This is useful for safely inserting user input into values within SQL queries." }, "sql_alias": { "result": "text", "group": "text", "description": "Wraps text in double quotes and escapes any double quotes within the text to prevent SQL injection attacks. This is useful for safely inserting user input into table names or aliases within SQL queries." }, "is_uuid": { "result": "bool", "group": "comparison", "description": "Returns whether or not the value is a valid UUID." }, "lambda": { "args": [{ "name": "code", "type": "text" }, { "name": "timeout", "type": "int", "optional": true }], "result": "any", "group": "transform", "description": "Business logic using JavaScript." }, "lower": { "result": "text", "group": "text", "description": "Converts all characters to lower case and returns the result" }, "upper": { "result": "text", "group": "text", "description": "Converts all characters to upper case and returns the result" }, "sub": { "args": [{ "name": "value", "type": "decimal" }], "result": "decimal", "group": "math", "description": "Subtracts 2 values together and returns the answer" }, "mul": { "args": [{ "name": "value", "type": "decimal" }], "result": "decimal", "group": "math", "description": "Multiplies 2 values together and returns the answer" }, "div": { "args": [{ "name": "value", "type": "decimal" }], "result": "decimal", "group": "math", "description": "Divides 2 values together and returns the answer" }, "fsort": { "args": [{ "name": "path", "type": "text", "optional": true }, { "name": "type", "type": "enum", "optional": true, "enum": ["text", "itext", "natural", "inatural", "number"] }, { "name": "asc", "type": "bool", "optional": true }], "result": "<T>[]", "group": "array", "description": 'Sort an array of elements with an optional path inside the element Only `type: "number"` compares NUMERICALLY. "text"/"itext" are case-sensitive/insensitive string compares, "natural"/"inatural" are the human-readable "a2 < a10" orderings, and the default is "itext". An unrecognized spelling silently falls through to "itext" rather than erroring, so a numeric sort MUST spell "number" \u2014 anything else returns the right elements in the wrong order. `path` drills into each element.' }, "array_shuffle": { "result": "<T>[]", "group": "array", "description": "Shuffles the order of the entries in the array." }, "array_diff": { "args": [{ "name": "value", "type": "<T>[]" }], "result": "<T>[]", "group": "array", "description": "Return the entries from the first array that are not in the second" }, "array_diff_assoc": { "args": [{ "name": "value", "type": "<T>[]" }], "result": "<T>[]", "group": "array", "description": "Return the entries from the first array that are not in the second" }, "array_intersect": { "args": [{ "name": "value", "type": "<T>[]" }], "result": "<T>[]", "group": "array", "description": "Return the entries from the first array that are also present in" }, "array_intersect_assoc": { "args": [{ "name": "value", "type": "<T>[]" }], "result": "<T>[]", "group": "array", "description": "Return the entries from the first array that are also present in" }, "array_merge": { "args": [{ "name": "value", "type": "<T>[]" }], "result": "<T>[]", "group": "array", "description": "Merge the first level of elements of both arrays together and" }, "array_merge_recursive": { "args": [{ "name": "value", "type": "<T>[]" }], "result": "<T>[]", "group": "array", "description": "Merge the elements from all levels of both arrays together and" }, "array_push": { "args": [{ "name": "value", "type": "<T>" }], "result": "<T>[]", "group": "array", "description": "Push an element on to the end of an array and return the new array" }, "array_pop": { "result": "<T>", "group": "array", "description": "Pops the last element of the array off and returns it" }, "array_unshift": { "args": [{ "name": "value", "type": "<T>" }], "result": "<T>[]", "group": "array", "description": "Push an element to the beginning of an array and return the new array" }, "array_shift": { "result": "<T>", "group": "array", "description": "Shifts the first element of the array off and returns it" }, "array_remove": { "args": [{ "name": "value", "type": "<T>" }, { "name": "path", "type": "text" }, { "name": "strict", "type": "bool", "optional": true }], "result": "<T>[]", "group": "array", "description": "Remove any elements from the array that match the supplied value" }, "num_min": { "args": [{ "name": "value", "type": "any" }], "result": "decimal", "group": "math", "description": "Returns the min both values" }, "num_max": { "args": [{ "name": "value", "type": "any" }], "result": "decimal", "group": "math", "description": "Returns the max both values" }, "eq": { "args": [{ "name": "value", "type": "<T>" }], "result": "bool", "group": "comparison", "description": "Returns a boolean if both values are equal" }, "ne": { "args": [{ "name": "value", "type": "<T>" }], "result": "bool", "group": "comparison", "description": "Returns a boolean if both values are not equal" }, "gt": { "args": [{ "name": "value", "type": "<T>" }], "result": "bool", "group": "comparison", "description": "Returns a boolean if the left value is greater than the right value" }, "gte": { "args": [{ "name": "value", "type": "<T>" }], "result": "bool", "group": "comparison", "description": "Returns a boolean if the left value is greater than or equal to the" }, "lt": { "args": [{ "name": "value", "type": "<T>" }], "result": "bool", "group": "comparison", "description": "Returns a boolean if the left value is less than the right value" }, "lte": { "args": [{ "name": "value", "type": "<T>" }], "result": "bool", "group": "comparison", "description": "Returns a boolean if the left value is less than or equal to the right value" }, "mod": { "args": [{ "name": "value", "type": "int" }], "result": "int", "group": "math", "description": "Modulus 2 values together and return the answer" }, "empty": { "result": "bool", "group": "comparison", "description": 'Returns whether or not the value is empty ("", null, 0, "0", false, [], {})' }, "array_slice": { "args": [{ "name": "offset", "type": "int" }, { "name": "length", "type": "int", "optional": true }], "result": "<T>[]", "group": "array", "description": "Extract a section from an array." }, "strip_tags": { "args": [{ "name": "exclude", "type": "text", "optional": true }], "result": "text", "group": "text", "description": "Removes HTML tags from a string" }, "strip_accents": { "result": "text", "group": "text", "description": "Removes accents from characters" }, "strpos": { "args": [{ "name": "search", "type": "text" }], "result": "int", "group": "text", "description": "Returns the index of the case-sensitive expression or false if it" }, "stripos": { "args": [{ "name": "search", "type": "text" }], "result": "int", "group": "text", "description": "Returns the index of the case-insensitive expression or false if it" }, "array_fill": { "args": [{ "name": "start", "type": "int" }, { "name": "count", "type": "int" }], "result": "any[]", "group": "manipulation", "description": "Create an array of a certain size with a default value." }, "array_fill_keys": { "args": [{ "name": "keys", "type": "any[]" }], "result": "any[]", "group": "manipulation", "description": "Create an array of keys with a default value." }, "array_keys": { "result": "text[]", "group": "array", "description": "Get the property keys of an object/array as a numerically indexed array." }, "array_values": { "result": "any[]", "group": "array", "description": "Get the property values of an object/array as a numerically indexed array" }, "array_entries": { "result": "any[]", "group": "array", "description": "Get the property entries of an object/array as a numerically" }, "to_epochms": { "args": [{ "name": "timezone", "type": "text", "optional": true }], "result": "epochms", "group": "transform", "description": "Converts a text expression (now, next friday, Jan 1 2000) to" }, "to_epoch_ms": { "args": [{ "name": "timezone", "type": "text", "optional": true }], "result": "int", "group": "transform", "description": "Converts a text expression (now, next friday, Jan 1 2000) to the" }, "to_epoch_sec": { "args": [{ "name": "timezone", "type": "text", "optional": true }], "result": "int", "group": "transform", "description": "Converts a text expression (now, next friday, Jan 1 2000) to the" }, "to_epoch_minute": { "args": [{ "name": "timezone", "type": "text", "optional": true }], "result": "int", "group": "transform", "description": "Converts a text expression (now, next friday, Jan 1 2000) to the" }, "to_epoch_hour": { "args": [{ "name": "timezone", "type": "text", "optional": true }], "result": "int", "group": "transform", "description": "Converts a text expression (now, next friday, Jan 1 2000) to the" }, "to_epoch_day": { "args": [{ "name": "timezone", "type": "text", "optional": true }], "result": "int", "group": "transform", "description": "Converts a text expression (now, next friday, Jan 1 2000) to the" }, "crypto_jws_encode": { "args": [{ "name": "headers", "type": "json" }, { "name": "key", "type": "json" }, { "name": "algorithm", "type": "enum" }, { "name": "ttl", "type": "int", "optional": true }], "result": "text", "group": "security", "description": "Encodes the value and return the result as a JWS token" }, "crypto_jws_decode": { "args": [{ "name": "check_claims", "type": "json" }, { "name": "key", "type": "json" }, { "name": "algorithm", "type": "enum" }, { "name": "timeDrift", "type": "int", "optional": true }], "result": "json", "group": "security", "description": "Decodes the JWS token and return the result" }, "crypto_jwe_encode": { "args": [{ "name": "headers", "type": "json" }, { "name": "key", "type": "json" }, { "name": "key_algorithm", "type": "enum" }, { "name": "content_algorithm", "type": "enum" }, { "name": "ttl", "type": "int", "optional": true }], "result": "text", "group": "security", "description": "Encodes the value and return the result as a JWE token" }, "crypto_jwe_decode": { "args": [{ "name": "check_claims", "type": "json" }, { "name": "key", "type": "json" }, { "name": "key_algorithm", "type": "enum" }, { "name": "content_algorithm", "type": "enum" }, { "name": "timeDrift", "type": "int", "optional": true }], "result": "json", "group": "security", "description": "Decodes the JWE token and return the result" }, "string_replace": { "args": [{ "name": "search", "type": "text" }, { "name": "replacement", "type": "text" }], "result": "text", "group": "text", "description": "Replace a text phrase with another" }, "uid": { "result": "int", "group": "security", "description": "Returns a unique 64bit unsigned int value seeded off the value" }, "uuid4": { "result": "text", "group": "security", "description": "Returns a universally unique identifier" }, "epochms_from_format": { "args": [{ "name": "format", "type": "text" }, { "name": "timezone", "type": "text", "optional": true }], "result": "text", "group": "timestamp", "description": "Parse a timestamp from a flexible format." }, "epochms_date": { "args": [{ "name": "format", "type": "text" }, { "name": "timezone", "type": "text", "optional": true }], "result": "text", "group": "timestamp", "description": "Converts a timestamp into a human readable formatted date based on" }, "epochms_transform": { "args": [{ "name": "format", "type": "text" }, { "name": "timezone", "type": "text", "optional": true }], "result": "text", "group": "timestamp", "description": "Takes a timestamp and applies a relative transformation to it. Ex." }, "epochms_add_secs": { "args": [{ "name": "seconds", "type": "int" }], "result": "epochms", "group": "timestamp", "description": "Add seconds to a timestamp. (negative values are ok)" }, "epochms_add_ms": { "args": [{ "name": "milliseconds", "type": "int" }], "result": "epochms", "group": "timestamp", "description": "Add milliseconds to a timestamp. (negative values are ok)" }, "regex_test": { "args": [{ "name": "subject", "type": "text" }], "result": "bool", "group": "text", "description": "Tests if a regular expression matches the supplied subject text. Direction: the piped value is the regex PATTERN; the `subject` argument is the text tested against it \u2014 reversed vs starts_with/contains. Build the pattern with c.regex(...) (delimiter-wrapped); a bare c.text(...) is rejected." }, "regex_match": { "args": [{ "name": "subject", "type": "text" }], "result": "text[]", "group": "text", "description": "Return the first set of matches performed by a regular expression Direction: the piped value is the regex PATTERN; the `subject` argument is the text tested against it \u2014 reversed vs starts_with/contains. Build the pattern with c.regex(...) (delimiter-wrapped); a bare c.text(...) is rejected." }, "regex_match_all": { "args": [{ "name": "subject", "type": "text" }], "result": "text[]", "group": "text", "description": "Return all matches performed by a regular expression on the Direction: the piped value is the regex PATTERN; the `subject` argument is the text tested against it \u2014 reversed vs starts_with/contains. Build the pattern with c.regex(...) (delimiter-wrapped); a bare c.text(...) is rejected." }, "null": { "result": "null", "group": "misc", "description": "Returns a null value." } };
|
|
1698
|
+
var FILTER_REQUIRED_ARGS = { "add": 1, "append": 2, "array_diff": 1, "array_diff_assoc": 1, "array_fill": 2, "array_fill_keys": 1, "array_intersect": 1, "array_intersect_assoc": 1, "array_merge": 1, "array_merge_recursive": 1, "array_push": 1, "array_remove": 2, "array_slice": 1, "array_unshift": 1, "base_convert": 2, "bitwise_and": 1, "bitwise_or": 1, "bitwise_xor": 1, "concat": 1, "contains": 1, "convert_encoding": 2, "create_object": 1, "crypto_jwe_decode": 4, "crypto_jwe_encode": 4, "crypto_jws_decode": 3, "crypto_jws_encode": 3, "csv_create": 4, "csv_decode": 3, "csv_encode": 3, "csv_parse": 3, "decrypt": 3, "div": 1, "encrypt": 3, "ends_with": 1, "epochms_add_ms": 1, "epochms_add_secs": 1, "epochms_date": 1, "epochms_from_format": 1, "epochms_transform": 1, "eq": 1, "every": 1, "filter": 1, "find": 1, "findIndex": 1, "first_notempty": 1, "first_notnull": 1, "get": 1, "gt": 1, "gte": 1, "has": 1, "hmac_md5": 1, "hmac_sha1": 1, "hmac_sha256": 1, "hmac_sha384": 1, "hmac_sha512": 1, "icontains": 1, "iends_with": 1, "in": 1, "index_by": 1, "istarts_with": 1, "join": 1, "jwe_decode": 1, "jwe_encode": 1, "lambda": 1, "log": 1, "lt": 1, "lte": 1, "map": 1, "mod": 1, "mul": 1, "ne": 1, "num_max": 1, "num_min": 1, "number_format": 3, "pick": 1, "pow": 1, "prepend": 2, "range": 2, "reduce": 2, "regex_match": 1, "regex_match_all": 1, "regex_replace": 2, "regex_test": 1, "secureid_decode": 1, "secureid_encode": 1, "set": 2, "set_conditional": 3, "set_ifnotempty": 2, "set_ifnotnull": 2, "some": 1, "split": 1, "starts_with": 1, "string_replace": 2, "stripos": 1, "strpos": 1, "sub": 1, "substr": 2, "transform": 1, "unpick": 1, "unset": 1, "url_addarg": 2, "url_delarg": 1, "url_getarg": 1, "url_hasarg": 1 };
|
|
1699
|
+
var fl = {
|
|
1700
|
+
"abs": (
|
|
1701
|
+
/** Returns the absolute value (group: math, result: decimal) */
|
|
1702
|
+
() => filter("abs")
|
|
1703
|
+
),
|
|
1704
|
+
"acos": (
|
|
1705
|
+
/** Calculates the arc cosine of the supplied value in radians (group: math, result: decimal) */
|
|
1706
|
+
() => filter("acos")
|
|
1707
|
+
),
|
|
1708
|
+
"acosh": (
|
|
1709
|
+
/** Calculates the inverse hyperbolic cosine of the supplied value in radians (group: math, result: decimal) */
|
|
1710
|
+
() => filter("acosh")
|
|
1711
|
+
),
|
|
1712
|
+
"add": (
|
|
1713
|
+
/** Add 2 values together and return the answer (group: math, result: decimal) */
|
|
1714
|
+
slotted("add", ["value"], 1)
|
|
1715
|
+
),
|
|
1716
|
+
"addslashes": (
|
|
1717
|
+
/** Adds a backslash to the following characters: single quote, double quote, backslash, and null character. (group: text, result: text) */
|
|
1718
|
+
() => filter("addslashes")
|
|
1719
|
+
),
|
|
1720
|
+
"append": (
|
|
1721
|
+
/** Push an element on to the end of an array within an object and return the updated object (group: array, result: <T>[]) */
|
|
1722
|
+
slotted("append", ["value", "path"], 2)
|
|
1723
|
+
),
|
|
1724
|
+
"array_diff": (
|
|
1725
|
+
/** Return the entries from the first array that are not in the second (group: array, result: <T>[]) */
|
|
1726
|
+
slotted("array_diff", ["value"], 1)
|
|
1727
|
+
),
|
|
1728
|
+
"array_diff_assoc": (
|
|
1729
|
+
/** Return the entries from the first array that are not in the second (group: array, result: <T>[]) */
|
|
1730
|
+
slotted("array_diff_assoc", ["value"], 1)
|
|
1731
|
+
),
|
|
1732
|
+
"array_entries": (
|
|
1733
|
+
/** Get the property entries of an object/array as a numerically (group: array, result: any[]) */
|
|
1734
|
+
() => filter("array_entries")
|
|
1735
|
+
),
|
|
1736
|
+
"array_fill": (
|
|
1737
|
+
/** Create an array of a certain size with a default value. (group: manipulation, result: any[]) */
|
|
1738
|
+
slotted("array_fill", ["start", "count"], 2)
|
|
1739
|
+
),
|
|
1740
|
+
"array_fill_keys": (
|
|
1741
|
+
/** Create an array of keys with a default value. (group: manipulation, result: any[]) */
|
|
1742
|
+
slotted("array_fill_keys", ["keys"], 1)
|
|
1743
|
+
),
|
|
1744
|
+
"array_intersect": (
|
|
1745
|
+
/** Return the entries from the first array that are also present in (group: array, result: <T>[]) */
|
|
1746
|
+
slotted("array_intersect", ["value"], 1)
|
|
1747
|
+
),
|
|
1748
|
+
"array_intersect_assoc": (
|
|
1749
|
+
/** Return the entries from the first array that are also present in (group: array, result: <T>[]) */
|
|
1750
|
+
slotted("array_intersect_assoc", ["value"], 1)
|
|
1751
|
+
),
|
|
1752
|
+
"array_keys": (
|
|
1753
|
+
/** Get the property keys of an object/array as a numerically indexed array. (group: array, result: text[]) */
|
|
1754
|
+
() => filter("array_keys")
|
|
1755
|
+
),
|
|
1756
|
+
"array_merge": (
|
|
1757
|
+
/** Merge the first level of elements of both arrays together and (group: array, result: <T>[]) */
|
|
1758
|
+
slotted("array_merge", ["value"], 1, false)
|
|
1759
|
+
),
|
|
1760
|
+
"array_merge_recursive": (
|
|
1761
|
+
/** Merge the elements from all levels of both arrays together and (group: array, result: <T>[]) */
|
|
1762
|
+
slotted("array_merge_recursive", ["value"], 1, false)
|
|
1763
|
+
),
|
|
1764
|
+
"array_pop": (
|
|
1765
|
+
/** Pops the last element of the array off and returns it (group: array, result: <T>) */
|
|
1766
|
+
() => filter("array_pop")
|
|
1767
|
+
),
|
|
1768
|
+
"array_push": (
|
|
1769
|
+
/** Push an element on to the end of an array and return the new array (group: array, result: <T>[]) */
|
|
1770
|
+
slotted("array_push", ["value"], 1)
|
|
1771
|
+
),
|
|
1772
|
+
"array_remove": (
|
|
1773
|
+
/** Remove any elements from the array that match the supplied value (group: array, result: <T>[]) */
|
|
1774
|
+
slotted("array_remove", ["value", "path", "strict"], 2)
|
|
1775
|
+
),
|
|
1776
|
+
"array_shift": (
|
|
1777
|
+
/** Shifts the first element of the array off and returns it (group: array, result: <T>) */
|
|
1778
|
+
() => filter("array_shift")
|
|
1779
|
+
),
|
|
1780
|
+
"array_shuffle": (
|
|
1781
|
+
/** Shuffles the order of the entries in the array. (group: array, result: <T>[]) */
|
|
1782
|
+
() => filter("array_shuffle")
|
|
1783
|
+
),
|
|
1784
|
+
"array_slice": (
|
|
1785
|
+
/** Extract a section from an array. (group: array, result: <T>[]) */
|
|
1786
|
+
slotted("array_slice", ["offset", "length"], 1)
|
|
1787
|
+
),
|
|
1788
|
+
"array_unshift": (
|
|
1789
|
+
/** Push an element to the beginning of an array and return the new array (group: array, result: <T>[]) */
|
|
1790
|
+
slotted("array_unshift", ["value"], 1)
|
|
1791
|
+
),
|
|
1792
|
+
"array_values": (
|
|
1793
|
+
/** Get the property values of an object/array as a numerically indexed array (group: array, result: any[]) */
|
|
1794
|
+
() => filter("array_values")
|
|
1795
|
+
),
|
|
1796
|
+
"asin": (
|
|
1797
|
+
/** Calculates the arc sine of the supplied value in radians (group: math, result: decimal) */
|
|
1798
|
+
() => filter("asin")
|
|
1799
|
+
),
|
|
1800
|
+
"asinh": (
|
|
1801
|
+
/** Calculates the inverse hyperbolic sine of the supplied value in radians (group: math, result: decimal) */
|
|
1802
|
+
() => filter("asinh")
|
|
1803
|
+
),
|
|
1804
|
+
"atan": (
|
|
1805
|
+
/** Calculates the arc tangent of the supplied value in radians (group: math, result: decimal) */
|
|
1806
|
+
() => filter("atan")
|
|
1807
|
+
),
|
|
1808
|
+
"atanh": (
|
|
1809
|
+
/** Calculates the inverse hyperbolic tangent of the supplied value in radians (group: math, result: decimal) */
|
|
1810
|
+
() => filter("atanh")
|
|
1811
|
+
),
|
|
1812
|
+
"avg": (
|
|
1813
|
+
/** Returns the average of the values of the array (group: math, result: decimal) */
|
|
1814
|
+
() => filter("avg")
|
|
1815
|
+
),
|
|
1816
|
+
"base64_decode": (
|
|
1817
|
+
/** Decodes the value represented as base64 text and returns the result (group: transform, result: text) */
|
|
1818
|
+
() => filter("base64_decode")
|
|
1819
|
+
),
|
|
1820
|
+
"base64_decode_urlsafe": (
|
|
1821
|
+
/** Decodes the value represented as base64 urlsafe text and returns the result (group: transform, result: text) */
|
|
1822
|
+
() => filter("base64_decode_urlsafe")
|
|
1823
|
+
),
|
|
1824
|
+
"base64_encode": (
|
|
1825
|
+
/** Encodes the value and returns the result as base64 text (group: transform, result: text) */
|
|
1826
|
+
() => filter("base64_encode")
|
|
1827
|
+
),
|
|
1828
|
+
"base64_encode_urlsafe": (
|
|
1829
|
+
/** Encodes the value and returns the result as base64 urlsafe text (group: transform, result: text) */
|
|
1830
|
+
() => filter("base64_encode_urlsafe")
|
|
1831
|
+
),
|
|
1832
|
+
"base_convert": (
|
|
1833
|
+
/** Converts a value between two bases (group: transform, result: text) */
|
|
1834
|
+
slotted("base_convert", ["from_base", "to_base"], 2)
|
|
1835
|
+
),
|
|
1836
|
+
"bin2hex": (
|
|
1837
|
+
/** Converts a binary value into its hex equivalent (group: transform, result: text) */
|
|
1838
|
+
() => filter("bin2hex")
|
|
1839
|
+
),
|
|
1840
|
+
"bindec": (
|
|
1841
|
+
/** Converts a binary string (i.e. 01010) into its decimal equivalent (group: transform, result: text) */
|
|
1842
|
+
() => filter("bindec")
|
|
1843
|
+
),
|
|
1844
|
+
"bitwise_and": (
|
|
1845
|
+
/** Bitwise AND 2 values together and return the answer (group: math, result: int) */
|
|
1846
|
+
slotted("bitwise_and", ["value"], 1)
|
|
1847
|
+
),
|
|
1848
|
+
"bitwise_not": (
|
|
1849
|
+
/** Returns the existing value with its bits flipped (group: comparison, result: bool) */
|
|
1850
|
+
() => filter("bitwise_not")
|
|
1851
|
+
),
|
|
1852
|
+
"bitwise_or": (
|
|
1853
|
+
/** Bitwise OR 2 values together and return the answer (group: math, result: int) */
|
|
1854
|
+
slotted("bitwise_or", ["value"], 1)
|
|
1855
|
+
),
|
|
1856
|
+
"bitwise_xor": (
|
|
1857
|
+
/** Bitwise XOR 2 values together and return the answer (group: math, result: int) */
|
|
1858
|
+
slotted("bitwise_xor", ["value"], 1)
|
|
1859
|
+
),
|
|
1860
|
+
"capitalize": (
|
|
1861
|
+
/** Converts the first letter of each word to a capital letter (group: text, result: text) */
|
|
1862
|
+
() => filter("capitalize")
|
|
1863
|
+
),
|
|
1864
|
+
"ceil": (
|
|
1865
|
+
/** Round a decimal up to its integer equivalent (group: math, result: int) */
|
|
1866
|
+
() => filter("ceil")
|
|
1867
|
+
),
|
|
1868
|
+
"concat": (
|
|
1869
|
+
/** Concatenates two values together (group: text, result: text) */
|
|
1870
|
+
slotted("concat", ["value"], 1, false)
|
|
1871
|
+
),
|
|
1872
|
+
"contains": (
|
|
1873
|
+
/** Returns whether or not the expression is found Direction: the piped value is the subject text; the argument is the substring searched for. (group: text, result: bool) */
|
|
1874
|
+
slotted("contains", ["search"], 1)
|
|
1875
|
+
),
|
|
1876
|
+
"convert_encoding": (
|
|
1877
|
+
/** Convert the character encoding of the supplied text (group: text, result: text) */
|
|
1878
|
+
slotted("convert_encoding", ["to", "from"], 2)
|
|
1879
|
+
),
|
|
1880
|
+
"cos": (
|
|
1881
|
+
/** Calculates the cosine of the supplied value in radians (group: math, result: decimal) */
|
|
1882
|
+
() => filter("cos")
|
|
1883
|
+
),
|
|
1884
|
+
"count": (
|
|
1885
|
+
/** Return the number of items in an object/array (group: array, result: int) */
|
|
1886
|
+
() => filter("count")
|
|
1887
|
+
),
|
|
1888
|
+
"create_object": (
|
|
1889
|
+
/** Creates an object based on a list of keys and a list of values (group: transform, result: any) */
|
|
1890
|
+
slotted("create_object", ["values"], 1)
|
|
1891
|
+
),
|
|
1892
|
+
"create_object_from_entries": (
|
|
1893
|
+
/** Creates an object based on an array of key/value pairs. (i.e. same result as the entries filter) (group: transform, result: any) */
|
|
1894
|
+
() => filter("create_object_from_entries")
|
|
1895
|
+
),
|
|
1896
|
+
"crypto_jwe_decode": (
|
|
1897
|
+
/** Decodes the JWE token and return the result (group: security, result: json) */
|
|
1898
|
+
slotted("crypto_jwe_decode", ["check_claims", "key", "key_algorithm", "content_algorithm", "timeDrift"], 4)
|
|
1899
|
+
),
|
|
1900
|
+
"crypto_jwe_encode": (
|
|
1901
|
+
/** Encodes the value and return the result as a JWE token (group: security, result: text) */
|
|
1902
|
+
slotted("crypto_jwe_encode", ["headers", "key", "key_algorithm", "content_algorithm", "ttl"], 4)
|
|
1903
|
+
),
|
|
1904
|
+
"crypto_jws_decode": (
|
|
1905
|
+
/** Decodes the JWS token and return the result (group: security, result: json) */
|
|
1906
|
+
slotted("crypto_jws_decode", ["check_claims", "key", "algorithm", "timeDrift"], 3)
|
|
1907
|
+
),
|
|
1908
|
+
"crypto_jws_encode": (
|
|
1909
|
+
/** Encodes the value and return the result as a JWS token (group: security, result: text) */
|
|
1910
|
+
slotted("crypto_jws_encode", ["headers", "key", "algorithm", "ttl"], 3)
|
|
1911
|
+
),
|
|
1912
|
+
"csv_create": (
|
|
1913
|
+
/** Creates a CSV format data stream from a list of column names and their corresponding data rows. Direction: the piped value is the list of COLUMN NAMES (written as the header line); the `rows` argument carries the data rows. This is the header-writing counterpart to `csv_encode`, which emits values only. (group: transform, result: text) */
|
|
1914
|
+
slotted("csv_create", ["rows", "separator", "enclosure", "escape"], 4)
|
|
1915
|
+
),
|
|
1916
|
+
"csv_decode": (
|
|
1917
|
+
/** Decodes the value represented in the CSV format and returns the result (group: transform, result: any) */
|
|
1918
|
+
slotted("csv_decode", ["separator", "enclosure", "escape"], 3)
|
|
1919
|
+
),
|
|
1920
|
+
"csv_encode": (
|
|
1921
|
+
/** Encodes the value and returns the result in CSV format Writes NO header row — values only. Each row contributes its own values in THAT row's key order, with no normalization across rows, so rows whose keys differ in order or count silently misalign columns rather than erroring. Non-scalar cells (nested objects/arrays) are JSON-encoded into the cell, and `false` writes as empty. If the piped array's elements are scalars rather than objects, the whole array is treated as ONE row and you get a single line. For a header, use `csv_create`. (group: transform, result: text) */
|
|
1922
|
+
slotted("csv_encode", ["separator", "enclosure", "escape"], 3)
|
|
1923
|
+
),
|
|
1924
|
+
"csv_parse": (
|
|
1925
|
+
/** Parse the contents of a CSV file and convert it into an array of objects. (group: transform, result: any) */
|
|
1926
|
+
slotted("csv_parse", ["separator", "enclosure", "escape"], 3)
|
|
1927
|
+
),
|
|
1928
|
+
"decbin": (
|
|
1929
|
+
/** Converts a decimal value into its binary string (i.e. 01010) equivalent (group: transform, result: text) */
|
|
1930
|
+
() => filter("decbin")
|
|
1931
|
+
),
|
|
1932
|
+
"dechex": (
|
|
1933
|
+
/** Converts a decimal value into its hex equivalent (group: transform, result: text) */
|
|
1934
|
+
() => filter("dechex")
|
|
1935
|
+
),
|
|
1936
|
+
"decoct": (
|
|
1937
|
+
/** Converts a decimal value into its octal equivalent (group: transform, result: text) */
|
|
1938
|
+
() => filter("decoct")
|
|
1939
|
+
),
|
|
1940
|
+
"decrypt": (
|
|
1941
|
+
/** Decrypts the value and returns the result. (group: security, result: text) */
|
|
1942
|
+
slotted("decrypt", ["algorithm", "key", "iv"], 3)
|
|
1943
|
+
),
|
|
1944
|
+
"deg2rad": (
|
|
1945
|
+
/** Convert degrees to radians (group: math, result: decimal) */
|
|
1946
|
+
() => filter("deg2rad")
|
|
1947
|
+
),
|
|
1948
|
+
"detect_encoding": (
|
|
1949
|
+
/** Detect the character encoding of the supplied text (group: text, result: text) */
|
|
1950
|
+
slotted("detect_encoding", ["encodings"], 0)
|
|
1951
|
+
),
|
|
1952
|
+
"div": (
|
|
1953
|
+
/** Divides 2 values together and returns the answer (group: math, result: decimal) */
|
|
1954
|
+
slotted("div", ["value"], 1)
|
|
1955
|
+
),
|
|
1956
|
+
"empty": (
|
|
1957
|
+
/** Returns whether or not the value is empty ("", null, 0, "0", false, [], {}) (group: comparison, result: bool) */
|
|
1958
|
+
() => filter("empty")
|
|
1959
|
+
),
|
|
1960
|
+
"encrypt": (
|
|
1961
|
+
/** Encrypts the value and returns the result in raw binary form. (group: security, result: text) */
|
|
1962
|
+
slotted("encrypt", ["algorithm", "key", "iv"], 3)
|
|
1963
|
+
),
|
|
1964
|
+
"ends_with": (
|
|
1965
|
+
/** Returns whether or not the expression is present at the end Direction: the piped value is the subject text; the argument is the substring searched for. (group: text, result: bool) */
|
|
1966
|
+
slotted("ends_with", ["search"], 1)
|
|
1967
|
+
),
|
|
1968
|
+
"epochms_add_ms": (
|
|
1969
|
+
/** Add milliseconds to a timestamp. (negative values are ok) (group: timestamp, result: epochms) */
|
|
1970
|
+
slotted("epochms_add_ms", ["milliseconds"], 1)
|
|
1971
|
+
),
|
|
1972
|
+
"epochms_add_secs": (
|
|
1973
|
+
/** Add seconds to a timestamp. (negative values are ok) (group: timestamp, result: epochms) */
|
|
1974
|
+
slotted("epochms_add_secs", ["seconds"], 1)
|
|
1975
|
+
),
|
|
1976
|
+
"epochms_date": (
|
|
1977
|
+
/** Converts a timestamp into a human readable formatted date based on (group: timestamp, result: text) */
|
|
1978
|
+
slotted("epochms_date", ["format", "timezone"], 1)
|
|
1979
|
+
),
|
|
1980
|
+
"epochms_from_format": (
|
|
1981
|
+
/** Parse a timestamp from a flexible format. (group: timestamp, result: text) */
|
|
1982
|
+
slotted("epochms_from_format", ["format", "timezone"], 1)
|
|
1983
|
+
),
|
|
1984
|
+
"epochms_transform": (
|
|
1985
|
+
/** Takes a timestamp and applies a relative transformation to it. Ex. (group: timestamp, result: text) */
|
|
1986
|
+
slotted("epochms_transform", ["format", "timezone"], 1)
|
|
1987
|
+
),
|
|
1988
|
+
"eq": (
|
|
1989
|
+
/** Returns a boolean if both values are equal (group: comparison, result: bool) */
|
|
1990
|
+
slotted("eq", ["value"], 1)
|
|
1991
|
+
),
|
|
1992
|
+
"escape": (
|
|
1993
|
+
/** Converts special characters into their escaped variants. Ex: for tabs and (group: text, result: text) */
|
|
1994
|
+
() => filter("escape")
|
|
1995
|
+
),
|
|
1996
|
+
"even": (
|
|
1997
|
+
/** Returns whether or not the value is even (group: comparison, result: bool) */
|
|
1998
|
+
() => filter("even")
|
|
1999
|
+
),
|
|
2000
|
+
"every": (
|
|
2001
|
+
/** Checks if all elements in the array pass the test implemented by the provided function. (group: array, result: any[]) */
|
|
2002
|
+
slotted("every", ["code", "timeout"], 1)
|
|
2003
|
+
),
|
|
2004
|
+
"exp": (
|
|
2005
|
+
/** Returns the exponent of mathematical expression "e" (group: math, result: decimal) */
|
|
2006
|
+
() => filter("exp")
|
|
2007
|
+
),
|
|
2008
|
+
"filter": (
|
|
2009
|
+
/** Filters the elements of an array based on the code block returning true to keep the element or false to skip it. (group: array, result: any[]) */
|
|
2010
|
+
slotted("filter", ["code", "timeout"], 1)
|
|
2011
|
+
),
|
|
2012
|
+
"filter_empty": (
|
|
2013
|
+
/** Returns a new array with only entries that are not empty ("", null, 0, "0", false, [], {}) (group: array, result: <T>[]) */
|
|
2014
|
+
slotted("filter_empty", ["path"], 0)
|
|
2015
|
+
),
|
|
2016
|
+
"filter_empty_array": (
|
|
2017
|
+
/** Returns a new array with only entries that are not an empty array `[]` (group: array, result: <T>[]) */
|
|
2018
|
+
slotted("filter_empty_array", ["path"], 0)
|
|
2019
|
+
),
|
|
2020
|
+
"filter_empty_object": (
|
|
2021
|
+
/** Returns a new array with only entries that are not an empty object `{}` (group: array, result: <T>[]) */
|
|
2022
|
+
slotted("filter_empty_object", ["path"], 0)
|
|
2023
|
+
),
|
|
2024
|
+
"filter_empty_text": (
|
|
2025
|
+
/** Returns a new array with only entries that are not empty ("", null, 0, "0", false, [], {}) (group: array, result: <T>[]) */
|
|
2026
|
+
slotted("filter_empty_text", ["path"], 0)
|
|
2027
|
+
),
|
|
2028
|
+
"filter_false": (
|
|
2029
|
+
/** Returns a new array with only entries that are not false (group: array, result: <T>[]) */
|
|
2030
|
+
slotted("filter_false", ["path"], 0)
|
|
2031
|
+
),
|
|
2032
|
+
"filter_null": (
|
|
2033
|
+
/** Returns a new array with only entries that are not null (group: array, result: <T>[]) */
|
|
2034
|
+
slotted("filter_null", ["path"], 0)
|
|
2035
|
+
),
|
|
2036
|
+
"filter_zero": (
|
|
2037
|
+
/** Returns a new array with only entries that are not zero (group: array, result: <T>[]) */
|
|
2038
|
+
slotted("filter_zero", ["path"], 0)
|
|
2039
|
+
),
|
|
2040
|
+
"find": (
|
|
2041
|
+
/** Finds if all elements in the array pass the test implemented by the provided function. (group: array, result: any[]) */
|
|
2042
|
+
slotted("find", ["code", "timeout"], 1)
|
|
2043
|
+
),
|
|
2044
|
+
"findIndex": (
|
|
2045
|
+
/** Finds the index of the first element in the array that passes the test implemented by the provided function. (group: array, result: any[]) */
|
|
2046
|
+
slotted("findIndex", ["code", "timeout"], 1)
|
|
2047
|
+
),
|
|
2048
|
+
"first": (
|
|
2049
|
+
/** Get the first entry of an array (group: array, result: <T>) */
|
|
2050
|
+
() => filter("first")
|
|
2051
|
+
),
|
|
2052
|
+
"first_notempty": (
|
|
2053
|
+
/** Returns the first value that is not empty - i.e. not ("", null, 0, "0", false, [], {}) (group: manipulation, result: any) */
|
|
2054
|
+
slotted("first_notempty", ["value"], 1)
|
|
2055
|
+
),
|
|
2056
|
+
"first_notnull": (
|
|
2057
|
+
/** Returns the first value that is not null (group: manipulation, result: any) */
|
|
2058
|
+
slotted("first_notnull", ["value"], 1)
|
|
2059
|
+
),
|
|
2060
|
+
"flatten": (
|
|
2061
|
+
/** Flattens a multidimensional array into a single level array of values. (group: array, result: <T>[]) */
|
|
2062
|
+
() => filter("flatten")
|
|
2063
|
+
),
|
|
2064
|
+
"floor": (
|
|
2065
|
+
/** Round a decimal down to its integer equivalent (group: math, result: int) */
|
|
2066
|
+
() => filter("floor")
|
|
2067
|
+
),
|
|
2068
|
+
"from_utf8": (
|
|
2069
|
+
/** Convert the supplied text from UTF-8 to its binary form (ISO-8859-1). (group: text, result: text) */
|
|
2070
|
+
() => filter("from_utf8")
|
|
2071
|
+
),
|
|
2072
|
+
"fsort": (
|
|
2073
|
+
/** Sort an array of elements with an optional path inside the element Only `type: "number"` compares NUMERICALLY. "text"/"itext" are case-sensitive/insensitive string compares, "natural"/"inatural" are the human-readable "a2 < a10" orderings, and the default is "itext". An unrecognized spelling silently falls through to "itext" rather than erroring, so a numeric sort MUST spell "number" — anything else returns the right elements in the wrong order. `path` drills into each element. (group: array, result: <T>[]) */
|
|
2074
|
+
slotted("fsort", ["path", "type", "asc"], 0)
|
|
2075
|
+
),
|
|
2076
|
+
"get": (
|
|
2077
|
+
/** Returns the value of an object at the specified path (group: manipulation, result: any) */
|
|
2078
|
+
slotted("get", ["path", "default_"], 1, false)
|
|
2079
|
+
),
|
|
2080
|
+
"gt": (
|
|
2081
|
+
/** Returns a boolean if the left value is greater than the right value (group: comparison, result: bool) */
|
|
2082
|
+
slotted("gt", ["value"], 1)
|
|
2083
|
+
),
|
|
2084
|
+
"gte": (
|
|
2085
|
+
/** Returns a boolean if the left value is greater than or equal to the (group: comparison, result: bool) */
|
|
2086
|
+
slotted("gte", ["value"], 1)
|
|
2087
|
+
),
|
|
2088
|
+
"has": (
|
|
2089
|
+
/** Returns the existence of whether or not something is present in the object at the specified path (group: manipulation, result: bool) */
|
|
2090
|
+
slotted("has", ["path"], 1)
|
|
2091
|
+
),
|
|
2092
|
+
"hex2bin": (
|
|
2093
|
+
/** Converts a hex value into its binary equivalent (group: transform, result: text) */
|
|
2094
|
+
() => filter("hex2bin")
|
|
2095
|
+
),
|
|
2096
|
+
"hexdec": (
|
|
2097
|
+
/** Converts a hex value into its decimal equivalent (group: transform, result: text) */
|
|
2098
|
+
() => filter("hexdec")
|
|
2099
|
+
),
|
|
2100
|
+
"hmac_md5": (
|
|
2101
|
+
/** Returns a MD5 signature representation of the value using a shared secret via the HMAC method (group: security, result: text) */
|
|
2102
|
+
slotted("hmac_md5", ["key", "raw"], 1)
|
|
2103
|
+
),
|
|
2104
|
+
"hmac_sha1": (
|
|
2105
|
+
/** Returns a SHA1 signature representation of the value using a shared secret via the HMAC method (group: security, result: text) */
|
|
2106
|
+
slotted("hmac_sha1", ["key", "raw"], 1)
|
|
2107
|
+
),
|
|
2108
|
+
"hmac_sha256": (
|
|
2109
|
+
/** Returns a SHA256 signature representation of the value using a shared secret via the HMAC method (group: security, result: text) */
|
|
2110
|
+
slotted("hmac_sha256", ["key", "raw"], 1)
|
|
2111
|
+
),
|
|
2112
|
+
"hmac_sha384": (
|
|
2113
|
+
/** Returns a SHA384 signature representation of the value using a shared secret via the HMAC method (group: security, result: text) */
|
|
2114
|
+
slotted("hmac_sha384", ["key", "raw"], 1)
|
|
2115
|
+
),
|
|
2116
|
+
"hmac_sha512": (
|
|
2117
|
+
/** Returns a SHA512 signature representation of the value using a shared secret via the HMAC method (group: security, result: text) */
|
|
2118
|
+
slotted("hmac_sha512", ["key", "raw"], 1)
|
|
2119
|
+
),
|
|
2120
|
+
"icontains": (
|
|
2121
|
+
/** Returns whether or not the case-insensitive expression is found Direction: the piped value is the subject text; the argument is the substring searched for. (group: text, result: bool) */
|
|
2122
|
+
slotted("icontains", ["search"], 1)
|
|
2123
|
+
),
|
|
2124
|
+
"iends_with": (
|
|
2125
|
+
/** Returns whether or not the case-insensitive expression is present at the end Direction: the piped value is the subject text; the argument is the substring searched for. (group: text, result: bool) */
|
|
2126
|
+
slotted("iends_with", ["search"], 1)
|
|
2127
|
+
),
|
|
2128
|
+
"in": (
|
|
2129
|
+
/** Returns whether or not the value is in the array (group: comparison, result: bool) */
|
|
2130
|
+
slotted("in", ["search"], 1)
|
|
2131
|
+
),
|
|
2132
|
+
"index_by": (
|
|
2133
|
+
/** Groups the piped array into an OBJECT keyed by each item's path — every value is an ARRAY of the items sharing that key, even when only one item does. So a lookup reads `idx[key][0]`, not `idx[key]`; the singular spelling yields null rather than erroring. Items whose path is missing or non-scalar are dropped. (group: array, result: { [key: string]: <T>[] }) */
|
|
2134
|
+
slotted("index_by", ["path"], 1, false)
|
|
2135
|
+
),
|
|
2136
|
+
"is_array": (
|
|
2137
|
+
/** Returns whether or not the value is a numerical indexed array. (group: comparison, result: bool) */
|
|
2138
|
+
() => filter("is_array")
|
|
2139
|
+
),
|
|
2140
|
+
"is_bool": (
|
|
2141
|
+
/** Returns whether or not the value is a boolean. (group: comparison, result: bool) */
|
|
2142
|
+
() => filter("is_bool")
|
|
2143
|
+
),
|
|
2144
|
+
"is_decimal": (
|
|
2145
|
+
/** Returns whether or not the value is a decimal value. (group: comparison, result: bool) */
|
|
2146
|
+
() => filter("is_decimal")
|
|
2147
|
+
),
|
|
2148
|
+
"is_int": (
|
|
2149
|
+
/** Returns whether or not the value is an integer. (group: comparison, result: bool) */
|
|
2150
|
+
() => filter("is_int")
|
|
2151
|
+
),
|
|
2152
|
+
"is_object": (
|
|
2153
|
+
/** Returns whether or not the value is an object. (group: comparison, result: bool) */
|
|
2154
|
+
() => filter("is_object")
|
|
2155
|
+
),
|
|
2156
|
+
"is_text": (
|
|
2157
|
+
/** Returns whether or not the value is text. (group: comparison, result: bool) */
|
|
2158
|
+
() => filter("is_text")
|
|
2159
|
+
),
|
|
2160
|
+
"is_uuid": (
|
|
2161
|
+
/** Returns whether or not the value is a valid UUID. (group: comparison, result: bool) */
|
|
2162
|
+
() => filter("is_uuid")
|
|
2163
|
+
),
|
|
2164
|
+
"istarts_with": (
|
|
2165
|
+
/** Returns whether or not the case-insensitive expression is present at the beginning Direction: the piped value is the subject text; the argument is the substring searched for. (group: text, result: bool) */
|
|
2166
|
+
slotted("istarts_with", ["search"], 1)
|
|
2167
|
+
),
|
|
2168
|
+
"join": (
|
|
2169
|
+
/** Joins an array into a text string via the separator and returns the result (group: array, result: text) */
|
|
2170
|
+
slotted("join", ["separator"], 1)
|
|
2171
|
+
),
|
|
2172
|
+
"json_decode": (
|
|
2173
|
+
/** Decodes the value represented as json and returns the result (group: transform, result: any) */
|
|
2174
|
+
() => filter("json_decode")
|
|
2175
|
+
),
|
|
2176
|
+
"json_encode": (
|
|
2177
|
+
/** Encodes the value and returns the result as json text (group: transform, result: text) */
|
|
2178
|
+
() => filter("json_encode")
|
|
2179
|
+
),
|
|
2180
|
+
"jwe_decode": (
|
|
2181
|
+
/** Decodes the JWE token and return the result */
|
|
2182
|
+
slotted("jwe_decode", ["arg1"], 1, false)
|
|
2183
|
+
),
|
|
2184
|
+
"jwe_encode": (
|
|
2185
|
+
/** Encodes the value and return the result as a JWE token */
|
|
2186
|
+
slotted("jwe_encode", ["arg1"], 1, false)
|
|
2187
|
+
),
|
|
2188
|
+
"lambda": (
|
|
2189
|
+
/** Business logic using JavaScript. (group: transform, result: any) */
|
|
2190
|
+
slotted("lambda", ["code", "timeout"], 1)
|
|
2191
|
+
),
|
|
2192
|
+
"last": (
|
|
2193
|
+
/** Get the last entry of an array (group: array, result: <T>) */
|
|
2194
|
+
() => filter("last")
|
|
2195
|
+
),
|
|
2196
|
+
"list_encodings": (
|
|
2197
|
+
/** List support character encodings (group: text, result: text[]) */
|
|
2198
|
+
() => filter("list_encodings")
|
|
2199
|
+
),
|
|
2200
|
+
"ln": (
|
|
2201
|
+
/** Returns the natural logarithm (group: math, result: decimal) */
|
|
2202
|
+
() => filter("ln")
|
|
2203
|
+
),
|
|
2204
|
+
"log": (
|
|
2205
|
+
/** Returns the logarithm with a custom base (group: math, result: decimal) */
|
|
2206
|
+
slotted("log", ["base"], 1)
|
|
2207
|
+
),
|
|
2208
|
+
"log10": (
|
|
2209
|
+
/** Returns the Base-10 logarithm (group: math, result: decimal) */
|
|
2210
|
+
() => filter("log10")
|
|
2211
|
+
),
|
|
2212
|
+
"lower": (
|
|
2213
|
+
/** Converts all characters to lower case and returns the result (group: text, result: text) */
|
|
2214
|
+
() => filter("lower")
|
|
2215
|
+
),
|
|
2216
|
+
"lt": (
|
|
2217
|
+
/** Returns a boolean if the left value is less than the right value (group: comparison, result: bool) */
|
|
2218
|
+
slotted("lt", ["value"], 1)
|
|
2219
|
+
),
|
|
2220
|
+
"lte": (
|
|
2221
|
+
/** Returns a boolean if the left value is less than or equal to the right value (group: comparison, result: bool) */
|
|
2222
|
+
slotted("lte", ["value"], 1)
|
|
2223
|
+
),
|
|
2224
|
+
"ltrim": (
|
|
2225
|
+
/** Trim whitespace or other characters from the left side and return the result (group: text, result: text) */
|
|
2226
|
+
slotted("ltrim", ["mask"], 0)
|
|
2227
|
+
),
|
|
2228
|
+
"map": (
|
|
2229
|
+
/** Creates a new array with the results of calling a provided function on every element in the calling array. (group: array, result: any[]) */
|
|
2230
|
+
slotted("map", ["code", "timeout"], 1)
|
|
2231
|
+
),
|
|
2232
|
+
"max": (
|
|
2233
|
+
/** Returns the max of the values of the array (group: math, result: decimal) */
|
|
2234
|
+
() => filter("max")
|
|
2235
|
+
),
|
|
2236
|
+
"md5": (
|
|
2237
|
+
/** Returns a MD5 signature representation of the value (group: security, result: text) */
|
|
2238
|
+
slotted("md5", ["raw"], 0)
|
|
2239
|
+
),
|
|
2240
|
+
"min": (
|
|
2241
|
+
/** Returns the min of the values of the array (group: math, result: decimal) */
|
|
2242
|
+
() => filter("min")
|
|
2243
|
+
),
|
|
2244
|
+
"mod": (
|
|
2245
|
+
/** Modulus 2 values together and return the answer (group: math, result: int) */
|
|
2246
|
+
slotted("mod", ["value"], 1)
|
|
2247
|
+
),
|
|
2248
|
+
"mul": (
|
|
2249
|
+
/** Multiplies 2 values together and returns the answer (group: math, result: decimal) */
|
|
2250
|
+
slotted("mul", ["value"], 1)
|
|
2251
|
+
),
|
|
2252
|
+
"ne": (
|
|
2253
|
+
/** Returns a boolean if both values are not equal (group: comparison, result: bool) */
|
|
2254
|
+
slotted("ne", ["value"], 1)
|
|
2255
|
+
),
|
|
2256
|
+
"not": (
|
|
2257
|
+
/** Returns the opposite of the existing value evaluated as a boolean (group: comparison, result: bool) */
|
|
2258
|
+
() => filter("not")
|
|
2259
|
+
),
|
|
2260
|
+
"null": (
|
|
2261
|
+
/** Returns a null value. (group: misc, result: null) */
|
|
2262
|
+
() => filter("null")
|
|
2263
|
+
),
|
|
2264
|
+
"num_max": (
|
|
2265
|
+
/** Returns the max both values (group: math, result: decimal) */
|
|
2266
|
+
slotted("num_max", ["value"], 1)
|
|
2267
|
+
),
|
|
2268
|
+
"num_min": (
|
|
2269
|
+
/** Returns the min both values (group: math, result: decimal) */
|
|
2270
|
+
slotted("num_min", ["value"], 1)
|
|
2271
|
+
),
|
|
2272
|
+
"number_format": (
|
|
2273
|
+
/** Format a number with flexible support over decimal places, thousands separator, and decimal separator. (group: math, result: string) */
|
|
2274
|
+
slotted("number_format", ["decimals", "decimal_separator", "thousands_separator"], 3)
|
|
2275
|
+
),
|
|
2276
|
+
"octdec": (
|
|
2277
|
+
/** Converts an octal value into its decimal equivalent (group: transform, result: text) */
|
|
2278
|
+
() => filter("octdec")
|
|
2279
|
+
),
|
|
2280
|
+
"odd": (
|
|
2281
|
+
/** Returns whether or not the value is odd (group: comparison, result: bool) */
|
|
2282
|
+
() => filter("odd")
|
|
2283
|
+
),
|
|
2284
|
+
"pick": (
|
|
2285
|
+
/** Pick keys from the object to create a new object of just those keys. (group: array, result: <T>) */
|
|
2286
|
+
slotted("pick", ["keys"], 1)
|
|
2287
|
+
),
|
|
2288
|
+
"pow": (
|
|
2289
|
+
/** Returns the value raised to the power of exp. (group: math, result: decimal) */
|
|
2290
|
+
slotted("pow", ["exp"], 1)
|
|
2291
|
+
),
|
|
2292
|
+
"prepend": (
|
|
2293
|
+
/** Push an element on to the beginning of an array within an object and return the updated object (group: array, result: <T>[]) */
|
|
2294
|
+
slotted("prepend", ["value", "path"], 2)
|
|
2295
|
+
),
|
|
2296
|
+
"product": (
|
|
2297
|
+
/** Returns the product of the values of the array (group: math, result: decimal) */
|
|
2298
|
+
() => filter("product")
|
|
2299
|
+
),
|
|
2300
|
+
"querystring_parse": (
|
|
2301
|
+
/** Parses a query string from a URL into its individual key-value pairs. (group: text, result: json) */
|
|
2302
|
+
() => filter("querystring_parse")
|
|
2303
|
+
),
|
|
2304
|
+
"rad2deg": (
|
|
2305
|
+
/** Convert radians to degrees (group: math, result: decimal) */
|
|
2306
|
+
() => filter("rad2deg")
|
|
2307
|
+
),
|
|
2308
|
+
"range": (
|
|
2309
|
+
/** Returns array of values between the specified start/stop. (group: array, result: int[]) */
|
|
2310
|
+
slotted("range", ["start", "stop"], 2)
|
|
2311
|
+
),
|
|
2312
|
+
"reduce": (
|
|
2313
|
+
/** Reduces the array to a single value using the code block to combine each element of the array. (group: array, result: any[]) */
|
|
2314
|
+
slotted("reduce", ["initial_value", "code", "timeout"], 2)
|
|
2315
|
+
),
|
|
2316
|
+
"regex_match": (
|
|
2317
|
+
/** Return the first set of matches performed by a regular expression Direction: the piped value is the regex PATTERN; the `subject` argument is the text tested against it — reversed vs starts_with/contains. Build the pattern with c.regex(...) (delimiter-wrapped); a bare c.text(...) is rejected. (group: text, result: text[]) */
|
|
2318
|
+
slotted("regex_match", ["subject"], 1)
|
|
2319
|
+
),
|
|
2320
|
+
"regex_match_all": (
|
|
2321
|
+
/** Return all matches performed by a regular expression on the Direction: the piped value is the regex PATTERN; the `subject` argument is the text tested against it — reversed vs starts_with/contains. Build the pattern with c.regex(...) (delimiter-wrapped); a bare c.text(...) is rejected. (group: text, result: text[]) */
|
|
2322
|
+
slotted("regex_match_all", ["subject"], 1)
|
|
2323
|
+
),
|
|
2324
|
+
"regex_quote": (
|
|
2325
|
+
/** Update the supplied text value to be properly escaped for regular expressions. (group: text, result: text) */
|
|
2326
|
+
slotted("regex_quote", ["delimiter"], 0)
|
|
2327
|
+
),
|
|
2328
|
+
"regex_replace": (
|
|
2329
|
+
/** Perform a regular expression search and replace on the supplied subject text. Direction: the piped value is the regex PATTERN; the `subject` argument is the text tested against it — reversed vs starts_with/contains. Build the pattern with c.regex(...) (delimiter-wrapped); a bare c.text(...) is rejected. (group: text, result: text) */
|
|
2330
|
+
slotted("regex_replace", ["replacement", "subject"], 2)
|
|
2331
|
+
),
|
|
2332
|
+
"regex_test": (
|
|
2333
|
+
/** Tests if a regular expression matches the supplied subject text. Direction: the piped value is the regex PATTERN; the `subject` argument is the text tested against it — reversed vs starts_with/contains. Build the pattern with c.regex(...) (delimiter-wrapped); a bare c.text(...) is rejected. (group: text, result: bool) */
|
|
2334
|
+
slotted("regex_test", ["subject"], 1)
|
|
2335
|
+
),
|
|
2336
|
+
"reverse": (
|
|
2337
|
+
/** Returns values of an array in reverse order (group: array, result: <T>[]) */
|
|
2338
|
+
() => filter("reverse")
|
|
2339
|
+
),
|
|
2340
|
+
"round": (
|
|
2341
|
+
/** Round a decimal with optional precision (group: math, result: decimal) */
|
|
2342
|
+
slotted("round", ["precision"], 0)
|
|
2343
|
+
),
|
|
2344
|
+
"rtrim": (
|
|
2345
|
+
/** Trim whitespace or other characters from the right return the result (group: text, result: text) */
|
|
2346
|
+
slotted("rtrim", ["mask"], 0)
|
|
2347
|
+
),
|
|
2348
|
+
"safe_array": (
|
|
2349
|
+
/** Always returns an array. Uses the existing value if it is an array or creates an array of one element. (group: array, result: <T>[]) */
|
|
2350
|
+
() => filter("safe_array")
|
|
2351
|
+
),
|
|
2352
|
+
"secureid_decode": (
|
|
2353
|
+
/** Returns the id of the original encode (group: security, result: int) */
|
|
2354
|
+
slotted("secureid_decode", ["salt"], 1)
|
|
2355
|
+
),
|
|
2356
|
+
"secureid_encode": (
|
|
2357
|
+
/** Returns an encrypted version of the id (group: security, result: text) */
|
|
2358
|
+
slotted("secureid_encode", ["salt"], 1)
|
|
2359
|
+
),
|
|
2360
|
+
"set": (
|
|
2361
|
+
/** Sets a value at the path within the object and returns the updated object (group: manipulation, result: any) */
|
|
2362
|
+
slotted("set", ["path", "value"], 2)
|
|
2363
|
+
),
|
|
2364
|
+
"set_conditional": (
|
|
2365
|
+
/** Sets a value at the path within the object and returns the updated object, if the conditional expression is true (group: manipulation, result: any) */
|
|
2366
|
+
slotted("set_conditional", ["path", "value", "conditional"], 3)
|
|
2367
|
+
),
|
|
2368
|
+
"set_ifnotempty": (
|
|
2369
|
+
/** Sets a value (if it is not empty: "", null, 0, "0", false, [], {}) at the path within the object and returns the updated object (group: manipulation, result: any) */
|
|
2370
|
+
slotted("set_ifnotempty", ["path", "value"], 2)
|
|
2371
|
+
),
|
|
2372
|
+
"set_ifnotnull": (
|
|
2373
|
+
/** Sets a value (if it is not null) at the path within the object and returns the updated object (group: manipulation, result: any) */
|
|
2374
|
+
slotted("set_ifnotnull", ["path", "value"], 2)
|
|
2375
|
+
),
|
|
2376
|
+
"sha1": (
|
|
2377
|
+
/** Returns a SHA1 signature representation of the value (group: security, result: text) */
|
|
2378
|
+
slotted("sha1", ["raw"], 0)
|
|
2379
|
+
),
|
|
2380
|
+
"sha256": (
|
|
2381
|
+
/** Returns a SHA256 signature representation of the value (group: security, result: text) */
|
|
2382
|
+
slotted("sha256", ["raw"], 0)
|
|
2383
|
+
),
|
|
2384
|
+
"sha384": (
|
|
2385
|
+
/** Returns a SHA384 signature representation of the value (group: security, result: text) */
|
|
2386
|
+
slotted("sha384", ["raw"], 0)
|
|
2387
|
+
),
|
|
2388
|
+
"sha512": (
|
|
2389
|
+
/** Returns a SHA512 signature representation of the value (group: security, result: text) */
|
|
2390
|
+
slotted("sha512", ["raw"], 0)
|
|
2391
|
+
),
|
|
2392
|
+
"sin": (
|
|
2393
|
+
/** Calculates the sine of the supplied value in radians (group: math, result: decimal) */
|
|
2394
|
+
() => filter("sin")
|
|
2395
|
+
),
|
|
2396
|
+
"some": (
|
|
2397
|
+
/** Checks if at least one element in the array passes the test implemented by the provided function. (group: array, result: any[]) */
|
|
2398
|
+
slotted("some", ["code", "timeout"], 1)
|
|
2399
|
+
),
|
|
2400
|
+
"sort": (
|
|
2401
|
+
/** Sort an array of elements with an optional path inside the element */
|
|
2402
|
+
(...args) => filter("sort", ...args.map(v))
|
|
2403
|
+
),
|
|
2404
|
+
"split": (
|
|
2405
|
+
/** Splits text into an array of text and returns the result (group: text, result: text[]) */
|
|
2406
|
+
slotted("split", ["separator"], 1)
|
|
2407
|
+
),
|
|
2408
|
+
"sprintf": (
|
|
2409
|
+
/** formats text with variable substitution (group: text, result: text) */
|
|
2410
|
+
(...args) => filter("sprintf", ...args.map(v))
|
|
2411
|
+
),
|
|
2412
|
+
"sql_alias": (
|
|
2413
|
+
/** Wraps text in double quotes and escapes any double quotes within the text to prevent SQL injection attacks. This is useful for safely inserting user input into table names or aliases within SQL queries. (group: text, result: text) */
|
|
2414
|
+
() => filter("sql_alias")
|
|
2415
|
+
),
|
|
2416
|
+
"sql_esc": (
|
|
2417
|
+
/** Wraps text in single quotes and escapes any single quotes within the text to prevent SQL injection attacks. This is useful for safely inserting user input into values within SQL queries. (group: text, result: text) */
|
|
2418
|
+
() => filter("sql_esc")
|
|
2419
|
+
),
|
|
2420
|
+
"sqrt": (
|
|
2421
|
+
/** Returns the square root of the value (group: math, result: decimal) */
|
|
2422
|
+
() => filter("sqrt")
|
|
2423
|
+
),
|
|
2424
|
+
"starts_with": (
|
|
2425
|
+
/** Returns whether or not the expression is present at the beginning Direction: the piped value is the subject text; the argument is the substring searched for. (group: text, result: bool) */
|
|
2426
|
+
slotted("starts_with", ["search"], 1)
|
|
2427
|
+
),
|
|
2428
|
+
"string_replace": (
|
|
2429
|
+
/** Replace a text phrase with another (group: text, result: text) */
|
|
2430
|
+
slotted("string_replace", ["search", "replacement"], 2)
|
|
2431
|
+
),
|
|
2432
|
+
"strip_accents": (
|
|
2433
|
+
/** Removes accents from characters (group: text, result: text) */
|
|
2434
|
+
() => filter("strip_accents")
|
|
2435
|
+
),
|
|
2436
|
+
"strip_tags": (
|
|
2437
|
+
/** Removes HTML tags from a string (group: text, result: text) */
|
|
2438
|
+
slotted("strip_tags", ["exclude"], 0)
|
|
2439
|
+
),
|
|
2440
|
+
"stripos": (
|
|
2441
|
+
/** Returns the index of the case-insensitive expression or false if it (group: text, result: int) */
|
|
2442
|
+
slotted("stripos", ["search"], 1)
|
|
2443
|
+
),
|
|
2444
|
+
"strlen": (
|
|
2445
|
+
/** Returns the number of characters (group: text, result: int) */
|
|
2446
|
+
() => filter("strlen")
|
|
2447
|
+
),
|
|
2448
|
+
"strpos": (
|
|
2449
|
+
/** Returns the index of the case-sensitive expression or false if it (group: text, result: int) */
|
|
2450
|
+
slotted("strpos", ["search"], 1)
|
|
2451
|
+
),
|
|
2452
|
+
"sub": (
|
|
2453
|
+
/** Subtracts 2 values together and returns the answer (group: math, result: decimal) */
|
|
2454
|
+
slotted("sub", ["value"], 1)
|
|
2455
|
+
),
|
|
2456
|
+
"substr": (
|
|
2457
|
+
/** Extracts a section of text (group: text, result: text) */
|
|
2458
|
+
slotted("substr", ["start", "length"], 2)
|
|
2459
|
+
),
|
|
2460
|
+
"sum": (
|
|
2461
|
+
/** Returns the sum of the values of the array (group: math, result: decimal) */
|
|
2462
|
+
() => filter("sum")
|
|
2463
|
+
),
|
|
2464
|
+
"tan": (
|
|
2465
|
+
/** Calculates the tangent of the supplied value in radians (group: math, result: decimal) */
|
|
2466
|
+
() => filter("tan")
|
|
2467
|
+
),
|
|
2468
|
+
"text_escape": (
|
|
2469
|
+
/** Converts control characters into their escaped sequences. Ex: newlines as \n, tabs as \t (group: text, result: text) */
|
|
2470
|
+
() => filter("text_escape")
|
|
2471
|
+
),
|
|
2472
|
+
"text_unescape": (
|
|
2473
|
+
/** Convert escaped sequences into their raw form. Ex: \n becomes a newline, \t becomes a tab. (group: text, result: text) */
|
|
2474
|
+
() => filter("text_unescape")
|
|
2475
|
+
),
|
|
2476
|
+
"to_bool": (
|
|
2477
|
+
/** Converts text, integer, or decimal types to a bool and returns the result (group: transform, result: bool) */
|
|
2478
|
+
() => filter("to_bool")
|
|
2479
|
+
),
|
|
2480
|
+
"to_decimal": (
|
|
2481
|
+
/** Converts text, integer, or bool types to a decimal and returns the result (group: transform, result: decimal) */
|
|
2482
|
+
() => filter("to_decimal")
|
|
2483
|
+
),
|
|
2484
|
+
"to_epoch_day": (
|
|
2485
|
+
/** Converts a text expression (now, next friday, Jan 1 2000) to the (group: transform, result: int) */
|
|
2486
|
+
slotted("to_epoch_day", ["timezone"], 0)
|
|
2487
|
+
),
|
|
2488
|
+
"to_epoch_hour": (
|
|
2489
|
+
/** Converts a text expression (now, next friday, Jan 1 2000) to the (group: transform, result: int) */
|
|
2490
|
+
slotted("to_epoch_hour", ["timezone"], 0)
|
|
2491
|
+
),
|
|
2492
|
+
"to_epoch_minute": (
|
|
2493
|
+
/** Converts a text expression (now, next friday, Jan 1 2000) to the (group: transform, result: int) */
|
|
2494
|
+
slotted("to_epoch_minute", ["timezone"], 0)
|
|
2495
|
+
),
|
|
2496
|
+
"to_epoch_ms": (
|
|
2497
|
+
/** Converts a text expression (now, next friday, Jan 1 2000) to the (group: transform, result: int) */
|
|
2498
|
+
slotted("to_epoch_ms", ["timezone"], 0)
|
|
2499
|
+
),
|
|
2500
|
+
"to_epoch_sec": (
|
|
2501
|
+
/** Converts a text expression (now, next friday, Jan 1 2000) to the (group: transform, result: int) */
|
|
2502
|
+
slotted("to_epoch_sec", ["timezone"], 0)
|
|
2503
|
+
),
|
|
2504
|
+
"to_epochms": (
|
|
2505
|
+
/** Converts a text expression (now, next friday, Jan 1 2000) to (group: transform, result: epochms) */
|
|
2506
|
+
slotted("to_epochms", ["timezone"], 0)
|
|
2507
|
+
),
|
|
2508
|
+
"to_expr": (
|
|
2509
|
+
/** Treats the PIPED TEXT as Xano Expression Engine source, evaluates it, and returns the result. Takes no argument. `$var`, `$input`, `$env` and `$auth` resolve inside it; there is no operand binding (`$0` is null) because the operand IS the source. For an expression OVER a value, use fl.transform. (group: transform, result: any) */
|
|
2510
|
+
() => filter("to_expr")
|
|
2511
|
+
),
|
|
2512
|
+
"to_int": (
|
|
2513
|
+
/** Converts text, decimal, or bool types to an integer and returns the result (group: transform, result: int) */
|
|
2514
|
+
() => filter("to_int")
|
|
2515
|
+
),
|
|
2516
|
+
"to_text": (
|
|
2517
|
+
/** Converts integer, decimal, or bool types to text and returns the result (group: transform, result: text) */
|
|
2518
|
+
() => filter("to_text")
|
|
2519
|
+
),
|
|
2520
|
+
"to_utf8": (
|
|
2521
|
+
/** Convert the supplied text from its binary form (ISO-8859-1) to UTF-8. (group: text, result: text) */
|
|
2522
|
+
() => filter("to_utf8")
|
|
2523
|
+
),
|
|
2524
|
+
"transform": (
|
|
2525
|
+
/** Evaluates a Xano Expression Engine expression over the piped value. NOT a JavaScript body — there is no `return`, and the piped value binds POSITIONALLY as `$0` (or `$$`), NOT as `$this` (which resolves to null here, yielding a wrong answer with no error). `$var`, `$input`, `$env` and `$auth` also resolve, and filters may be piped inside the expression: `$0 * 2`, `$0|sort|join:","`, `{ id: $0.id, total: $0.qty * $0.price }`. PARENTHESIZE a pipe used inside an object/array literal — `{ s: ($0|sort|join:",") }` — or the filter argument's comma is read as the key separator, returning null and silently dropping every later key. For JavaScript use fl.lambda, whose body does bind `$this`. (group: manipulation, result: any) */
|
|
2526
|
+
slotted("transform", ["expression"], 1)
|
|
2527
|
+
),
|
|
2528
|
+
"trim": (
|
|
2529
|
+
/** Trim whitespace or other characters from both sides and return the result (group: text, result: text) */
|
|
2530
|
+
slotted("trim", ["mask"], 0)
|
|
2531
|
+
),
|
|
2532
|
+
"uid": (
|
|
2533
|
+
/** Returns a unique 64bit unsigned int value seeded off the value (group: security, result: int) */
|
|
2534
|
+
() => filter("uid")
|
|
2535
|
+
),
|
|
2536
|
+
"unique": (
|
|
2537
|
+
/** Returns unique values of an array (group: array, result: <T>[]) */
|
|
2538
|
+
slotted("unique", ["path"], 0)
|
|
2539
|
+
),
|
|
2540
|
+
"unpick": (
|
|
2541
|
+
/** Remove keys from the object to create a new object of the remaining keys. (group: array, result: <T>) */
|
|
2542
|
+
slotted("unpick", ["keys"], 1)
|
|
2543
|
+
),
|
|
2544
|
+
"unset": (
|
|
2545
|
+
/** Removes a value at the path within the object and returns the updated object (group: manipulation, result: any) */
|
|
2546
|
+
slotted("unset", ["path"], 1)
|
|
2547
|
+
),
|
|
2548
|
+
"upper": (
|
|
2549
|
+
/** Converts all characters to upper case and returns the result (group: text, result: text) */
|
|
2550
|
+
() => filter("upper")
|
|
2551
|
+
),
|
|
2552
|
+
"url_addarg": (
|
|
2553
|
+
/** Parses a URL and returns an updated version with an encoded version of the supplied argument (group: text, result: text) */
|
|
2554
|
+
slotted("url_addarg", ["key", "value", "encoding_rfc3986"], 2)
|
|
2555
|
+
),
|
|
2556
|
+
"url_decode": (
|
|
2557
|
+
/** Decodes the value represented as a url encoded value (group: transform, result: text) */
|
|
2558
|
+
() => filter("url_decode")
|
|
2559
|
+
),
|
|
2560
|
+
"url_decode_rfc3986": (
|
|
2561
|
+
/** Decodes the value represented as a url encoded value using the RFC 3986 specification (group: transform, result: text) */
|
|
2562
|
+
() => filter("url_decode_rfc3986")
|
|
2563
|
+
),
|
|
2564
|
+
"url_delarg": (
|
|
2565
|
+
/** Parses a URL and returns an updated version with the supplied argument removed (group: text, result: text) */
|
|
2566
|
+
slotted("url_delarg", ["key"], 1)
|
|
2567
|
+
),
|
|
2568
|
+
"url_encode": (
|
|
2569
|
+
/** Encodes the value and returns the result as a url encoded value (group: transform, result: text) */
|
|
2570
|
+
() => filter("url_encode")
|
|
2571
|
+
),
|
|
2572
|
+
"url_encode_rfc3986": (
|
|
2573
|
+
/** Encodes the value and returns the result as a url encoded value using the RFC 3986 specification (group: transform, result: text) */
|
|
2574
|
+
() => filter("url_encode_rfc3986")
|
|
2575
|
+
),
|
|
2576
|
+
"url_getarg": (
|
|
2577
|
+
/** Gets the argument's value from a URL (group: text, result: text) */
|
|
2578
|
+
slotted("url_getarg", ["key", "default_"], 1)
|
|
2579
|
+
),
|
|
2580
|
+
"url_hasarg": (
|
|
2581
|
+
/** Returns the existence of a argument in the URL (group: text, result: text) */
|
|
2582
|
+
slotted("url_hasarg", ["key"], 1)
|
|
2583
|
+
),
|
|
2584
|
+
"url_parse": (
|
|
2585
|
+
/** Parses a URL into its individual components. (group: text, result: json) */
|
|
2586
|
+
() => filter("url_parse")
|
|
2587
|
+
),
|
|
2588
|
+
"uuid4": (
|
|
2589
|
+
/** Returns a universally unique identifier (group: security, result: text) */
|
|
2590
|
+
() => filter("uuid4")
|
|
2591
|
+
),
|
|
2592
|
+
"xml_decode": (
|
|
2593
|
+
/** Decodes XML and returns the result (group: transform, result: any) */
|
|
2594
|
+
() => filter("xml_decode")
|
|
2595
|
+
),
|
|
2596
|
+
"yaml_decode": (
|
|
2597
|
+
/** Decodes the value represented as yaml and returns the result (group: transform, result: obj) */
|
|
2598
|
+
() => filter("yaml_decode")
|
|
2599
|
+
),
|
|
2600
|
+
"yaml_encode": (
|
|
2601
|
+
/** Encodes the value and returns the result as yaml text (group: transform, result: text) */
|
|
2602
|
+
() => filter("yaml_encode")
|
|
2603
|
+
)
|
|
2604
|
+
};
|
|
2605
|
+
|
|
2606
|
+
// src/statements/expression.ts
|
|
2607
|
+
var SUPPORTED_OPS = ["=", "!=", "===", "!==", ">", "<", ">=", "<="];
|
|
2608
|
+
var OP_ALIASES = { "==": "=" };
|
|
2609
|
+
function expr(left, op, right) {
|
|
2610
|
+
const normalized = OP_ALIASES[op] ?? op;
|
|
2611
|
+
if (!SUPPORTED_OPS.includes(normalized)) {
|
|
2612
|
+
throw new Error(
|
|
2613
|
+
`Unsupported conditional operator "${op}". Supported: ${SUPPORTED_OPS.join(", ")} (also ==, a synonym for =)`
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2616
|
+
return { left, op: normalized, right };
|
|
2617
|
+
}
|
|
2618
|
+
var SEARCH_OPS = /* @__PURE__ */ new Set([
|
|
2619
|
+
"=",
|
|
2620
|
+
"==",
|
|
2621
|
+
"!=",
|
|
2622
|
+
"===",
|
|
2623
|
+
"!==",
|
|
2624
|
+
"<",
|
|
2625
|
+
"<=",
|
|
2626
|
+
">",
|
|
2627
|
+
">=",
|
|
2628
|
+
"in",
|
|
2629
|
+
"not in",
|
|
2630
|
+
"like",
|
|
2631
|
+
"not like",
|
|
2632
|
+
"ilike",
|
|
2633
|
+
"not ilike",
|
|
2634
|
+
"~",
|
|
2635
|
+
"!~",
|
|
2636
|
+
"between",
|
|
2637
|
+
"not between",
|
|
2638
|
+
"@>",
|
|
2639
|
+
"contains",
|
|
2640
|
+
"not contains",
|
|
2641
|
+
"includes",
|
|
2642
|
+
"not includes",
|
|
2643
|
+
"overlaps",
|
|
2644
|
+
"not overlaps",
|
|
2645
|
+
"search"
|
|
2646
|
+
]);
|
|
2647
|
+
function isSearchOp(op) {
|
|
2648
|
+
return typeof op === "string" && SEARCH_OPS.has(op);
|
|
2649
|
+
}
|
|
2650
|
+
function cmp(left, op, right, opts) {
|
|
2651
|
+
if (!SEARCH_OPS.has(op)) {
|
|
2652
|
+
throw new Error(
|
|
2653
|
+
`unsupported operator "${op}". Supported: ${[...SEARCH_OPS].join(", ")}.`
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
return { left, op, right, ignoreEmpty: opts?.ignoreEmpty };
|
|
2657
|
+
}
|
|
2658
|
+
function and(...children) {
|
|
2659
|
+
return { or: false, children };
|
|
2660
|
+
}
|
|
2661
|
+
function or(...children) {
|
|
2662
|
+
return { or: true, children };
|
|
2663
|
+
}
|
|
2664
|
+
function mixed(first, ...rest) {
|
|
2665
|
+
if (rest.length === 0) {
|
|
2666
|
+
throw new Error(
|
|
2667
|
+
"mixed(): needs at least two terms \u2014 a single term has no join to mix. Pass the node on its own, or use and()/or() for a uniform container."
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
for (const term of rest) {
|
|
2671
|
+
const keys = Object.keys(term).filter((k) => term[k] !== void 0);
|
|
2672
|
+
if (keys.length !== 1 || keys[0] !== "and" && keys[0] !== "or") {
|
|
2673
|
+
throw new Error(
|
|
2674
|
+
`mixed(): every term after the first is exactly one of \`{ and: node }\` or \`{ or: node }\` \u2014 got ${JSON.stringify(keys)}.`
|
|
2675
|
+
);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
return { mixed: [first, ...rest] };
|
|
2679
|
+
}
|
|
2680
|
+
function isValue(w) {
|
|
2681
|
+
return typeof w === "object" && w !== null && !Array.isArray(w) && "tag" in w && "value" in w;
|
|
2682
|
+
}
|
|
2683
|
+
function isGroup(w) {
|
|
2684
|
+
return typeof w === "object" && w !== null && !Array.isArray(w) && "children" in w;
|
|
2685
|
+
}
|
|
2686
|
+
function isMixed(w) {
|
|
2687
|
+
return typeof w === "object" && w !== null && !Array.isArray(w) && "mixed" in w;
|
|
2688
|
+
}
|
|
2689
|
+
function containerTerms(node) {
|
|
2690
|
+
if (isMixed(node)) {
|
|
2691
|
+
return node.mixed.map(
|
|
2692
|
+
(term, i) => i === 0 ? { or: false, node: term } : { or: "or" in term, node: "or" in term ? term.or : term.and }
|
|
2693
|
+
);
|
|
2694
|
+
}
|
|
2695
|
+
return node.children.map((child, i) => ({ or: node.or && i > 0, node: child }));
|
|
2696
|
+
}
|
|
2697
|
+
function isCmpNode(w) {
|
|
2698
|
+
return typeof w === "object" && w !== null && !Array.isArray(w) && "op" in w && "left" in w;
|
|
2699
|
+
}
|
|
2700
|
+
function toOperand(v2) {
|
|
2701
|
+
return { operand: v2.value, tag: v2.tag, filters: v2.filters };
|
|
2702
|
+
}
|
|
2703
|
+
function toStatementNode(node, or2) {
|
|
2704
|
+
const right = toOperand(node.right);
|
|
2705
|
+
if (node.ignoreEmpty) right.ignore_empty = true;
|
|
2706
|
+
return {
|
|
2707
|
+
type: "statement",
|
|
2708
|
+
or: or2,
|
|
2709
|
+
group: { expression: [] },
|
|
2710
|
+
statement: { op: node.op, left: toOperand(node.left), right }
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
function encodeContainer(children, joinOr) {
|
|
2714
|
+
return encodeTerms(children.map((child, i) => ({ or: joinOr && i > 0, node: child })));
|
|
2715
|
+
}
|
|
2716
|
+
function encodeTerms(terms) {
|
|
2717
|
+
return terms.map(({ or: or2, node }) => {
|
|
2718
|
+
if (isGroup(node) || isMixed(node)) {
|
|
2719
|
+
const group = {
|
|
2720
|
+
type: "group",
|
|
2721
|
+
or: or2,
|
|
2722
|
+
group: { expression: encodeTerms(containerTerms(node)) }
|
|
2723
|
+
};
|
|
2724
|
+
return group;
|
|
2725
|
+
}
|
|
2726
|
+
return toStatementNode(node, or2);
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
var MAX_EXPRESSION_DEPTH = 128;
|
|
2730
|
+
function assertExpressionDepth(nodes) {
|
|
2731
|
+
const stack = nodes.map((node) => ({ node, depth: 1 }));
|
|
2732
|
+
while (stack.length > 0) {
|
|
2733
|
+
const { node, depth } = stack.pop();
|
|
2734
|
+
if (!isGroup(node) && !isMixed(node)) continue;
|
|
2735
|
+
if (depth > MAX_EXPRESSION_DEPTH) {
|
|
2736
|
+
throw new Error(
|
|
2737
|
+
`condition: expression containers are nested ${depth} levels deep, past the supported limit of ${MAX_EXPRESSION_DEPTH}. Nesting costs quadratic bytes \u2014 every level re-wraps everything below it \u2014 so a deep chain exports megabytes and eventually cannot be walked at all. This is almost always a rule set folded one term at a time (\`rules.reduce((acc, r) => and(acc, r))\`); pass the terms as SIBLINGS of one container instead (\`and(...rules)\` \u2014 flat, linear, unlimited in count), or balance the tree so its depth is logarithmic. Mixing joins? Group each run: \`and(or(...anyOf), ...allOf)\`.`
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
for (const term of containerTerms(node)) stack.push({ node: term.node, depth: depth + 1 });
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
function encodeExpression(input2) {
|
|
2744
|
+
const nodes = Array.isArray(input2) ? input2 : [input2];
|
|
2745
|
+
assertExpressionDepth(nodes);
|
|
2746
|
+
const only = nodes.length === 1 ? nodes[0] : void 0;
|
|
2747
|
+
if (only !== void 0 && isGroup(only) && only.or) {
|
|
2748
|
+
return { expression: encodeContainer(only.children, true) };
|
|
2749
|
+
}
|
|
2750
|
+
if (only !== void 0 && isMixed(only)) {
|
|
2751
|
+
return { expression: encodeTerms(containerTerms(only)) };
|
|
2752
|
+
}
|
|
2753
|
+
return { expression: encodeContainer(nodes, false) };
|
|
2754
|
+
}
|
|
2755
|
+
var RUNTIME_OPS = new Set(SUPPORTED_OPS);
|
|
2756
|
+
function isRuntimeConditionOp(op) {
|
|
2757
|
+
return typeof op === "string" && (RUNTIME_OPS.has(op) || op in OP_ALIASES);
|
|
2758
|
+
}
|
|
2759
|
+
function assertRuntimeOps(node, surface) {
|
|
2760
|
+
if (isGroup(node) || isMixed(node)) {
|
|
2761
|
+
for (const term of containerTerms(node)) assertRuntimeOps(term.node, surface);
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
const op = node.op;
|
|
2765
|
+
if (isRuntimeConditionOp(op)) return;
|
|
2766
|
+
throw new Error(
|
|
2767
|
+
`${surface}: operator "${op}" is a database-search operator and cannot be evaluated in a condition \u2014 the request fails at runtime with \`Invalid op: ${op}\`. A condition evaluates ${SUPPORTED_OPS.join(", ")} (and \`==\`). Keep "${op}" for a db.query/bulk/addon \`where\`, a table view filter, or a database trigger \`search\`; in a condition, spell it out \u2014 e.g. membership as \`or(expr(x, "=", a), expr(x, "=", b))\`.`
|
|
2768
|
+
);
|
|
2769
|
+
}
|
|
2770
|
+
function encodeRuntimeCondition(input2, surface) {
|
|
2771
|
+
const nodes = Array.isArray(input2) ? input2 : [input2];
|
|
2772
|
+
assertExpressionDepth(nodes);
|
|
2773
|
+
for (const node of nodes) assertRuntimeOps(node, surface);
|
|
2774
|
+
return encodeExpression(input2);
|
|
2775
|
+
}
|
|
2776
|
+
function encodeComparison(when) {
|
|
2777
|
+
return encodeExpression(when);
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// src/statements/conditional.ts
|
|
2781
|
+
var CONDITIONAL = "mvp:conditional";
|
|
2782
|
+
var CONDITIONAL_ELIF = "mvp:conditional_elif";
|
|
2783
|
+
function conditionalElif(args) {
|
|
2784
|
+
const context = {
|
|
2785
|
+
expr: encodeRuntimeCondition(args.when, "s.conditional elif `when`"),
|
|
2786
|
+
if: { run: args.then.map(encodeStatement) }
|
|
2787
|
+
};
|
|
2788
|
+
return annotate({ name: CONDITIONAL_ELIF, context, input: [] }, args);
|
|
2789
|
+
}
|
|
2790
|
+
function conditional(args) {
|
|
2791
|
+
const context = {
|
|
2792
|
+
expr: encodeRuntimeCondition(args.when, "s.conditional `when`"),
|
|
2793
|
+
if: { run: args.then.map(encodeStatement) },
|
|
2794
|
+
elif: { run: (args.elif ?? []).map((e) => encodeStatement(conditionalElif(e))) },
|
|
2795
|
+
else: { run: (args.else ?? []).map(encodeStatement) }
|
|
2796
|
+
};
|
|
2797
|
+
return annotate({ name: CONDITIONAL, context, input: [] }, args);
|
|
2798
|
+
}
|
|
2799
|
+
registerStatement(CONDITIONAL, conditional);
|
|
2800
|
+
registerStatement(CONDITIONAL_ELIF, conditionalElif);
|
|
2801
|
+
|
|
2802
|
+
// src/kinds/test-mocks.ts
|
|
2803
|
+
function mockBearingItem(node) {
|
|
2804
|
+
if (node === null || typeof node !== "object" || Array.isArray(node)) return null;
|
|
2805
|
+
const rec = node;
|
|
2806
|
+
if (typeof rec.name !== "string") return null;
|
|
2807
|
+
const mocks = rec.mocks;
|
|
2808
|
+
if (mocks === null || typeof mocks !== "object" || Array.isArray(mocks)) return null;
|
|
2809
|
+
if (Object.keys(mocks).length === 0) return null;
|
|
2810
|
+
return rec;
|
|
2811
|
+
}
|
|
2812
|
+
function resolveMockKeys(run, tests, ownerKind, ownerName) {
|
|
2813
|
+
const byName = new Map(tests.map((t) => [t.name, t.id]));
|
|
2814
|
+
const rewrite = (item) => {
|
|
2815
|
+
const resolved = {};
|
|
2816
|
+
for (const [testName, entry] of Object.entries(item.mocks)) {
|
|
2817
|
+
const id = byName.get(testName);
|
|
2818
|
+
if (id === void 0) {
|
|
2819
|
+
if (/^[0-9a-f]{32}$/.test(testName)) {
|
|
2820
|
+
resolved[testName] = entry;
|
|
2821
|
+
continue;
|
|
2822
|
+
}
|
|
2823
|
+
throw new Error(
|
|
2824
|
+
`${ownerKind} "${ownerName}": a statement mocks a test named "${testName}", which this object does not declare. ` + (tests.length === 0 ? `It declares no tests at all \u2014 add one to \`tests\`, or drop the mock. ` : `Declared tests: ${tests.map((t) => `"${t.name}"`).join(", ")}. `) + `A mock is stored against a test's id, so a name that matches nothing would deploy clean and then never apply.`
|
|
2825
|
+
);
|
|
2826
|
+
}
|
|
2827
|
+
resolved[id] = entry;
|
|
2828
|
+
}
|
|
2829
|
+
item.mocks = resolved;
|
|
2830
|
+
};
|
|
2831
|
+
const pending = [run];
|
|
2832
|
+
while (pending.length > 0) {
|
|
2833
|
+
const node = pending.pop();
|
|
2834
|
+
if (Array.isArray(node)) {
|
|
2835
|
+
for (const child of node) pending.push(child);
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2838
|
+
if (node === null || typeof node !== "object") continue;
|
|
2839
|
+
const item = mockBearingItem(node);
|
|
2840
|
+
if (item) rewrite(item);
|
|
2841
|
+
for (const [key, child] of Object.entries(node)) {
|
|
2842
|
+
if (key === "mocks") continue;
|
|
2843
|
+
pending.push(child);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
function assertMockKeysResolved(xdo, kindName) {
|
|
2848
|
+
const pending = [xdo];
|
|
2849
|
+
while (pending.length > 0) {
|
|
2850
|
+
const node = pending.pop();
|
|
2851
|
+
if (Array.isArray(node)) {
|
|
2852
|
+
for (const child of node) pending.push(child);
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
if (node === null || typeof node !== "object") continue;
|
|
2856
|
+
const item = mockBearingItem(node);
|
|
2857
|
+
if (item) {
|
|
2858
|
+
for (const key of Object.keys(item.mocks)) {
|
|
2859
|
+
if (/^[0-9a-f]{32}$/.test(key)) continue;
|
|
2860
|
+
throw new Error(
|
|
2861
|
+
`${kindName} "${String(node.name ?? "")}": a statement carries \`mock: { "${key}": \u2026 }\`, but a ${kindName} does not store unit tests \u2014 only a query, function, or middleware does. A mock is stored against a test's id, so this one would deploy clean and never apply. Move the step into a function that declares the test, or drop the mock.`
|
|
2862
|
+
);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
for (const [key, child] of Object.entries(node)) {
|
|
2866
|
+
if (key === "mocks") continue;
|
|
2867
|
+
pending.push(child);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
// src/kinds/kind.ts
|
|
2873
|
+
var kindRegistry = /* @__PURE__ */ new Map();
|
|
2874
|
+
function registerKind(kind) {
|
|
2875
|
+
kindRegistry.set(kind.name, kind);
|
|
2876
|
+
}
|
|
2877
|
+
function isRegisteredKind(name) {
|
|
2878
|
+
return kindRegistry.has(name);
|
|
2879
|
+
}
|
|
2880
|
+
function getKind(name) {
|
|
2881
|
+
const kind = kindRegistry.get(name);
|
|
2882
|
+
if (!kind) {
|
|
2883
|
+
throw new Error(
|
|
2884
|
+
`Unknown object kind "${name}". Registered: ${[...kindRegistry.keys()].join(", ") || "(none)"}`
|
|
2885
|
+
);
|
|
2886
|
+
}
|
|
2887
|
+
return kind;
|
|
2888
|
+
}
|
|
2889
|
+
function encodeObject(name, def) {
|
|
2890
|
+
const xdo = getKind(name).encode(def);
|
|
2891
|
+
assertMockKeysResolved(xdo, name);
|
|
2892
|
+
return xdo;
|
|
2893
|
+
}
|
|
2894
|
+
function registeredKinds() {
|
|
2895
|
+
return [...kindRegistry.values()];
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
// src/kinds/common.ts
|
|
2899
|
+
function emptyMiddleware() {
|
|
2900
|
+
return { pre_customize: false, post_customize: false, pre: [], post: [] };
|
|
2901
|
+
}
|
|
2902
|
+
function encodeTags(tags) {
|
|
2903
|
+
return (tags ?? []).map((tag) => ({ tag }));
|
|
2904
|
+
}
|
|
2905
|
+
var HISTORY_DEFAULT_OFF = /* @__PURE__ */ new Set(["function", "middleware", "trigger", "message"]);
|
|
2906
|
+
function defaultHistory(objType, override) {
|
|
2907
|
+
return {
|
|
2908
|
+
inherit: override?.inherit ?? true,
|
|
2909
|
+
enabled: override?.enabled ?? !HISTORY_DEFAULT_OFF.has(objType),
|
|
2910
|
+
limit: override?.limit ?? 100
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
// src/kinds/table.ts
|
|
2915
|
+
var SCALAR_FAMILY = {
|
|
2916
|
+
bool: "bool",
|
|
2917
|
+
int: "int",
|
|
2918
|
+
epochms: "epochms",
|
|
2919
|
+
decimal: "decimal",
|
|
2920
|
+
text: "text",
|
|
2921
|
+
uuid: "text",
|
|
2922
|
+
email: "text",
|
|
2923
|
+
password: "text",
|
|
2924
|
+
enum: "enum"
|
|
2925
|
+
};
|
|
2926
|
+
function normalizeIndexType(type) {
|
|
2927
|
+
return type === "unique" ? "btree|unique" : type;
|
|
2928
|
+
}
|
|
2929
|
+
var SEED_FILE = /* @__PURE__ */ Symbol.for("xanots.seed.file");
|
|
2930
|
+
function seedFile(path, base) {
|
|
2931
|
+
return { [SEED_FILE]: { path, base: typeof base === "string" ? base : base.href } };
|
|
2932
|
+
}
|
|
2933
|
+
function isSeedFileSource(source) {
|
|
2934
|
+
return typeof source === "object" && source !== null && SEED_FILE in source;
|
|
2935
|
+
}
|
|
2936
|
+
function toColumns(schema) {
|
|
2937
|
+
if (Array.isArray(schema)) return schema;
|
|
2938
|
+
return Object.entries(schema).map(([name, d]) => ({ name, type: d.type, ...d.options }));
|
|
2939
|
+
}
|
|
2940
|
+
function systemColumns(idType = "int") {
|
|
2941
|
+
return [
|
|
2942
|
+
// A uuid primary key persists NO `default` key — the engine stores it that
|
|
2943
|
+
// way because the value is engine-generated. An `int` key and an ordinary
|
|
2944
|
+
// (non-key) uuid column both carry `default: ""`, so this is specific to the
|
|
2945
|
+
// uuid key. See {@link FieldOptions.noDefault}.
|
|
2946
|
+
// `nullable` is pinned on both, not left to the per-type default: a uuid
|
|
2947
|
+
// column is nullable by default (see NULLABLE_BY_DEFAULT), but a PRIMARY KEY
|
|
2948
|
+
// never is, and every captured `id`/`created_at` stores `nullable: false`.
|
|
2949
|
+
{
|
|
2950
|
+
name: "id",
|
|
2951
|
+
type: idType,
|
|
2952
|
+
required: true,
|
|
2953
|
+
nullable: false,
|
|
2954
|
+
...idType === "uuid" ? { noDefault: true } : {}
|
|
2955
|
+
},
|
|
2956
|
+
{ name: "created_at", type: "epochms", default: "now", access: "private", nullable: false }
|
|
2957
|
+
];
|
|
2958
|
+
}
|
|
2959
|
+
function tableColumns(def) {
|
|
2960
|
+
const cols = toColumns(def.schema);
|
|
2961
|
+
if (def.system === false) return cols;
|
|
2962
|
+
const present = new Set(cols.map((col2) => col2.name));
|
|
2963
|
+
const missing = systemColumns(def.idType).filter((sc) => !present.has(sc.name));
|
|
2964
|
+
return [...missing, ...cols];
|
|
2965
|
+
}
|
|
2966
|
+
function systemIndexes(useXdo = false) {
|
|
2967
|
+
return [
|
|
2968
|
+
{ type: "primary", fields: [{ name: "id" }] },
|
|
2969
|
+
...useXdo ? [{ type: "gin", fields: [{ name: "xdo", op: "jsonb_path_op" }] }] : [],
|
|
2970
|
+
{ type: "btree", fields: [{ name: "created_at", op: "desc" }] }
|
|
2971
|
+
];
|
|
2972
|
+
}
|
|
2973
|
+
function indexSignature(def) {
|
|
2974
|
+
return `${normalizeIndexType(def.type)}|${def.fields.map((field) => field.name).join(",")}`;
|
|
2975
|
+
}
|
|
2976
|
+
function tableIndexes(def) {
|
|
2977
|
+
const declared = def.index ?? [];
|
|
2978
|
+
if (def.system === false) return declared;
|
|
2979
|
+
const present = new Set(declared.map(indexSignature));
|
|
2980
|
+
const missing = systemIndexes(def.useXdo).filter((idx) => !present.has(indexSignature(idx)));
|
|
2981
|
+
return [...missing, ...declared];
|
|
2982
|
+
}
|
|
2983
|
+
var SYSTEM_COLUMN_NAMES = systemColumns().map((col2) => col2.name);
|
|
2984
|
+
function elideSystemIndexes(index, useXdo) {
|
|
2985
|
+
const system = systemIndexes(useXdo);
|
|
2986
|
+
const encoded = (list) => JSON.stringify(list.map(encodeIndex));
|
|
2987
|
+
const target = encoded(index);
|
|
2988
|
+
for (const subset of subsetsLargestFirst(system.length)) {
|
|
2989
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2990
|
+
for (const position of subset) {
|
|
2991
|
+
const signature = indexSignature(system[position]);
|
|
2992
|
+
pending.set(signature, (pending.get(signature) ?? 0) + 1);
|
|
2993
|
+
}
|
|
2994
|
+
const kept = index.filter((entry) => {
|
|
2995
|
+
const remaining = pending.get(indexSignature(entry)) ?? 0;
|
|
2996
|
+
if (remaining === 0) return true;
|
|
2997
|
+
pending.set(indexSignature(entry), remaining - 1);
|
|
2998
|
+
return false;
|
|
2999
|
+
});
|
|
3000
|
+
if (kept.length === index.length) continue;
|
|
3001
|
+
if (encoded(tableIndexes({ index: kept, useXdo })) === target) return kept;
|
|
3002
|
+
}
|
|
3003
|
+
return null;
|
|
3004
|
+
}
|
|
3005
|
+
function subsetsLargestFirst(n) {
|
|
3006
|
+
const all = [];
|
|
3007
|
+
for (let mask = 1; mask < 1 << n; mask += 1) {
|
|
3008
|
+
const members = [];
|
|
3009
|
+
for (let bit = 0; bit < n; bit += 1) if (mask & 1 << bit) members.push(bit);
|
|
3010
|
+
all.push(members);
|
|
3011
|
+
}
|
|
3012
|
+
return all.sort((a, b) => b.length - a.length || a[0] - b[0]);
|
|
3013
|
+
}
|
|
3014
|
+
function encodeIndex(def) {
|
|
3015
|
+
return {
|
|
3016
|
+
name: def.name ?? "",
|
|
3017
|
+
lang: def.lang ?? "",
|
|
3018
|
+
type: normalizeIndexType(def.type),
|
|
3019
|
+
fields: def.fields.map((field) => ({ name: field.name, op: field.op ?? "" })),
|
|
3020
|
+
market_item: { id: 0, version: 0, guid: "" }
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
function encodeView(def) {
|
|
3024
|
+
return {
|
|
3025
|
+
alias: def.alias ?? "",
|
|
3026
|
+
hiddenCols: def.hide ?? [],
|
|
3027
|
+
id: def.id,
|
|
3028
|
+
name: def.name,
|
|
3029
|
+
q: def.q ?? "",
|
|
3030
|
+
expression: def.where ? encodeComparison(def.where).expression : [],
|
|
3031
|
+
sort: (def.sort ?? []).map((s) => ({ name: s.name, order: s.order }))
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
function encodeColumn(def) {
|
|
3035
|
+
const { name, type, ...options } = def;
|
|
3036
|
+
return encodeField(name, type, options, COLUMN_CONTEXT);
|
|
3037
|
+
}
|
|
3038
|
+
function assertBmpColumnDefault(tableName, col2) {
|
|
3039
|
+
if (col2.default === void 0) return;
|
|
3040
|
+
for (const ch of String(col2.default)) {
|
|
3041
|
+
const cp = ch.codePointAt(0);
|
|
3042
|
+
if (cp > 65535) {
|
|
3043
|
+
const u = `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3044
|
+
throw new Error(
|
|
3045
|
+
`table "${tableName}", column "${col2.name}": \`default\` contains a 4-byte character (${JSON.stringify(ch)}, ${u}). The engine mangles such characters into an invalid UTF-8 sequence in the column default, failing at deploy with Postgres 22021 (invalid byte sequence for encoding "UTF8"). Use a default within the Basic Multilingual Plane (accents, most CJK, and \u20AC are fine), or move the value onto an endpoint input (\`input.text({ default })\`), applied at runtime bind. (issue #45)`
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
function assertIndexColumns(def) {
|
|
3051
|
+
const known = tableColumns(def).map((col2) => col2.name);
|
|
3052
|
+
const set = /* @__PURE__ */ new Set([...known, "xdo"]);
|
|
3053
|
+
for (const index of def.index ?? []) {
|
|
3054
|
+
for (const field of index.fields) {
|
|
3055
|
+
if (set.has(field.name)) continue;
|
|
3056
|
+
const column = field.name.slice(0, field.name.indexOf("."));
|
|
3057
|
+
if (column !== "" && set.has(column)) continue;
|
|
3058
|
+
const signature = `${normalizeIndexType(index.type)}(${index.fields.map((f2) => f2.name).join(", ")})`;
|
|
3059
|
+
throw new Error(
|
|
3060
|
+
`table "${def.name}", index "${signature}": unknown column "${field.name}" (not in table schema). Known columns: ${known.join(", ")}. An index over a column that does not exist exports cleanly and fails at deploy with an engine error that names neither. (issue #187)`
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
function encodeTable(def) {
|
|
3066
|
+
if (!def.name) throw new Error("table: `name` is required.");
|
|
3067
|
+
for (const col2 of tableColumns(def)) assertBmpColumnDefault(def.name, col2);
|
|
3068
|
+
assertIndexColumns(def);
|
|
3069
|
+
return {
|
|
3070
|
+
name: def.name,
|
|
3071
|
+
description: def.description ?? "",
|
|
3072
|
+
docs: def.docs ?? "",
|
|
3073
|
+
auth: def.auth ?? false,
|
|
3074
|
+
install: def.install ?? false,
|
|
3075
|
+
schema: tableColumns(def).map(encodeColumn),
|
|
3076
|
+
index: tableIndexes(def).map(encodeIndex),
|
|
3077
|
+
autocomplete: (def.autocomplete ?? []).map((name) => ({ name })),
|
|
3078
|
+
external: def.external ?? { source: "", id: "" },
|
|
3079
|
+
views: (def.views ?? []).map(encodeView),
|
|
3080
|
+
tag: encodeTags(def.tags),
|
|
3081
|
+
// A table has no `as` — it returns nothing (it's a datastore, not a stack
|
|
3082
|
+
// statement); confirmed 0/16 live `mvp_dbo` rows carry one. (Older golden
|
|
3083
|
+
// export fixtures store `as:<name>`, a stale generation — see normalize.ts.)
|
|
3084
|
+
// `sql_name` is the engine's physical table name; persisted as "" (the
|
|
3085
|
+
// engine derives the real name) — confirmed against live `mvp_dbo`.
|
|
3086
|
+
sql_name: "",
|
|
3087
|
+
use_xdo: def.useXdo ?? false,
|
|
3088
|
+
market_item: { id: 0, version: 0, guid: "" }
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
var tableKind = {
|
|
3092
|
+
name: "table",
|
|
3093
|
+
payloadKey: "dbo",
|
|
3094
|
+
encode: encodeTable
|
|
3095
|
+
};
|
|
3096
|
+
registerKind(tableKind);
|
|
3097
|
+
function table(def) {
|
|
3098
|
+
return def;
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
// src/responses/response.ts
|
|
3102
|
+
function warnUnboundReturn(kind, name, stack, response) {
|
|
3103
|
+
if (response !== void 0) return;
|
|
3104
|
+
if (!stack?.some((statement) => statement?.name === "mvp:return")) return;
|
|
3105
|
+
emitDiagnostic({
|
|
3106
|
+
severity: "warning",
|
|
3107
|
+
code: "response.unbound-return",
|
|
3108
|
+
message: `${kind} "${name}" ends with s.return(...) but has no \`response\` field. The value DOES come back at runtime \u2014 a top-level s.return short-circuits the stack and carries its own value out \u2014 but the stored response envelope is empty, so the shape is invisible to InferResponse, to a typed frontend, and to codegen. Declare it too: \`response: <value>\` (e.g. \`response: ref("...")\`). Where both exist, whichever executes wins \u2014 an early s.return that fires beats the declared response.`
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
3111
|
+
function isValue2(x) {
|
|
3112
|
+
return typeof x.tag === "string" && "value" in x && "filters" in x;
|
|
3113
|
+
}
|
|
3114
|
+
function encodeResponse(response) {
|
|
3115
|
+
if (response === void 0) {
|
|
3116
|
+
return [];
|
|
3117
|
+
}
|
|
3118
|
+
const carried = rawResponseItems(response);
|
|
3119
|
+
if (carried !== void 0) return carried;
|
|
3120
|
+
if (isValue2(response)) {
|
|
3121
|
+
return [
|
|
3122
|
+
{ filters: response.filters, name: "", tag: response.tag, value: response.value, _xsid: "", disabled: false }
|
|
3123
|
+
];
|
|
3124
|
+
}
|
|
3125
|
+
return Object.entries(response).map(([name, member]) => {
|
|
3126
|
+
const value = isValue2(member) ? member : obj(member);
|
|
3127
|
+
return { filters: value.filters, name, tag: value.tag, value: value.value, _xsid: "", disabled: false };
|
|
3128
|
+
});
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
// src/kinds/history.ts
|
|
3132
|
+
var CONTAINER_DEFAULT_ENABLED = {
|
|
3133
|
+
query: true,
|
|
3134
|
+
tool: true,
|
|
3135
|
+
message: false
|
|
3136
|
+
};
|
|
3137
|
+
var WORKSPACE_HISTORY_TYPES = [
|
|
3138
|
+
"query",
|
|
3139
|
+
"function",
|
|
3140
|
+
"task",
|
|
3141
|
+
"tool",
|
|
3142
|
+
"trigger",
|
|
3143
|
+
"middleware",
|
|
3144
|
+
"message"
|
|
3145
|
+
];
|
|
3146
|
+
function scalarToEnabledLimit(input2) {
|
|
3147
|
+
if (input2 === false) return { enabled: false, limit: 100 };
|
|
3148
|
+
if (input2 === true) return { enabled: true, limit: 100 };
|
|
3149
|
+
if (input2 === "all") return { enabled: true, limit: -1 };
|
|
3150
|
+
if (!Number.isInteger(input2) || input2 < 0) {
|
|
3151
|
+
throw new Error(
|
|
3152
|
+
`history: a numeric limit must be a non-negative integer (the capture depth), or use "all" for unlimited \u2014 got ${input2}`
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
return { enabled: true, limit: input2 };
|
|
3156
|
+
}
|
|
3157
|
+
function encodeHistory(objType, input2) {
|
|
3158
|
+
if (input2 === void 0) return defaultHistory(objType);
|
|
3159
|
+
const { enabled, limit } = scalarToEnabledLimit(input2);
|
|
3160
|
+
return { inherit: false, enabled, limit };
|
|
3161
|
+
}
|
|
3162
|
+
function encodeContainerHistory(prefix, input2) {
|
|
3163
|
+
const { inherit, enabled, limit } = input2 === void 0 ? { inherit: true, enabled: CONTAINER_DEFAULT_ENABLED[prefix], limit: 100 } : { inherit: false, ...scalarToEnabledLimit(input2) };
|
|
3164
|
+
return {
|
|
3165
|
+
inherit,
|
|
3166
|
+
[`${prefix}_enabled`]: enabled,
|
|
3167
|
+
[`${prefix}_limit`]: limit
|
|
3168
|
+
};
|
|
3169
|
+
}
|
|
3170
|
+
function buildWorkspaceHistory(map) {
|
|
3171
|
+
const out2 = {};
|
|
3172
|
+
for (const t of WORKSPACE_HISTORY_TYPES) {
|
|
3173
|
+
const input2 = map[t];
|
|
3174
|
+
const { enabled, limit } = input2 === void 0 ? { enabled: defaultHistory(t).enabled, limit: 100 } : scalarToEnabledLimit(input2);
|
|
3175
|
+
out2[`${t}_enabled`] = enabled;
|
|
3176
|
+
out2[`${t}_limit`] = limit;
|
|
3177
|
+
}
|
|
3178
|
+
return out2;
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
// src/kinds/middleware-attach.ts
|
|
3182
|
+
function isRawEntry(entry) {
|
|
3183
|
+
return typeof entry === "object" && entry !== null && entry[RAW_ENVELOPE] !== void 0;
|
|
3184
|
+
}
|
|
3185
|
+
function parseEntry(entry) {
|
|
3186
|
+
if (typeof entry === "object" && "middleware" in entry) {
|
|
3187
|
+
return { ref: entry.middleware, active: entry.active };
|
|
3188
|
+
}
|
|
3189
|
+
return { ref: entry };
|
|
3190
|
+
}
|
|
3191
|
+
function middlewareAttachStatement(args) {
|
|
3192
|
+
return {
|
|
3193
|
+
name: "mvp:middleware",
|
|
3194
|
+
context: { middleware: { id: resolveRef("middleware", args.middleware) } },
|
|
3195
|
+
...args.active === false ? { disabled: true } : {}
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
registerStatement("mvp:middleware", middlewareAttachStatement);
|
|
3199
|
+
function encodeMiddlewareEntry(entry) {
|
|
3200
|
+
if (isRawEntry(entry)) return encodeStatement(entry);
|
|
3201
|
+
const { ref: ref2, active } = parseEntry(entry);
|
|
3202
|
+
return encodeStatement(middlewareAttachStatement({ middleware: ref2, active }));
|
|
3203
|
+
}
|
|
3204
|
+
function middlewareEntryGuid(entry) {
|
|
3205
|
+
const id = entry?.context?.middleware?.id;
|
|
3206
|
+
return typeof id === "string" ? id : void 0;
|
|
3207
|
+
}
|
|
3208
|
+
function buildMiddlewareBlock(attach) {
|
|
3209
|
+
if (!attach) return emptyMiddleware();
|
|
3210
|
+
return {
|
|
3211
|
+
pre_customize: attach.pre !== void 0,
|
|
3212
|
+
post_customize: attach.post !== void 0,
|
|
3213
|
+
pre: encodeMiddlewareList(attach.pre),
|
|
3214
|
+
post: encodeMiddlewareList(attach.post)
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3217
|
+
function encodeMiddlewareList(entries) {
|
|
3218
|
+
return (entries ?? []).map(encodeMiddlewareEntry);
|
|
3219
|
+
}
|
|
3220
|
+
function clear() {
|
|
3221
|
+
return [];
|
|
3222
|
+
}
|
|
3223
|
+
function stackReferencesAuth(stack) {
|
|
3224
|
+
return (stack ?? []).some(nodeReferencesAuth);
|
|
3225
|
+
}
|
|
3226
|
+
function nodeReferencesAuth(node) {
|
|
3227
|
+
if (node === null || typeof node !== "object" && typeof node !== "function") return false;
|
|
3228
|
+
if (isTaggedValue(node) && node.tag === "auth") return true;
|
|
3229
|
+
if (Array.isArray(node)) return node.some(nodeReferencesAuth);
|
|
3230
|
+
return Object.values(node).some(nodeReferencesAuth);
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
// src/refs/auth.ts
|
|
3234
|
+
function resolveAuthRef(hostLabel, host, auth2) {
|
|
3235
|
+
if (auth2 === void 0 || auth2 === false || auth2 === null) return false;
|
|
3236
|
+
if (auth2 === true) {
|
|
3237
|
+
throw new Error(
|
|
3238
|
+
`${hostLabel} "${host}": \`auth: true\` is no longer supported. Pass the auth table (e.g. \`auth: user\` for a \`table({ auth: true })\`) or its numeric id.`
|
|
3239
|
+
);
|
|
3240
|
+
}
|
|
3241
|
+
if (typeof auth2 === "number") {
|
|
3242
|
+
if (!Number.isInteger(auth2) || auth2 <= 0) {
|
|
3243
|
+
throw new Error(
|
|
3244
|
+
`${hostLabel} "${host}": numeric \`auth\` must be a positive integer \`dbo.id\` (got ${auth2}). Use \`false\` for no auth, or pass the auth table def/name.`
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
return auth2;
|
|
3248
|
+
}
|
|
3249
|
+
return resolveRef("dbo", auth2);
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
// src/statements/lean-input.ts
|
|
3253
|
+
function leanInput(name, v2) {
|
|
3254
|
+
assertInputValue(name, v2);
|
|
3255
|
+
return { name, tag: v2.tag, value: v2.value, filters: v2.filters };
|
|
3256
|
+
}
|
|
3257
|
+
function assertInputValue(name, v2) {
|
|
3258
|
+
if (isInputValue(v2)) return;
|
|
3259
|
+
throw new Error(
|
|
3260
|
+
`input \`${name}\`: expected a tagged value (\`c.*\`/\`inp\`/\`ref\`/\`col\`/\u2026), got ${describe(v2)}. An input stores \`{tag,value,filters}\` copied off this argument, so a plain JS value emits an empty slot that deploys clean and then fails every request with "Unable to locate input: ". Wrap it \u2014 an array of rows is \`c.array([{ \u2026 }])\` (plain JSON inside, no nested \`c.*\`), a record is \`c.obj({ \u2026 })\`.`
|
|
3261
|
+
);
|
|
3262
|
+
}
|
|
3263
|
+
function isInputValue(v2) {
|
|
3264
|
+
return (typeof v2 === "object" || typeof v2 === "function") && v2 !== null && typeof v2.tag === "string" && "value" in v2 && Array.isArray(v2.filters);
|
|
3265
|
+
}
|
|
3266
|
+
function describe(v2) {
|
|
3267
|
+
if (v2 === null) return "null";
|
|
3268
|
+
if (v2 === void 0) return "undefined";
|
|
3269
|
+
if (Array.isArray(v2)) return `a JS array (${v2.length} item${v2.length === 1 ? "" : "s"})`;
|
|
3270
|
+
if (typeof v2 === "object") return "a plain object";
|
|
3271
|
+
return `a ${typeof v2}`;
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
// src/kinds/datasource.ts
|
|
3275
|
+
function warnLiveDatasource(opts) {
|
|
3276
|
+
if (opts.datasource.trim().toLowerCase() !== "live") return;
|
|
3277
|
+
emitDiagnostic({
|
|
3278
|
+
severity: "warning",
|
|
3279
|
+
code: opts.code,
|
|
3280
|
+
message: `${opts.subject} runs against the "live" datasource. Running a test CLONES its datasource first \u2014 against production-sized data this is slow enough to fail the run. Prefer \`datasource: ""\` (an empty datasource, the recommended default) or a small fixture datasource.`
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
// src/kinds/test.ts
|
|
3285
|
+
function testIdSeed(ownerKind, ownerName, testName) {
|
|
3286
|
+
return `${ownerKind}/${ownerName}:${testName}`;
|
|
3287
|
+
}
|
|
3288
|
+
function encodeTest(def, ownerKind, ownerName) {
|
|
3289
|
+
if (!def.name) throw new Error("test: `name` is required.");
|
|
3290
|
+
const datasource = def.datasource ?? "";
|
|
3291
|
+
warnLiveDatasource({
|
|
3292
|
+
code: "test.live-datasource",
|
|
3293
|
+
subject: `test "${def.name}"`,
|
|
3294
|
+
datasource
|
|
3295
|
+
});
|
|
3296
|
+
const xdo = {
|
|
3297
|
+
datasource,
|
|
3298
|
+
description: def.description ?? "",
|
|
3299
|
+
expect: def.expect ?? [],
|
|
3300
|
+
id: def.id ?? deriveGuid("test", testIdSeed(ownerKind, ownerName, def.name)),
|
|
3301
|
+
input: Object.entries(def.input ?? {}).map(([name, v2]) => leanInput(name, v2)),
|
|
3302
|
+
name: def.name,
|
|
3303
|
+
token: def.token ?? ""
|
|
3304
|
+
};
|
|
3305
|
+
return xdo;
|
|
3306
|
+
}
|
|
3307
|
+
function encodeTests(defs, ownerKind, ownerName) {
|
|
3308
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3309
|
+
for (const def of defs ?? []) {
|
|
3310
|
+
if (seen.has(def.name)) {
|
|
3311
|
+
throw new Error(
|
|
3312
|
+
`${ownerKind} "${ownerName}": two tests are named "${def.name}". A test name is how a statement's \`mock\` names the test it belongs to, so names must be unique within an object. Rename one of them.`
|
|
3313
|
+
);
|
|
3314
|
+
}
|
|
3315
|
+
seen.add(def.name);
|
|
3316
|
+
}
|
|
3317
|
+
return (defs ?? []).map((def) => encodeTest(def, ownerKind, ownerName));
|
|
3318
|
+
}
|
|
3319
|
+
function test(def) {
|
|
3320
|
+
return def;
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
// src/kinds/stored-name.ts
|
|
3324
|
+
var NAME_MAX = 200;
|
|
3325
|
+
var EXTRA = {
|
|
3326
|
+
route: "_-{}/",
|
|
3327
|
+
plain: "_-"
|
|
3328
|
+
};
|
|
3329
|
+
var PATTERN = {
|
|
3330
|
+
route: /^[A-Za-z0-9_\-{}/]+$/,
|
|
3331
|
+
plain: /^[A-Za-z0-9_-]+$/
|
|
3332
|
+
};
|
|
3333
|
+
var DESCRIPTION = {
|
|
3334
|
+
route: 'only letters, digits, "_", "-", "/", and the "{}" of a path param',
|
|
3335
|
+
plain: 'only letters, digits, "_" and "-"'
|
|
3336
|
+
};
|
|
3337
|
+
function assertCanonical(context, canonical) {
|
|
3338
|
+
if (canonical === void 0 || canonical === "") return;
|
|
3339
|
+
if (canonical.length > NAME_MAX) {
|
|
3340
|
+
throw new Error(
|
|
3341
|
+
`${context}: the canonical is ${canonical.length} characters \u2014 the engine stores at most ${NAME_MAX}. Shorten it, or omit it and let \`xanots export <entry> --lock\` mint one.`
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
if (PATTERN.plain.test(canonical)) return;
|
|
3345
|
+
const bad = [.../* @__PURE__ */ new Set([...canonical])].filter((ch) => !/[A-Za-z0-9_-]/.test(ch));
|
|
3346
|
+
throw new Error(
|
|
3347
|
+
`${context}: the canonical contains ${bad.map((ch) => JSON.stringify(ch)).join(", ")}, which cannot appear in a URL token. A canonical holds ${DESCRIPTION.plain} \u2014 it is the \`<canonical>\` of \`/api:<canonical>\` and of the MCP/realtime stream paths. Unlike a name, the engine does NOT reject or blank it: it stores the value verbatim, the deploy succeeds, and every client then 404s against an address the router cannot match. Omit \`canonical\` to let \`xanots export <entry> --lock\` mint one, or supply a token in charset.`
|
|
3348
|
+
);
|
|
3349
|
+
}
|
|
3350
|
+
function assertStoredName(context, name, shape) {
|
|
3351
|
+
if (name.length > NAME_MAX) {
|
|
3352
|
+
throw new Error(
|
|
3353
|
+
`${context}: the name is ${name.length} characters \u2014 the engine stores at most ${NAME_MAX}, and a longer one is dropped rather than truncated, leaving the object with an empty name. Shorten it.`
|
|
3354
|
+
);
|
|
3355
|
+
}
|
|
3356
|
+
if (PATTERN[shape].test(name)) return;
|
|
3357
|
+
const allowed = /* @__PURE__ */ new Set([...EXTRA[shape]]);
|
|
3358
|
+
const bad = [.../* @__PURE__ */ new Set([...name])].filter(
|
|
3359
|
+
(ch) => !/[A-Za-z0-9]/.test(ch) && !allowed.has(ch)
|
|
3360
|
+
);
|
|
3361
|
+
const list = bad.map((ch) => JSON.stringify(ch)).join(", ");
|
|
3362
|
+
const hint = bad.includes(".") ? ` A "." is the one authors reach for: an endpoint that serves a file wants "export_zip" or "export/zip", not "export.zip" \u2014 the extension belongs in the response headers.` : "";
|
|
3363
|
+
throw new Error(
|
|
3364
|
+
`${context}: the name contains ${list}, which the engine cannot store. This name holds ${DESCRIPTION[shape]}.${hint} Xano does not reject the write \u2014 it saves the object with an EMPTY name, so it deploys clean and then 404s "Unable to locate request." on every request.`
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
// src/kinds/closed-set.ts
|
|
3369
|
+
function assertOneOf(context, field, value, allowed, note) {
|
|
3370
|
+
if (value === void 0 || value === null) return;
|
|
3371
|
+
if (typeof value === "string" && allowed.includes(value)) return;
|
|
3372
|
+
const list = allowed.map((v2) => JSON.stringify(v2)).join(", ");
|
|
3373
|
+
const cased = typeof value === "string" ? allowed.find((v2) => v2.toLowerCase() === value.toLowerCase()) : void 0;
|
|
3374
|
+
const hint = cased ? ` Write it as ${JSON.stringify(cased)} \u2014 the casing is the whole difference.` : "";
|
|
3375
|
+
throw new Error(
|
|
3376
|
+
`${context}: \`${field}\` is ${JSON.stringify(value) ?? String(value)}, which is not one of ${list}.${hint} ${note}`
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
// src/kinds/query.ts
|
|
3381
|
+
var HTTP_VERBS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"];
|
|
3382
|
+
var QUERY_RESPONSE_TYPES = ["standard", "stream"];
|
|
3383
|
+
function encodeExample(example) {
|
|
3384
|
+
const out2 = {};
|
|
3385
|
+
if (example?.input !== void 0 && example.input !== null) out2.input = example.input;
|
|
3386
|
+
if (example?.output !== void 0 && example.output !== null) out2.output = example.output;
|
|
3387
|
+
return out2;
|
|
3388
|
+
}
|
|
3389
|
+
function defaultCache(override) {
|
|
3390
|
+
return {
|
|
3391
|
+
active: false,
|
|
3392
|
+
ttl: 3600,
|
|
3393
|
+
input: true,
|
|
3394
|
+
auth: true,
|
|
3395
|
+
datasource: true,
|
|
3396
|
+
ip: false,
|
|
3397
|
+
headers: [],
|
|
3398
|
+
env: [],
|
|
3399
|
+
...override
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
function resolveAuth(name, auth2) {
|
|
3403
|
+
return resolveAuthRef("query", name, auth2);
|
|
3404
|
+
}
|
|
3405
|
+
function assertQueryPathParams(def) {
|
|
3406
|
+
const context = `query "${def.name}"`;
|
|
3407
|
+
assertStoredName(context, def.name, "route");
|
|
3408
|
+
const params = parsePathParams(context, def.name);
|
|
3409
|
+
assertPathParamInputs(context, params, def.input);
|
|
3410
|
+
return params;
|
|
3411
|
+
}
|
|
3412
|
+
function assertQueryClosedSets(def) {
|
|
3413
|
+
const context = `query "${def.name}"`;
|
|
3414
|
+
assertOneOf(
|
|
3415
|
+
context,
|
|
3416
|
+
"verb",
|
|
3417
|
+
def.verb,
|
|
3418
|
+
HTTP_VERBS,
|
|
3419
|
+
'The engine does not reject an unrecognized verb \u2014 it stores NULL, and a null verb serves as GET. So the endpoint deploys clean, answers on the wrong method, and the method you meant 404s "Unable to locate request.", which reads as a routing or api-group problem.'
|
|
3420
|
+
);
|
|
3421
|
+
assertOneOf(
|
|
3422
|
+
context,
|
|
3423
|
+
"responseType",
|
|
3424
|
+
def.responseType,
|
|
3425
|
+
QUERY_RESPONSE_TYPES,
|
|
3426
|
+
'The engine does not reject an unrecognized response_type \u2014 it stores NULL, which falls back to "standard". So a misspelled stream deploys clean and then quietly buffers the whole response instead of streaming it.'
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
function encodeQuery(def) {
|
|
3430
|
+
if (!def.name) throw new Error("query: `name` is required.");
|
|
3431
|
+
if (!def.verb) throw new Error("query: `verb` is required.");
|
|
3432
|
+
assertQueryPathParams(def);
|
|
3433
|
+
assertQueryClosedSets(def);
|
|
3434
|
+
warnUnboundReturn("query", def.name, def.stack, def.response);
|
|
3435
|
+
const xdo = {
|
|
3436
|
+
name: def.name,
|
|
3437
|
+
description: def.description ?? "",
|
|
3438
|
+
docs: def.docs ?? "",
|
|
3439
|
+
api_enabled: def.apiEnabled ?? true,
|
|
3440
|
+
auth: resolveAuth(def.name, def.auth),
|
|
3441
|
+
response_type: def.responseType ?? "standard",
|
|
3442
|
+
verb: def.verb,
|
|
3443
|
+
disabled: def.disabled ?? false,
|
|
3444
|
+
// The engine binds a query to its api group by the group's guid (export
|
|
3445
|
+
// maps id→guid, import remaps guid→local id). Deriving that guid from the
|
|
3446
|
+
// group's name is stateless and stable across syncs. A numeric `apiGroupId`
|
|
3447
|
+
// is an explicit escape hatch and wins when given.
|
|
3448
|
+
app: {
|
|
3449
|
+
id: def.apiGroupId ?? (def.apiGroup !== void 0 ? resolveRef("app", def.apiGroup) : 0)
|
|
3450
|
+
},
|
|
3451
|
+
cache: defaultCache(def.cache),
|
|
3452
|
+
output: [],
|
|
3453
|
+
middleware: buildMiddlewareBlock(def.middleware),
|
|
3454
|
+
tag: encodeTags(def.tags),
|
|
3455
|
+
history: encodeHistory("query", def.history),
|
|
3456
|
+
input: Object.entries(def.input ?? {}).map(([name, d]) => encodeInput(name, d)),
|
|
3457
|
+
result: encodeResponse(def.response),
|
|
3458
|
+
run: (def.stack ?? []).map(encodeStatement),
|
|
3459
|
+
test: encodeTests(def.tests, "query", def.name),
|
|
3460
|
+
example: encodeExample(def.example),
|
|
3461
|
+
market_item: { id: 0, version: 0, guid: "" }
|
|
3462
|
+
};
|
|
3463
|
+
resolveMockKeys(xdo.run, xdo.test, "query", def.name);
|
|
3464
|
+
return xdo;
|
|
3465
|
+
}
|
|
3466
|
+
var queryKind = {
|
|
3467
|
+
name: "query",
|
|
3468
|
+
payloadKey: "query",
|
|
3469
|
+
encode: encodeQuery
|
|
3470
|
+
};
|
|
3471
|
+
registerKind(queryKind);
|
|
3472
|
+
function resolveCanonical(def, override) {
|
|
3473
|
+
if (override) return override;
|
|
3474
|
+
const group = def.apiGroup;
|
|
3475
|
+
if (group && typeof group === "object" && typeof group.canonical === "string" && group.canonical !== "") {
|
|
3476
|
+
return group.canonical;
|
|
3477
|
+
}
|
|
3478
|
+
const groupName = typeof group === "string" ? group : group?.name;
|
|
3479
|
+
if (groupName) {
|
|
3480
|
+
const locked = getLockedCanonical(lockKey("app", groupName));
|
|
3481
|
+
if (locked) return locked;
|
|
3482
|
+
}
|
|
3483
|
+
throw new Error(
|
|
3484
|
+
`query "${def.name}": getPath() cannot resolve the api group's canonical URL token. Set an explicit \`apiGroup({ canonical })\`, or run \`xanots export <entry> --lock\` once (it mints a unique canonical and freezes it in xano.lock) and seed that lock before importing defs \u2014 the CLI does this automatically, and build scripts call seedLockOverrides(readLockFile(path)) first. As a last resort pass one directly: getPath({ canonical: "..." }). (Minting here is unsafe \u2014 canonicals must be unique per instance across all workspaces, so they are only generated at locked export.)`
|
|
3485
|
+
);
|
|
3486
|
+
}
|
|
3487
|
+
function pathSegment(name) {
|
|
3488
|
+
return name.replace(/^\/+/, "");
|
|
3489
|
+
}
|
|
3490
|
+
function queryImpl(def) {
|
|
3491
|
+
const params = assertQueryPathParams(def);
|
|
3492
|
+
assertQueryClosedSets(def);
|
|
3493
|
+
const context = `query "${def.name}"`;
|
|
3494
|
+
const path = pathSegment(def.name);
|
|
3495
|
+
const getPath = (opts) => `/api:${resolveCanonical(def, opts?.canonical)}/${params.length ? fillPathParams(context, "getPath()", path, opts?.params, true) : path}`;
|
|
3496
|
+
const search = (values) => toSearchParams(
|
|
3497
|
+
params.length ? Object.fromEntries(Object.entries(values).filter(([key]) => !params.includes(key))) : values
|
|
3498
|
+
);
|
|
3499
|
+
return { ...def, getPath, toSearchParams: search };
|
|
3500
|
+
}
|
|
3501
|
+
function toSearchParams(input2) {
|
|
3502
|
+
const params = new URLSearchParams();
|
|
3503
|
+
const append = (key, item) => {
|
|
3504
|
+
if (typeof item === "number" && !Number.isFinite(item)) {
|
|
3505
|
+
throw new TypeError(`toSearchParams: param "${key}" is ${item} \u2014 not a finite value`);
|
|
3506
|
+
}
|
|
3507
|
+
if (typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
|
|
3508
|
+
throw new TypeError(`toSearchParams: param "${key}" is not a scalar (got ${typeof item})`);
|
|
3509
|
+
}
|
|
3510
|
+
params.append(key, String(item));
|
|
3511
|
+
};
|
|
3512
|
+
for (const [key, value] of Object.entries(input2)) {
|
|
3513
|
+
if (value === void 0 || value === null) continue;
|
|
3514
|
+
if (Array.isArray(value)) {
|
|
3515
|
+
for (const item of value) {
|
|
3516
|
+
if (item === void 0 || item === null) continue;
|
|
3517
|
+
append(key, item);
|
|
3518
|
+
}
|
|
3519
|
+
} else {
|
|
3520
|
+
append(key, value);
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
return params;
|
|
3524
|
+
}
|
|
3525
|
+
var query = Object.assign(queryImpl, { toSearchParams });
|
|
3526
|
+
|
|
3527
|
+
// src/workspace/export.ts
|
|
3528
|
+
var BUNDLE_APP = "xano";
|
|
3529
|
+
var BUNDLE_VERSION_JSON = "1.03";
|
|
3530
|
+
var PAYLOAD_ARRAY_KEYS = [
|
|
3531
|
+
"dbo",
|
|
3532
|
+
"addon",
|
|
3533
|
+
"function",
|
|
3534
|
+
"middleware",
|
|
3535
|
+
"trigger",
|
|
3536
|
+
"task",
|
|
3537
|
+
"query",
|
|
3538
|
+
"tool",
|
|
3539
|
+
"toolset",
|
|
3540
|
+
"app",
|
|
3541
|
+
// Realtime, in dependency order: a channel resolves its server, and a message
|
|
3542
|
+
// resolves both. They sit after `app` so every object they can reference
|
|
3543
|
+
// (tables for auth, api groups) is already in place.
|
|
3544
|
+
"realtime_server",
|
|
3545
|
+
"channel",
|
|
3546
|
+
"message",
|
|
3547
|
+
// A container workload. Independent of every other object — nothing
|
|
3548
|
+
// references it and it references nothing — so its position is arbitrary.
|
|
3549
|
+
"microservice",
|
|
3550
|
+
"vault",
|
|
3551
|
+
"market_item",
|
|
3552
|
+
"run_install",
|
|
3553
|
+
"action_package_install",
|
|
3554
|
+
"env",
|
|
3555
|
+
"workflow_test",
|
|
3556
|
+
"service",
|
|
3557
|
+
"branch"
|
|
3558
|
+
];
|
|
3559
|
+
function phpEncodeString(str) {
|
|
3560
|
+
let out2 = '"';
|
|
3561
|
+
for (const ch of str) {
|
|
3562
|
+
const code = ch.codePointAt(0);
|
|
3563
|
+
switch (ch) {
|
|
3564
|
+
case '"':
|
|
3565
|
+
out2 += "\\u0022";
|
|
3566
|
+
break;
|
|
3567
|
+
// JSON_HEX_QUOT
|
|
3568
|
+
case "\\":
|
|
3569
|
+
out2 += "\\\\";
|
|
3570
|
+
break;
|
|
3571
|
+
case "<":
|
|
3572
|
+
out2 += "\\u003C";
|
|
3573
|
+
break;
|
|
3574
|
+
// JSON_HEX_TAG
|
|
3575
|
+
case ">":
|
|
3576
|
+
out2 += "\\u003E";
|
|
3577
|
+
break;
|
|
3578
|
+
// JSON_HEX_TAG
|
|
3579
|
+
case "&":
|
|
3580
|
+
out2 += "\\u0026";
|
|
3581
|
+
break;
|
|
3582
|
+
// JSON_HEX_AMP
|
|
3583
|
+
case "'":
|
|
3584
|
+
out2 += "\\u0027";
|
|
3585
|
+
break;
|
|
3586
|
+
// JSON_HEX_APOS
|
|
3587
|
+
case "\b":
|
|
3588
|
+
out2 += "\\b";
|
|
3589
|
+
break;
|
|
3590
|
+
case "\f":
|
|
3591
|
+
out2 += "\\f";
|
|
3592
|
+
break;
|
|
3593
|
+
case "\n":
|
|
3594
|
+
out2 += "\\n";
|
|
3595
|
+
break;
|
|
3596
|
+
case "\r":
|
|
3597
|
+
out2 += "\\r";
|
|
3598
|
+
break;
|
|
3599
|
+
case " ":
|
|
3600
|
+
out2 += "\\t";
|
|
3601
|
+
break;
|
|
3602
|
+
default:
|
|
3603
|
+
out2 += code < 32 ? "\\u" + code.toString(16).padStart(4, "0") : ch;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
return out2 + '"';
|
|
3607
|
+
}
|
|
3608
|
+
function phpJsonEncode(value) {
|
|
3609
|
+
const out2 = [];
|
|
3610
|
+
const stack = [{ value }];
|
|
3611
|
+
while (stack.length > 0) {
|
|
3612
|
+
const frame = stack.pop();
|
|
3613
|
+
if ("lit" in frame) {
|
|
3614
|
+
out2.push(frame.lit);
|
|
3615
|
+
continue;
|
|
3616
|
+
}
|
|
3617
|
+
const current = frame.value;
|
|
3618
|
+
if (current === null || current === void 0) {
|
|
3619
|
+
out2.push("null");
|
|
3620
|
+
continue;
|
|
3621
|
+
}
|
|
3622
|
+
switch (typeof current) {
|
|
3623
|
+
case "string":
|
|
3624
|
+
out2.push(phpEncodeString(current));
|
|
3625
|
+
continue;
|
|
3626
|
+
case "number":
|
|
3627
|
+
out2.push(Number.isFinite(current) ? JSON.stringify(current) : "0");
|
|
3628
|
+
continue;
|
|
3629
|
+
case "boolean":
|
|
3630
|
+
out2.push(current ? "true" : "false");
|
|
3631
|
+
continue;
|
|
3632
|
+
case "object":
|
|
3633
|
+
break;
|
|
3634
|
+
default:
|
|
3635
|
+
out2.push("null");
|
|
3636
|
+
continue;
|
|
3637
|
+
}
|
|
3638
|
+
if (Array.isArray(current)) {
|
|
3639
|
+
stack.push({ lit: "]" });
|
|
3640
|
+
for (let i = current.length - 1; i >= 0; i--) {
|
|
3641
|
+
stack.push({ value: current[i] });
|
|
3642
|
+
if (i > 0) stack.push({ lit: "," });
|
|
3643
|
+
}
|
|
3644
|
+
stack.push({ lit: "[" });
|
|
3645
|
+
continue;
|
|
3646
|
+
}
|
|
3647
|
+
const obj2 = current;
|
|
3648
|
+
const keys = Object.keys(obj2).filter((key) => obj2[key] !== void 0);
|
|
3649
|
+
stack.push({ lit: "}" });
|
|
3650
|
+
for (let i = keys.length - 1; i >= 0; i--) {
|
|
3651
|
+
const key = keys[i];
|
|
3652
|
+
stack.push({ value: obj2[key] });
|
|
3653
|
+
stack.push({ lit: phpEncodeString(key) + ":" });
|
|
3654
|
+
if (i > 0) stack.push({ lit: "," });
|
|
3655
|
+
}
|
|
3656
|
+
stack.push({ lit: "{" });
|
|
3657
|
+
}
|
|
3658
|
+
return out2.join("");
|
|
3659
|
+
}
|
|
3660
|
+
function base64websafe(bytes) {
|
|
3661
|
+
let bin = "";
|
|
3662
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
3663
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ".");
|
|
3664
|
+
}
|
|
3665
|
+
function calcSignatureJson(exportObj) {
|
|
3666
|
+
const sorted = {};
|
|
3667
|
+
for (const key of Object.keys(exportObj).sort()) {
|
|
3668
|
+
sorted[key] = exportObj[key];
|
|
3669
|
+
}
|
|
3670
|
+
return base64websafe(sha1Bytes(phpJsonEncode(sorted)));
|
|
3671
|
+
}
|
|
3672
|
+
function assertUniqueGuids(payload, lock) {
|
|
3673
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3674
|
+
for (const key of PAYLOAD_ARRAY_KEYS) {
|
|
3675
|
+
const arr = payload[key];
|
|
3676
|
+
if (!Array.isArray(arr)) continue;
|
|
3677
|
+
for (const obj2 of arr) {
|
|
3678
|
+
if (!obj2 || typeof obj2 !== "object") continue;
|
|
3679
|
+
const guid = obj2.guid;
|
|
3680
|
+
if (typeof guid !== "string") continue;
|
|
3681
|
+
const name = obj2.name;
|
|
3682
|
+
const label = `${key}/${typeof name === "string" ? name : "<unnamed>"}`;
|
|
3683
|
+
const prev = seen.get(guid);
|
|
3684
|
+
if (prev !== void 0) {
|
|
3685
|
+
const pinnedBy = lock ? Object.entries(lock.objects).find(([, e]) => e.guid === guid)?.[0] : void 0;
|
|
3686
|
+
const lockNote = pinnedBy ? ` This guid is pinned by lock entry "${pinnedBy}" \u2014 if that entry was moved by \`xanots lock rename\`, a new object now re-derives the old name's guid. Pin a distinct explicit \`guid\` on the new object, or update the lock entry.` : ` Object identity is derived from (type, name) \u2014 two objects of the same kind share a name. Rename one, or pin a distinct explicit \`guid\`.`;
|
|
3687
|
+
throw new Error(`Duplicate object guid (${guid}) shared by "${prev}" and "${label}".` + lockNote);
|
|
3688
|
+
}
|
|
3689
|
+
seen.set(guid, label);
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
function buildContentEnvelope(rows) {
|
|
3694
|
+
const unsigned = {
|
|
3695
|
+
app: BUNDLE_APP,
|
|
3696
|
+
version: BUNDLE_VERSION_JSON,
|
|
3697
|
+
type: "content",
|
|
3698
|
+
payload: rows
|
|
3699
|
+
};
|
|
3700
|
+
return { ...unsigned, sig: calcSignatureJson(unsigned) };
|
|
3701
|
+
}
|
|
3702
|
+
function buildBundle(args) {
|
|
3703
|
+
const workspace = { ...args.workspace ?? {} };
|
|
3704
|
+
const wsEnv = workspace.env;
|
|
3705
|
+
const payload = { partial: false, workspace };
|
|
3706
|
+
for (const key of PAYLOAD_ARRAY_KEYS) {
|
|
3707
|
+
payload[key] = args.sections[key] ?? [];
|
|
3708
|
+
}
|
|
3709
|
+
if (Array.isArray(wsEnv) && wsEnv.length > 0) {
|
|
3710
|
+
payload.env = wsEnv;
|
|
3711
|
+
workspace.env = [];
|
|
3712
|
+
}
|
|
3713
|
+
assertUniqueGuids(payload, args.lock);
|
|
3714
|
+
const unsigned = {
|
|
3715
|
+
app: BUNDLE_APP,
|
|
3716
|
+
version: BUNDLE_VERSION_JSON,
|
|
3717
|
+
type: args.type ?? "workspace",
|
|
3718
|
+
payload
|
|
3719
|
+
};
|
|
3720
|
+
return { ...unsigned, sig: calcSignatureJson(unsigned) };
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
// src/values/query-filters.ts
|
|
3724
|
+
var VECTOR_FILTERS = [
|
|
3725
|
+
// The unqualified spelling, alongside the per-metric ones below. A real
|
|
3726
|
+
// workspace uses it on a vector column and it was reported unresolvable
|
|
3727
|
+
// (issue #137).
|
|
3728
|
+
"vector_distance",
|
|
3729
|
+
"vector_cos_distance",
|
|
3730
|
+
"vector_cos_similarity",
|
|
3731
|
+
"vector_inner_product",
|
|
3732
|
+
"vector_negative_inner_product",
|
|
3733
|
+
"vector_l1_distance",
|
|
3734
|
+
"vector_l2_distance"
|
|
3735
|
+
];
|
|
3736
|
+
var QUERY_EXPRESSION_FILTERS = [
|
|
3737
|
+
...VECTOR_FILTERS,
|
|
3738
|
+
// Geo predicates over a `f.geo.*` column.
|
|
3739
|
+
"covers",
|
|
3740
|
+
"distance",
|
|
3741
|
+
"within",
|
|
3742
|
+
// Full-text rank, over the search index a `search` operand matched against.
|
|
3743
|
+
"search_rank",
|
|
3744
|
+
"unaccent",
|
|
3745
|
+
// SQL-side scalar helpers.
|
|
3746
|
+
"array_length",
|
|
3747
|
+
"at_timezone",
|
|
3748
|
+
"between_filter",
|
|
3749
|
+
"coalesce",
|
|
3750
|
+
"length",
|
|
3751
|
+
"time",
|
|
3752
|
+
"to_timestamp",
|
|
3753
|
+
// Timestamp arithmetic and extraction, computed in the database.
|
|
3754
|
+
"epochms_add_day",
|
|
3755
|
+
"epochms_add_hour",
|
|
3756
|
+
"epochms_add_minute",
|
|
3757
|
+
"epochms_add_month",
|
|
3758
|
+
"epochms_add_sec",
|
|
3759
|
+
"epochms_add_year",
|
|
3760
|
+
"epochms_sub_day",
|
|
3761
|
+
"epochms_sub_hour",
|
|
3762
|
+
"epochms_sub_minute",
|
|
3763
|
+
"epochms_sub_month",
|
|
3764
|
+
"epochms_sub_sec",
|
|
3765
|
+
"epochms_sub_year",
|
|
3766
|
+
"epochms_day",
|
|
3767
|
+
"epochms_dow",
|
|
3768
|
+
"epochms_doy",
|
|
3769
|
+
"epochms_epoch_day",
|
|
3770
|
+
"epochms_epoch_hour",
|
|
3771
|
+
"epochms_epoch_minute",
|
|
3772
|
+
"epochms_epoch_sec",
|
|
3773
|
+
"epochms_hour",
|
|
3774
|
+
"epochms_minute",
|
|
3775
|
+
"epochms_month",
|
|
3776
|
+
"epochms_week",
|
|
3777
|
+
"epochms_year",
|
|
3778
|
+
// SQL-side AGGREGATES, computed by the database over a grouped result. They
|
|
3779
|
+
// are correctly absent from the runtime catalog — the resolvability probe
|
|
3780
|
+
// classifies them as phantoms because they genuinely do not resolve in a value
|
|
3781
|
+
// pipeline — but they are exactly what a db-query aggregate expression is for,
|
|
3782
|
+
// and every one of them works live. Missing here, they were reported as
|
|
3783
|
+
// guaranteed runtime 500s on a working workspace (issue #137).
|
|
3784
|
+
"count_distinct",
|
|
3785
|
+
"median",
|
|
3786
|
+
// The list aggregates: collect a column's values into an array, optionally
|
|
3787
|
+
// ordered, optionally de-duplicated.
|
|
3788
|
+
"to_list",
|
|
3789
|
+
"to_list_asc",
|
|
3790
|
+
"to_list_desc",
|
|
3791
|
+
"to_distinct_list",
|
|
3792
|
+
"to_distinct_list_asc",
|
|
3793
|
+
"to_distinct_list_desc"
|
|
3794
|
+
];
|
|
3795
|
+
var isQueryExpressionFilter = (name) => QUERY_EXPRESSION_FILTERS.includes(name);
|
|
3796
|
+
|
|
3797
|
+
// src/kinds/function.ts
|
|
3798
|
+
function encodeFunction(fn2) {
|
|
3799
|
+
if (!fn2.name) {
|
|
3800
|
+
throw new Error("function kind: `name` is required.");
|
|
3801
|
+
}
|
|
3802
|
+
warnUnboundReturn("function", fn2.name, fn2.stack, fn2.response);
|
|
3803
|
+
const xdo = {
|
|
3804
|
+
name: fn2.name,
|
|
3805
|
+
description: fn2.description ?? "",
|
|
3806
|
+
docs: fn2.docs ?? "",
|
|
3807
|
+
workspace: { id: fn2.workspace ?? 0 },
|
|
3808
|
+
branch: { id: 0 },
|
|
3809
|
+
cache: defaultCache(fn2.cache),
|
|
3810
|
+
history: encodeHistory("function", fn2.history),
|
|
3811
|
+
middleware: buildMiddlewareBlock(fn2.middleware),
|
|
3812
|
+
tag: encodeTags(fn2.tags),
|
|
3813
|
+
input: Object.entries(fn2.input ?? {}).map(
|
|
3814
|
+
([name, descriptor2]) => encodeInput(name, descriptor2)
|
|
3815
|
+
),
|
|
3816
|
+
result: encodeResponse(fn2.response),
|
|
3817
|
+
run: (fn2.stack ?? []).map(encodeStatement),
|
|
3818
|
+
test: encodeTests(fn2.tests, "function", fn2.name),
|
|
3819
|
+
market_item: { id: 0, version: 0, guid: "" },
|
|
3820
|
+
shared_workspace: { is_shared: false }
|
|
3821
|
+
};
|
|
3822
|
+
resolveMockKeys(xdo.run, xdo.test, "function", fn2.name);
|
|
3823
|
+
return xdo;
|
|
3824
|
+
}
|
|
3825
|
+
var functionKind = {
|
|
3826
|
+
name: "function",
|
|
3827
|
+
payloadKey: "function",
|
|
3828
|
+
encode: encodeFunction
|
|
3829
|
+
};
|
|
3830
|
+
registerKind(functionKind);
|
|
3831
|
+
|
|
3832
|
+
// src/function/compile.ts
|
|
3833
|
+
function compile(fn2) {
|
|
3834
|
+
return encodeFunction(fn2);
|
|
3835
|
+
}
|
|
3836
|
+
|
|
3837
|
+
// src/emit/emit.ts
|
|
3838
|
+
function emit(def, opts = {}) {
|
|
3839
|
+
const indent = opts.indent ?? 2;
|
|
3840
|
+
if ("schema" in def && def.schema !== void 0) {
|
|
3841
|
+
return JSON.stringify(encodeTable(def), null, indent);
|
|
3842
|
+
}
|
|
3843
|
+
if ("verb" in def && def.verb !== void 0) {
|
|
3844
|
+
return JSON.stringify(encodeQuery(def), null, indent);
|
|
3845
|
+
}
|
|
3846
|
+
return JSON.stringify(compile(def), null, indent);
|
|
3847
|
+
}
|
|
3848
|
+
function serializeBundle(bundle, opts = {}) {
|
|
3849
|
+
return JSON.stringify(bundle, null, opts.indent ?? 2);
|
|
3850
|
+
}
|
|
3851
|
+
function emitBundle(xano, opts = {}) {
|
|
3852
|
+
return serializeBundle(xano.export({ strict: opts.strict }), opts);
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
// src/statements/decode-only.ts
|
|
3856
|
+
var DECODE_ONLY_STATEMENTS = /* @__PURE__ */ new Map([
|
|
3857
|
+
[
|
|
3858
|
+
"mvp:placeholder",
|
|
3859
|
+
'an unconfigured statement slot the engine writes in place of a statement it could not resolve, so an export stays well-formed. There is no statement class behind it: importing a workspace that contains one fails outright with "Missing statement: mvp:placeholder". Replace it with the statement it stands in for before deploying \u2014 there is no destination where it runs'
|
|
3860
|
+
]
|
|
3861
|
+
]);
|
|
3862
|
+
|
|
3863
|
+
export {
|
|
3864
|
+
TAGS,
|
|
3865
|
+
LAMBDA_BINDINGS,
|
|
3866
|
+
LAMBDA_GLOBALS,
|
|
3867
|
+
LAMBDA_MODULE_GLOBALS,
|
|
3868
|
+
LAMBDA_CODE_FILTERS,
|
|
3869
|
+
LAMBDA_STATEMENTS,
|
|
3870
|
+
coerceLambdaFields,
|
|
3871
|
+
assertLambdaStatement,
|
|
3872
|
+
extractFunctionBody,
|
|
3873
|
+
maskNonCode,
|
|
3874
|
+
assertLambdaBody,
|
|
3875
|
+
collectLambdaBodyProblems,
|
|
3876
|
+
lambdaValue,
|
|
3877
|
+
lam,
|
|
3878
|
+
emitDiagnostic,
|
|
3879
|
+
DiagnosticBag,
|
|
3880
|
+
isTaggedValue,
|
|
3881
|
+
barePatternRefusal,
|
|
3882
|
+
c,
|
|
3883
|
+
ref,
|
|
3884
|
+
inp,
|
|
3885
|
+
col,
|
|
3886
|
+
auth,
|
|
3887
|
+
resp,
|
|
3888
|
+
caught,
|
|
3889
|
+
isToolsetPath,
|
|
3890
|
+
toolset,
|
|
3891
|
+
env,
|
|
3892
|
+
setting,
|
|
3893
|
+
sys,
|
|
3894
|
+
out,
|
|
3895
|
+
filter,
|
|
3896
|
+
withFilters,
|
|
3897
|
+
toNestedFields,
|
|
3898
|
+
f,
|
|
3899
|
+
input,
|
|
3900
|
+
encodeInput,
|
|
3901
|
+
obj,
|
|
3902
|
+
FILTER_NAMES,
|
|
3903
|
+
FILTER_SPECS,
|
|
3904
|
+
FILTER_REQUIRED_ARGS,
|
|
3905
|
+
fl,
|
|
3906
|
+
expr,
|
|
3907
|
+
isSearchOp,
|
|
3908
|
+
cmp,
|
|
3909
|
+
and,
|
|
3910
|
+
or,
|
|
3911
|
+
mixed,
|
|
3912
|
+
isValue,
|
|
3913
|
+
isGroup,
|
|
3914
|
+
isMixed,
|
|
3915
|
+
isCmpNode,
|
|
3916
|
+
encodeExpression,
|
|
3917
|
+
isRuntimeConditionOp,
|
|
3918
|
+
encodeRuntimeCondition,
|
|
3919
|
+
conditional,
|
|
3920
|
+
resolveMockKeys,
|
|
3921
|
+
assertMockKeysResolved,
|
|
3922
|
+
registerKind,
|
|
3923
|
+
isRegisteredKind,
|
|
3924
|
+
getKind,
|
|
3925
|
+
encodeObject,
|
|
3926
|
+
registeredKinds,
|
|
3927
|
+
emptyMiddleware,
|
|
3928
|
+
encodeTags,
|
|
3929
|
+
SCALAR_FAMILY,
|
|
3930
|
+
SEED_FILE,
|
|
3931
|
+
seedFile,
|
|
3932
|
+
isSeedFileSource,
|
|
3933
|
+
tableColumns,
|
|
3934
|
+
SYSTEM_COLUMN_NAMES,
|
|
3935
|
+
elideSystemIndexes,
|
|
3936
|
+
encodeIndex,
|
|
3937
|
+
encodeView,
|
|
3938
|
+
encodeColumn,
|
|
3939
|
+
encodeTable,
|
|
3940
|
+
tableKind,
|
|
3941
|
+
table,
|
|
3942
|
+
leanInput,
|
|
3943
|
+
assertInputValue,
|
|
3944
|
+
encodeResponse,
|
|
3945
|
+
encodeHistory,
|
|
3946
|
+
encodeContainerHistory,
|
|
3947
|
+
buildWorkspaceHistory,
|
|
3948
|
+
assertCanonical,
|
|
3949
|
+
assertStoredName,
|
|
3950
|
+
encodeMiddlewareEntry,
|
|
3951
|
+
middlewareEntryGuid,
|
|
3952
|
+
buildMiddlewareBlock,
|
|
3953
|
+
encodeMiddlewareList,
|
|
3954
|
+
clear,
|
|
3955
|
+
stackReferencesAuth,
|
|
3956
|
+
resolveAuthRef,
|
|
3957
|
+
warnLiveDatasource,
|
|
3958
|
+
encodeTests,
|
|
3959
|
+
test,
|
|
3960
|
+
assertOneOf,
|
|
3961
|
+
encodeQuery,
|
|
3962
|
+
queryKind,
|
|
3963
|
+
pathSegment,
|
|
3964
|
+
toSearchParams,
|
|
3965
|
+
query,
|
|
3966
|
+
PAYLOAD_ARRAY_KEYS,
|
|
3967
|
+
phpJsonEncode,
|
|
3968
|
+
calcSignatureJson,
|
|
3969
|
+
buildContentEnvelope,
|
|
3970
|
+
buildBundle,
|
|
3971
|
+
DECODE_ONLY_STATEMENTS,
|
|
3972
|
+
VECTOR_FILTERS,
|
|
3973
|
+
QUERY_EXPRESSION_FILTERS,
|
|
3974
|
+
isQueryExpressionFilter,
|
|
3975
|
+
encodeFunction,
|
|
3976
|
+
functionKind,
|
|
3977
|
+
compile,
|
|
3978
|
+
emit,
|
|
3979
|
+
serializeBundle,
|
|
3980
|
+
emitBundle
|
|
3981
|
+
};
|
|
3982
|
+
//# sourceMappingURL=chunk-EMCAWMHC.js.map
|