@xano/sdk 1.0.0
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/CHANGELOG.md +788 -0
- package/LICENSE +21 -0
- package/README.md +541 -0
- package/dist/.build-fingerprint +1 -0
- package/dist/agent-file-refresh-ZEMK33AX.js +38 -0
- package/dist/answer-shape-K67TAAPY.js +33 -0
- package/dist/backend-dir-2U53CIAL.js +39 -0
- package/dist/backend-slot-7QXW2QYC.js +28 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +255 -0
- package/dist/branch-NCRNWQBQ.js +61 -0
- package/dist/branch-commands-ACIRCEQJ.js +79 -0
- package/dist/bundle.d.ts +223 -0
- package/dist/bundle.js +153 -0
- package/dist/capture-WAXPD7DR.js +41 -0
- package/dist/chunk-27GWNP2I.js +23 -0
- package/dist/chunk-2CMJZDCL.js +177 -0
- package/dist/chunk-2EZ3ZJZZ.js +302 -0
- package/dist/chunk-2GNFDZA2.js +390 -0
- package/dist/chunk-2MNLRIOX.js +11 -0
- package/dist/chunk-2TFRR6JV.js +104 -0
- package/dist/chunk-2ZUEE4EK.js +408 -0
- package/dist/chunk-36JUQZVQ.js +22 -0
- package/dist/chunk-3DRXWLJ5.js +83 -0
- package/dist/chunk-3JDU4VRL.js +137 -0
- package/dist/chunk-3KZCLN4Z.js +241 -0
- package/dist/chunk-3LDULPRE.js +168 -0
- package/dist/chunk-3M4LFYNQ.js +111 -0
- package/dist/chunk-3XI5CTUE.js +22 -0
- package/dist/chunk-3YW23UFH.js +50 -0
- package/dist/chunk-45UIT5T2.js +862 -0
- package/dist/chunk-4BW2SG2N.js +46 -0
- package/dist/chunk-4KHEMC32.js +79 -0
- package/dist/chunk-4POWHT2K.js +321 -0
- package/dist/chunk-4Q2GN7NW.js +192 -0
- package/dist/chunk-5A2PLKZB.js +70 -0
- package/dist/chunk-5FBYXK5P.js +159 -0
- package/dist/chunk-5HVXPTU4.js +323 -0
- package/dist/chunk-5QU2P5CA.js +304 -0
- package/dist/chunk-5V5XU32D.js +191 -0
- package/dist/chunk-5ZSO4T2G.js +10 -0
- package/dist/chunk-622HUCZT.js +10 -0
- package/dist/chunk-65HWOMGV.js +177 -0
- package/dist/chunk-6ELFRN3P.js +365 -0
- package/dist/chunk-6HO3NCSJ.js +1055 -0
- package/dist/chunk-6IUMKYWI.js +457 -0
- package/dist/chunk-6KEB3C3U.js +65 -0
- package/dist/chunk-6MR5TI5V.js +987 -0
- package/dist/chunk-6NNTNXN7.js +157 -0
- package/dist/chunk-6PBR37LN.js +97 -0
- package/dist/chunk-6QXLMMT3.js +155 -0
- package/dist/chunk-6ZGJCBZS.js +224 -0
- package/dist/chunk-73IVBKMA.js +250 -0
- package/dist/chunk-7G3CTU7I.js +286 -0
- package/dist/chunk-A3UDOZB6.js +222 -0
- package/dist/chunk-A6E77GDW.js +85 -0
- package/dist/chunk-AHJWCYQO.js +660 -0
- package/dist/chunk-AKNCFVKZ.js +36 -0
- package/dist/chunk-AM4FKL4J.js +237 -0
- package/dist/chunk-AN6BL47T.js +26 -0
- package/dist/chunk-AOSYVMUS.js +147 -0
- package/dist/chunk-APWXY5SL.js +182 -0
- package/dist/chunk-AWEIIR2U.js +218 -0
- package/dist/chunk-AWQ7BQTQ.js +73 -0
- package/dist/chunk-BAUZOWGX.js +393 -0
- package/dist/chunk-BHACRS6R.js +940 -0
- package/dist/chunk-BWPNT55T.js +680 -0
- package/dist/chunk-C6RIMRU4.js +187 -0
- package/dist/chunk-CGVQZXYG.js +263 -0
- package/dist/chunk-CIMQZRRE.js +162 -0
- package/dist/chunk-CJHQKJU4.js +113 -0
- package/dist/chunk-CTMO46PK.js +1897 -0
- package/dist/chunk-D53J55C5.js +106 -0
- package/dist/chunk-D7VGG3WS.js +90 -0
- package/dist/chunk-DAD3ONIS.js +245 -0
- package/dist/chunk-DGWHAVRG.js +162 -0
- package/dist/chunk-DX4QR4B2.js +483 -0
- package/dist/chunk-EHP3WPEG.js +21 -0
- package/dist/chunk-ELP2HMXC.js +432 -0
- package/dist/chunk-ERVIASGL.js +21 -0
- package/dist/chunk-ET4Y4XCB.js +865 -0
- package/dist/chunk-ETO6LGLD.js +4750 -0
- package/dist/chunk-EUUUMY7J.js +24 -0
- package/dist/chunk-FQJ2TTHP.js +218 -0
- package/dist/chunk-FQOF7E2E.js +133 -0
- package/dist/chunk-FWD7AL6N.js +21 -0
- package/dist/chunk-FXFV3KU6.js +138 -0
- package/dist/chunk-FXRQOWTE.js +171 -0
- package/dist/chunk-G554NZNI.js +210 -0
- package/dist/chunk-GSGDWJB5.js +13 -0
- package/dist/chunk-H2V3N23D.js +66 -0
- package/dist/chunk-H5C7MBCR.js +159 -0
- package/dist/chunk-HHN734BU.js +371 -0
- package/dist/chunk-HRVBLCVJ.js +243 -0
- package/dist/chunk-HVRUQXGD.js +1264 -0
- package/dist/chunk-I4F5TCJH.js +130 -0
- package/dist/chunk-IDOTCMDV.js +57 -0
- package/dist/chunk-IGOLHKWF.js +427 -0
- package/dist/chunk-JGE7CAWB.js +148 -0
- package/dist/chunk-JTUKLCXS.js +92 -0
- package/dist/chunk-JZGVVJSI.js +66 -0
- package/dist/chunk-K4WX6UUU.js +923 -0
- package/dist/chunk-K5BBGTYZ.js +217 -0
- package/dist/chunk-K6RXBGUB.js +427 -0
- package/dist/chunk-KTSDSD36.js +165 -0
- package/dist/chunk-L5JYN3NM.js +1038 -0
- package/dist/chunk-LIEOKXKD.js +354 -0
- package/dist/chunk-LLNZGEFK.js +51 -0
- package/dist/chunk-LPECSJ2J.js +55 -0
- package/dist/chunk-LQCMKAJ7.js +413 -0
- package/dist/chunk-LRN4ZJCQ.js +1145 -0
- package/dist/chunk-MJ4ZVHKN.js +17 -0
- package/dist/chunk-MXHSQOC4.js +3061 -0
- package/dist/chunk-N5GKCWE5.js +367 -0
- package/dist/chunk-NA3PXMEF.js +10 -0
- package/dist/chunk-NAQDMEHV.js +76 -0
- package/dist/chunk-NWKXM5RD.js +109 -0
- package/dist/chunk-NZOWWHU5.js +196 -0
- package/dist/chunk-OHX6MIUZ.js +184 -0
- package/dist/chunk-OOXWYAA4.js +15 -0
- package/dist/chunk-OPHB5ORL.js +1990 -0
- package/dist/chunk-OXA6PSZT.js +1725 -0
- package/dist/chunk-P2HAGF6H.js +73 -0
- package/dist/chunk-PTHVMXZI.js +3036 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-Q325JKYB.js +189 -0
- package/dist/chunk-Q7DGTQ5G.js +255 -0
- package/dist/chunk-QCNYHBL7.js +601 -0
- package/dist/chunk-QO6BVGS5.js +145 -0
- package/dist/chunk-QXTKSOFI.js +76 -0
- package/dist/chunk-ROY4YAEQ.js +66 -0
- package/dist/chunk-RTJRZFFY.js +447 -0
- package/dist/chunk-SAVJUS2X.js +128 -0
- package/dist/chunk-SFLHVCKX.js +173 -0
- package/dist/chunk-STSFNCY4.js +127 -0
- package/dist/chunk-SWBF2LTK.js +170 -0
- package/dist/chunk-T5KELPAR.js +109 -0
- package/dist/chunk-TFFGOFVZ.js +1481 -0
- package/dist/chunk-TLHIFK4M.js +256 -0
- package/dist/chunk-TLQE7ZN3.js +156 -0
- package/dist/chunk-TPHWJ7UG.js +1981 -0
- package/dist/chunk-TRJCALW3.js +291 -0
- package/dist/chunk-TTEIYD5P.js +133 -0
- package/dist/chunk-U64O2MXY.js +407 -0
- package/dist/chunk-UCQ2VUTG.js +630 -0
- package/dist/chunk-UTI5G6OJ.js +44 -0
- package/dist/chunk-UVHDMMIP.js +188 -0
- package/dist/chunk-UZ6QQWJT.js +120 -0
- package/dist/chunk-VAX3UZOY.js +686 -0
- package/dist/chunk-W3HBUKFO.js +57 -0
- package/dist/chunk-WKQLGKCL.js +2823 -0
- package/dist/chunk-WQFNASK4.js +560 -0
- package/dist/chunk-WRBKLYSJ.js +224 -0
- package/dist/chunk-WTPZRR4I.js +17 -0
- package/dist/chunk-WZDJ2LLX.js +2385 -0
- package/dist/chunk-X2NL4KUO.js +14 -0
- package/dist/chunk-X4DVXBFY.js +38 -0
- package/dist/chunk-X677DMZM.js +7050 -0
- package/dist/chunk-XBKMWY7F.js +612 -0
- package/dist/chunk-XNCP4KYO.js +78 -0
- package/dist/chunk-XVBGLISP.js +35 -0
- package/dist/chunk-XW6KISBB.js +1192 -0
- package/dist/chunk-YGZHHAAB.js +8156 -0
- package/dist/chunk-YHEFQUQB.js +186 -0
- package/dist/chunk-YLDALTWZ.js +653 -0
- package/dist/chunk-YU4HSQW7.js +229 -0
- package/dist/chunk-ZDPP6RKB.js +27 -0
- package/dist/chunk-ZFDZKNGG.js +194 -0
- package/dist/chunk-ZIY7RNUT.js +49 -0
- package/dist/chunk-ZYJP6BOO.js +30 -0
- package/dist/cli.d.ts +1111 -0
- package/dist/cli.js +165 -0
- package/dist/codegen-command-VR2GWR6C.js +173 -0
- package/dist/codegen.d.ts +115 -0
- package/dist/codegen.js +19 -0
- package/dist/commands-GJMJ4XYZ.js +76 -0
- package/dist/completion-ZQLO6PB6.js +738 -0
- package/dist/config-MBYGCAFK.js +136 -0
- package/dist/deploy-command-JVZPWHMK.js +180 -0
- package/dist/deploy-source-3W45CXMP.js +81 -0
- package/dist/dev-env-sync-HN44K4D5.js +188 -0
- package/dist/diagnostics-UEAAHWVF.js +30 -0
- package/dist/display-name-hint-ABBPVQIR.js +46 -0
- package/dist/emit-CyhBDxli.d.ts +7007 -0
- package/dist/env-example-refresh-ET5HMMDW.js +126 -0
- package/dist/env-pull-command-TNMNB3CV.js +323 -0
- package/dist/env-target-O4HRKTGZ.js +40 -0
- package/dist/env-var-command-ZIRDCAZA.js +427 -0
- package/dist/ephemeral-P7F3PMAD.js +54 -0
- package/dist/ephemeral-command-CLJJKOFT.js +592 -0
- package/dist/ephemeral-state-PYCBSOGX.js +61 -0
- package/dist/file-lock-PT74GWYW.js +10 -0
- package/dist/generate-command-AYEOLA26.js +510 -0
- package/dist/gitignore-3SJ3WNRG.js +51 -0
- package/dist/guards-4EBNCZDC.js +162 -0
- package/dist/hosted-file-PSHJB3HX.js +28 -0
- package/dist/impersonate-command-5JAS3WSV.js +152 -0
- package/dist/import-EQSFU6JC.js +19 -0
- package/dist/index.d.ts +5470 -0
- package/dist/index.js +371 -0
- package/dist/init-command-X2FR4UAK.js +125 -0
- package/dist/init-modules-RY7UHAOL.js +50 -0
- package/dist/init-web-M4YZSZK6.js +42 -0
- package/dist/internal.d.ts +179 -0
- package/dist/internal.js +2634 -0
- package/dist/io-RC56QSN5.js +26 -0
- package/dist/keep-data-merge-7MPLVAVI.js +1119 -0
- package/dist/lambda-config-reconcile-OUZ4OYAB.js +116 -0
- package/dist/landed-753YLJ5H.js +40 -0
- package/dist/landing-record-SU52SKXU.js +61 -0
- package/dist/live-diff-YONW2VI3.js +65 -0
- package/dist/local-engine-choice-CH5X3W7E.js +79 -0
- package/dist/local-engine-command-Z3OL7VOD.js +766 -0
- package/dist/local-engine-config-5U3SC434.js +39 -0
- package/dist/local-engine-deploy-PCQ4SNPB.js +39 -0
- package/dist/local-engine-process-EHTE5LLU.js +63 -0
- package/dist/local-engine-state-A4FTRFAF.js +32 -0
- package/dist/local-engine-update-check-6L645VBL.js +162 -0
- package/dist/lock-MRRHW5GS.js +87 -0
- package/dist/lock-commands-2QG25HL7.js +963 -0
- package/dist/login-command-QPYRWOXZ.js +538 -0
- package/dist/logout-command-WOIQLDP6.js +56 -0
- package/dist/loop-ALIZLOSX.js +191 -0
- package/dist/marketplace-command-K5YL42NO.js +977 -0
- package/dist/marketplace-resolve-LY32JES5.js +40 -0
- package/dist/meta-client-YXGPW53C.js +244 -0
- package/dist/module-manifest-JFGHU2TC.js +38 -0
- package/dist/near-key.d.ts +13 -0
- package/dist/near-key.js +8 -0
- package/dist/node.d.ts +95 -0
- package/dist/node.js +969 -0
- package/dist/operation-outcome-TZSESNC7.js +51 -0
- package/dist/output-target-AL3I44GG.js +47 -0
- package/dist/package-manager-PRAHUSCN.js +34 -0
- package/dist/plan-disclosure-6G4MLZKD.js +114 -0
- package/dist/plugin-contributions-WRNY5ABG.js +28 -0
- package/dist/plugin-prompt-4VA3B4FF.js +20 -0
- package/dist/plugin-questions-JTSYA74Q.js +24 -0
- package/dist/plugin.d.ts +396 -0
- package/dist/plugin.js +1 -0
- package/dist/preflight-command-2T5Z36MI.js +321 -0
- package/dist/profile-command-AH3PIM4X.js +800 -0
- package/dist/profile-pointer-GRWJU3YD.js +31 -0
- package/dist/profile-select-74TRDEPD.js +23 -0
- package/dist/project-dependency-KSH63ODT.js +35 -0
- package/dist/project-sdk-REPPOIBD.js +24 -0
- package/dist/promote-command-PCWT5N6S.js +140 -0
- package/dist/prompt-II3F3LND.js +27 -0
- package/dist/publish-command-TTDUUJI5.js +414 -0
- package/dist/pull-command-VBO7C4S3.js +161 -0
- package/dist/pull-superseded-GJWLG7XO.js +20 -0
- package/dist/release-command-N4ZPKMHR.js +146 -0
- package/dist/release-ns-command-3KFDMZ6G.js +1745 -0
- package/dist/response-HlXahv4K.d.ts +925 -0
- package/dist/retry-command-AS5EVREX.js +34 -0
- package/dist/routes-manifest-SNWUVBT6.js +43 -0
- package/dist/runtime-XETHVF33.js +14 -0
- package/dist/scaffold.d.ts +1911 -0
- package/dist/scaffold.js +107 -0
- package/dist/secrets-file-LVZD6IIM.js +56 -0
- package/dist/secrets-fill-command-TTCPSVXN.js +218 -0
- package/dist/seed-VU7C3QYS.js +46 -0
- package/dist/sent-writes-N7X4OVXT.js +22 -0
- package/dist/source-resolve-S2XA3XZF.js +83 -0
- package/dist/static-host-B2Y6AG3W.js +352 -0
- package/dist/status-command-RUDXTF7I.js +554 -0
- package/dist/store-6VNCD4D7.js +20 -0
- package/dist/store-ARM6VMO5.js +98 -0
- package/dist/store-DuYfzKlW.d.ts +3144 -0
- package/dist/suggest-6FOOYKHG.js +12 -0
- package/dist/sveltekit.d.ts +154 -0
- package/dist/sveltekit.js +109 -0
- package/dist/sync-contributions-UCEROO26.js +415 -0
- package/dist/table-ENAVMITR.js +42 -0
- package/dist/tables-command-GUPXXSB5.js +156 -0
- package/dist/tenant-IIHPOHGP.js +42 -0
- package/dist/tenant-command-D5HGI2X4.js +965 -0
- package/dist/test-command-TIKZMDPZ.js +476 -0
- package/dist/tests-BGGIOK3L.js +26 -0
- package/dist/token-NICJUTMT.js +72 -0
- package/dist/toolchain-hooks-GHKLUJOH.js +96 -0
- package/dist/toolchain-modules-5EAZUONB.js +24 -0
- package/dist/tracked-backend-EIBUMDZA.js +80 -0
- package/dist/update-check-2W2NLDTC.js +136 -0
- package/dist/upgrade-command-QHNFWM7O.js +531 -0
- package/dist/verify-rollout-DRCK5TDD.js +61 -0
- package/dist/whoami-command-STGINTXH.js +51 -0
- package/dist/workspace-2CLHU2LK.js +93 -0
- package/dist/workspace-binding-56PIIPCP.js +54 -0
- package/dist/workspace-command-JEXQETJD.js +535 -0
- package/dist/workspace-env-F2RFMSC5.js +70 -0
- package/dist/workspace-export-OJSH2HIW.js +21 -0
- package/dist/writable-VA7OISSK.js +25 -0
- package/dist/xano-ICFUSPFR.js +42 -0
- package/dist/xanosdk-import-GN42TO2B.js +56 -0
- package/dist/xanosdk-project-S6DXJONQ.js +41 -0
- package/dist/xdo-CxDr2Q1C.d.ts +1371 -0
- package/guides/README.md +33 -0
- package/guides/authoring.md +921 -0
- package/guides/cli.md +401 -0
- package/guides/codegen.md +177 -0
- package/guides/codes.md +404 -0
- package/guides/coverage.md +74 -0
- package/guides/deploying.md +1525 -0
- package/guides/environment.md +400 -0
- package/guides/marketplace.md +415 -0
- package/guides/module-contract.md +558 -0
- package/guides/object-kinds.md +433 -0
- package/guides/project-structure.md +43 -0
- package/guides/scaffold.md +351 -0
- package/guides/typed-frontend.md +242 -0
- package/llms/client.md +63 -0
- package/llms/errors.md +56 -0
- package/llms/fields.md +176 -0
- package/llms/filters.md +183 -0
- package/llms/kinds-agent-mcp.md +30 -0
- package/llms/kinds-core.md +100 -0
- package/llms/kinds-knowledge.md +20 -0
- package/llms/kinds-realtime.md +76 -0
- package/llms/lambda.md +94 -0
- package/llms/legacy.md +44 -0
- package/llms/lock.md +40 -0
- package/llms/object-kinds.md +34 -0
- package/llms/statements-calls.md +62 -0
- package/llms/statements-catalog.md +320 -0
- package/llms/statements-data.md +66 -0
- package/llms/statements-runtime.md +68 -0
- package/llms/tests.md +59 -0
- package/llms/toolchain.md +16 -0
- package/llms/triggers.md +36 -0
- package/llms/values.md +29 -0
- package/llms-full.txt +2085 -0
- package/llms.txt +493 -0
- package/manifest.json +11224 -0
- package/package.json +162 -0
- package/types/lambda-globals.d.ts +129 -0
|
@@ -0,0 +1,1725 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SourceReader,
|
|
3
|
+
SourceShapeError,
|
|
4
|
+
lineOf,
|
|
5
|
+
maskNonCode,
|
|
6
|
+
tokenize
|
|
7
|
+
} from "./chunk-VAX3UZOY.js";
|
|
8
|
+
import {
|
|
9
|
+
describeEntry,
|
|
10
|
+
protoKeyed
|
|
11
|
+
} from "./chunk-CIMQZRRE.js";
|
|
12
|
+
import {
|
|
13
|
+
article,
|
|
14
|
+
withArticle
|
|
15
|
+
} from "./chunk-36JUQZVQ.js";
|
|
16
|
+
|
|
17
|
+
// src/values/lambda-ident.ts
|
|
18
|
+
var IDENT = String.raw`[\p{ID_Start}$_][\p{ID_Continue}$\u200C\u200D]*`;
|
|
19
|
+
var ID_CHAR = String.raw`[\p{ID_Continue}$\u200C\u200D]`;
|
|
20
|
+
var ESCAPE = /\\u(?:([0-9A-Fa-f]{4})|\{([0-9A-Fa-f]{1,6})\})/g;
|
|
21
|
+
var IDENT_NAME = new RegExp(`^${IDENT}$`, "u");
|
|
22
|
+
function decodeIdentifier(name) {
|
|
23
|
+
return name.replace(ESCAPE, (whole, four, braced) => {
|
|
24
|
+
const code = parseInt(four ?? braced ?? "", 16);
|
|
25
|
+
return code <= 1114111 ? String.fromCodePoint(code) : whole;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function isQuotedShorthand(prop) {
|
|
29
|
+
const m = /^\s*(["'])([^"'\\]*(?:\\u[^"'\\]*)*)\1\s*:\s*([^\s:=]+)\s*$/su.exec(prop);
|
|
30
|
+
if (m === null || !isIdentifierName(m[3])) return false;
|
|
31
|
+
return decodeIdentifier(m[2]) === decodeIdentifier(m[3]);
|
|
32
|
+
}
|
|
33
|
+
function isIdentifierName(name) {
|
|
34
|
+
return IDENT_NAME.test(decodeIdentifier(name));
|
|
35
|
+
}
|
|
36
|
+
function decodeIdentifierEscapes(src) {
|
|
37
|
+
if (!src.includes("\\u")) return src;
|
|
38
|
+
const mask = maskNonCode(src);
|
|
39
|
+
let out2 = "";
|
|
40
|
+
let last = 0;
|
|
41
|
+
for (const m of mask.matchAll(ESCAPE)) {
|
|
42
|
+
out2 += src.slice(last, m.index) + decodeIdentifier(m[0]);
|
|
43
|
+
last = m.index + m[0].length;
|
|
44
|
+
}
|
|
45
|
+
return out2 + src.slice(last);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/types/xdo.ts
|
|
49
|
+
var TAGS = [
|
|
50
|
+
"const",
|
|
51
|
+
"const:int",
|
|
52
|
+
"const:decimal",
|
|
53
|
+
"const:bool",
|
|
54
|
+
"const:array",
|
|
55
|
+
"const:obj",
|
|
56
|
+
"const:null",
|
|
57
|
+
// The engine's native epoch-milliseconds constant (`value:"now"`) — what
|
|
58
|
+
// `c.now()` emits. Distinct from `const` + a `to_epoch_ms` filter: both
|
|
59
|
+
// evaluate to the same number (live-verified), but this one needs no filter.
|
|
60
|
+
"const:epochms",
|
|
61
|
+
"const:expr",
|
|
62
|
+
"const:expr2",
|
|
63
|
+
// A text written as its quoted literal — what `c.text` stores for a value
|
|
64
|
+
// ending in two or more backslashes, the one text the plain `const` form
|
|
65
|
+
// does not read back exactly.
|
|
66
|
+
"const:encoded",
|
|
67
|
+
"var",
|
|
68
|
+
"input",
|
|
69
|
+
"auth",
|
|
70
|
+
"env",
|
|
71
|
+
"setting",
|
|
72
|
+
"col",
|
|
73
|
+
"output",
|
|
74
|
+
"response",
|
|
75
|
+
"trycatch",
|
|
76
|
+
"toolset"
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// src/values/expression-arg.ts
|
|
80
|
+
var EXPRESSION_ARG_FILTERS = {
|
|
81
|
+
transform: 0
|
|
82
|
+
};
|
|
83
|
+
var UNBOUND_HERE = ["$this", "$parent", "$index", "$result"];
|
|
84
|
+
function withoutStringLiterals(source) {
|
|
85
|
+
return source.replace(/(['"])(?:\\.|(?!\1)[^\\])*\1?/g, (m) => {
|
|
86
|
+
const closed = m.length > 1 && m.at(-1) === m[0];
|
|
87
|
+
return m[0] + " ".repeat(m.length - (closed ? 2 : 1)) + (closed ? m[0] : "");
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function assertExpressionFilterArgs(name, args) {
|
|
91
|
+
const slot = Object.hasOwn(EXPRESSION_ARG_FILTERS, name) ? EXPRESSION_ARG_FILTERS[name] : void 0;
|
|
92
|
+
if (slot === void 0) return;
|
|
93
|
+
const arg = args[slot];
|
|
94
|
+
if (!isInspectableSource(arg) || arg.value.trim() === "") return;
|
|
95
|
+
assertExpressionArg(arg.value, `fl.${name}`);
|
|
96
|
+
}
|
|
97
|
+
function isInspectableSource(arg) {
|
|
98
|
+
return typeof arg === "object" && arg !== null && arg.tag === "const" && typeof arg.value === "string" && (arg.filters?.length ?? 0) === 0;
|
|
99
|
+
}
|
|
100
|
+
function assertExpressionArg(source, fn2) {
|
|
101
|
+
const scannable = withoutStringLiterals(source);
|
|
102
|
+
const unbound = UNBOUND_HERE.find((b) => new RegExp(`\\${b}\\b`).test(scannable));
|
|
103
|
+
if (unbound !== void 0) {
|
|
104
|
+
const bare = unbound.slice(1);
|
|
105
|
+
throw new Error(
|
|
106
|
+
`${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}\`.)`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const js = /(?<![$\w.])return\b/.test(scannable) && "a `return` statement" || /=>/.test(scannable) && "an arrow function" || /;/.test(scannable) && "a statement separator (`;`)";
|
|
110
|
+
if (js) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`${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\`.`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/values/enum-arg.ts
|
|
118
|
+
var ENUM_ARG_FILTERS = {
|
|
119
|
+
fsort: {
|
|
120
|
+
slot: 1,
|
|
121
|
+
arg: "type",
|
|
122
|
+
members: ["text", "itext", "natural", "inatural", "number"],
|
|
123
|
+
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.'
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
function assertEnumFilterArgs(name, args) {
|
|
127
|
+
const spec = Object.hasOwn(ENUM_ARG_FILTERS, name) ? ENUM_ARG_FILTERS[name] : void 0;
|
|
128
|
+
if (spec === void 0) return;
|
|
129
|
+
const arg = args[spec.slot];
|
|
130
|
+
if (!isInspectableConst(arg) || arg.value === "") return;
|
|
131
|
+
if (spec.members.includes(arg.value)) return;
|
|
132
|
+
throw new Error(
|
|
133
|
+
`fl.${name}: \`${spec.arg}\` is ${JSON.stringify(arg.value)}, which is not one of ${spec.members.map((m) => JSON.stringify(m)).join(", ")}. ${spec.why}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
function isInspectableConst(arg) {
|
|
137
|
+
return typeof arg === "object" && arg !== null && arg.tag === "const" && typeof arg.value === "string" && (arg.filters?.length ?? 0) === 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/values/value.ts
|
|
141
|
+
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.";
|
|
142
|
+
function isTaggedValue(x) {
|
|
143
|
+
return (
|
|
144
|
+
// A trigger field accessor (`t.new`) is a *callable* Value — a function
|
|
145
|
+
// carrying the `{value,tag,filters}` props — so `typeof` is "function", not
|
|
146
|
+
// "object". Accept both, or a bare `t.new` slips past this check, falls
|
|
147
|
+
// through coerceObj's record path, and trips the c.obj nested-value guard.
|
|
148
|
+
(typeof x === "object" || typeof x === "function") && x !== null && "value" in x && "filters" in x && Array.isArray(x.filters) && TAGS.includes(x.tag) && // `{ a, __proto__: ref("v") }` inherits all three — a record, not `ref("v")`.
|
|
149
|
+
!protoKeyed(x)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
function assertPlainJson(root, helper) {
|
|
153
|
+
const stack = [[root, ""]];
|
|
154
|
+
const onPath = /* @__PURE__ */ new Map();
|
|
155
|
+
while (stack.length > 0) {
|
|
156
|
+
const frame = stack.pop();
|
|
157
|
+
if (frame.length === 3) {
|
|
158
|
+
onPath.delete(frame[2]);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const [x, path] = frame;
|
|
162
|
+
if (typeof x === "object" && x !== null) {
|
|
163
|
+
const back = onPath.get(x);
|
|
164
|
+
if (back !== void 0) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`${helper}: value at "${path}" refers back to itself (to ${back === "" ? "the argument" : `the value at "${back}"`}) \u2014 a cycle, which JSON cannot store. Pass a copy without the back-reference.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (isTaggedValue(x)) {
|
|
171
|
+
throw new Error(path === "" ? REJECT_TAGGED_VALUE : `${helper}: value at "${path}" is a tagged value. ${REJECT_TAGGED_VALUE}`);
|
|
172
|
+
}
|
|
173
|
+
const bad = notJson(x);
|
|
174
|
+
if (bad !== void 0) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`${helper}: ${path === "" ? "the argument" : `value at "${path}"`} is ${bad} \u2014 ${helper}() stores plain JSON only (strings, finite numbers, booleans, null, lists and plain records). Convert it first: a Date to date.toISOString(), a Map to Object.fromEntries(map), a bigint to Number() or String().`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (typeof x === "object" && x !== null) {
|
|
180
|
+
onPath.set(x, path);
|
|
181
|
+
stack.push([null, path, x]);
|
|
182
|
+
}
|
|
183
|
+
if (Array.isArray(x)) {
|
|
184
|
+
for (let i = x.length - 1; i >= 0; i--) stack.push([x[i], `${path}[${i}]`]);
|
|
185
|
+
} else if (typeof x === "object" && x !== null) {
|
|
186
|
+
const entries = Object.entries(x);
|
|
187
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
188
|
+
const [k, v] = entries[i];
|
|
189
|
+
stack.push([v, joinPath(path, k)]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function objTaggedList(a) {
|
|
195
|
+
assertPlainJson(a, "c.obj");
|
|
196
|
+
return val(JSON.stringify(a), "const:obj");
|
|
197
|
+
}
|
|
198
|
+
function joinPath(path, key) {
|
|
199
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) return path === "" ? key : `${path}.${key}`;
|
|
200
|
+
return `${path}[${JSON.stringify(key)}]`;
|
|
201
|
+
}
|
|
202
|
+
function notJson(x) {
|
|
203
|
+
switch (typeof x) {
|
|
204
|
+
case "function":
|
|
205
|
+
return "a function";
|
|
206
|
+
case "symbol":
|
|
207
|
+
return "a symbol";
|
|
208
|
+
case "bigint":
|
|
209
|
+
return `a bigint (${String(x)}n)`;
|
|
210
|
+
case "number":
|
|
211
|
+
return Number.isFinite(x) ? void 0 : `${String(x)}, which JSON cannot store`;
|
|
212
|
+
case "object": {
|
|
213
|
+
if (x === null || Array.isArray(x)) return void 0;
|
|
214
|
+
const proto = Object.getPrototypeOf(x);
|
|
215
|
+
if (proto === null || proto === Object.prototype) return void 0;
|
|
216
|
+
return protoKeyed(x) ? describeEntry(x) : describeInstance(x);
|
|
217
|
+
}
|
|
218
|
+
default:
|
|
219
|
+
return void 0;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function describeInstance(x) {
|
|
223
|
+
const name = x.constructor?.name;
|
|
224
|
+
if (typeof name !== "string" || name === "") return "a class instance";
|
|
225
|
+
const builtin = ["Date", "Map", "Set", "WeakMap", "WeakSet", "RegExp", "Promise", "Error"];
|
|
226
|
+
if (builtin.includes(name)) return `a ${name}`;
|
|
227
|
+
return `${withArticle(name)} instance`;
|
|
228
|
+
}
|
|
229
|
+
var DECIMAL_LITERAL = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
230
|
+
function val(value, tag, filters = []) {
|
|
231
|
+
return { value, tag, filters };
|
|
232
|
+
}
|
|
233
|
+
function textConst(s) {
|
|
234
|
+
return /\\\\$/.test(s) ? val(`(${JSON.stringify(s).slice(0, -3)}\\u005c")`, "const:encoded") : val(s, "const");
|
|
235
|
+
}
|
|
236
|
+
function toPlainValue(v) {
|
|
237
|
+
if (typeof v !== "function") return v;
|
|
238
|
+
const { value, tag, filters } = v;
|
|
239
|
+
if (typeof tag !== "string" || !Array.isArray(filters)) return v;
|
|
240
|
+
return { value, tag, filters };
|
|
241
|
+
}
|
|
242
|
+
function isPlainRecord(x) {
|
|
243
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
244
|
+
}
|
|
245
|
+
var BARE_SET_PATH = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
246
|
+
function setPath(key) {
|
|
247
|
+
if (BARE_SET_PATH.test(key)) return key;
|
|
248
|
+
return `["${key.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`;
|
|
249
|
+
}
|
|
250
|
+
function objMember(m) {
|
|
251
|
+
if (isPlainRecord(m)) return val("{}", "const:obj", objSetFilters(m));
|
|
252
|
+
if (Array.isArray(m)) return val(JSON.stringify(m), "const:array");
|
|
253
|
+
if (m === null) return val("null", "const:null");
|
|
254
|
+
if (typeof m === "boolean") return val(m ? "true" : "false", "const:bool");
|
|
255
|
+
if (typeof m === "number") {
|
|
256
|
+
if (!Number.isFinite(m)) return val("null", "const:null");
|
|
257
|
+
return Number.isInteger(m) && Number.isSafeInteger(m) ? val(String(m), "const:int") : val(String(m), "const:decimal");
|
|
258
|
+
}
|
|
259
|
+
return textConst(String(m));
|
|
260
|
+
}
|
|
261
|
+
function objSetFilters(o) {
|
|
262
|
+
return Object.entries(o).filter(([, m]) => m !== void 0).map(([key, m]) => filter("set", val(setPath(key), "const"), objMember(m)));
|
|
263
|
+
}
|
|
264
|
+
var PCRE_JS_FLAGS = "imsxu";
|
|
265
|
+
function escapeRegexSlashes(body) {
|
|
266
|
+
let out2 = "";
|
|
267
|
+
for (let i = 0; i < body.length; i++) {
|
|
268
|
+
const ch = body[i];
|
|
269
|
+
if (ch === "\\") {
|
|
270
|
+
out2 += ch + (body[i + 1] ?? "");
|
|
271
|
+
i++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
out2 += ch === "/" ? "\\/" : ch;
|
|
275
|
+
}
|
|
276
|
+
return out2;
|
|
277
|
+
}
|
|
278
|
+
function isValidPcreLiteral(s) {
|
|
279
|
+
if (s.length < 2) return false;
|
|
280
|
+
const d = s[0];
|
|
281
|
+
if (/[a-zA-Z0-9\\\s]/.test(d)) return false;
|
|
282
|
+
const close = { "(": ")", "[": "]", "{": "}", "<": ">" }[d] ?? d;
|
|
283
|
+
let i = 1;
|
|
284
|
+
for (; i < s.length; i++) {
|
|
285
|
+
if (s[i] === "\\") {
|
|
286
|
+
i++;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (s[i] === close) break;
|
|
290
|
+
}
|
|
291
|
+
if (i >= s.length) return false;
|
|
292
|
+
return /^[a-zA-Z]*$/.test(s.slice(i + 1));
|
|
293
|
+
}
|
|
294
|
+
function barePatternRefusal(value) {
|
|
295
|
+
if (value.filters.length > 0 || value.tag !== "const") return null;
|
|
296
|
+
if (isValidPcreLiteral(value.value)) return null;
|
|
297
|
+
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`;
|
|
298
|
+
}
|
|
299
|
+
var REGEX_SUBJECT_SLOT = {
|
|
300
|
+
regex_test: 0,
|
|
301
|
+
regex_match: 0,
|
|
302
|
+
regex_match_all: 0,
|
|
303
|
+
regex_matches: 0,
|
|
304
|
+
regex_replace: 1,
|
|
305
|
+
regex_get_all_matches: 0,
|
|
306
|
+
regex_get_first_match: 0
|
|
307
|
+
};
|
|
308
|
+
var REGEX_PATTERN_FILTERS = new Set(Object.keys(REGEX_SUBJECT_SLOT));
|
|
309
|
+
var REGEX_METACHARACTERS = /[\\^$.|?*+()[\]{}]/;
|
|
310
|
+
function readablePattern(value) {
|
|
311
|
+
if (!value || value.tag !== "const") return null;
|
|
312
|
+
if (Array.isArray(value.filters) && value.filters.length > 0) return null;
|
|
313
|
+
return isValidPcreLiteral(value.value) ? value.value : null;
|
|
314
|
+
}
|
|
315
|
+
function reversedOperandRefusal(pattern, subject) {
|
|
316
|
+
const misplaced = readablePattern(subject);
|
|
317
|
+
if (misplaced === null || !REGEX_METACHARACTERS.test(misplaced)) return null;
|
|
318
|
+
if (readablePattern(pattern) !== null) return null;
|
|
319
|
+
return `the OPERANDS are reversed. ${JSON.stringify(misplaced)} is a delimiter-wrapped regex pattern sitting in the SUBJECT slot, which means the pattern and the text it tests were written the other way round. The regex family is pattern-piped \u2014 the piped value is the PATTERN and the argument is the text matched against it, the reverse of every other filter \u2014 so this runs the subject as a pattern and answers false for every input, with HTTP 200 and no error. Swap them: pipe the c.regex(...) pattern and pass the subject as the argument`;
|
|
320
|
+
}
|
|
321
|
+
function reversedRegexOperands(v) {
|
|
322
|
+
const chain = Array.isArray(v.filters) ? v.filters : [];
|
|
323
|
+
for (const [i, applied] of chain.entries()) {
|
|
324
|
+
const slot = Object.hasOwn(REGEX_SUBJECT_SLOT, applied.name) ? REGEX_SUBJECT_SLOT[applied.name] : void 0;
|
|
325
|
+
if (slot === void 0) continue;
|
|
326
|
+
const pattern = { value: v.value, tag: v.tag, filters: chain.slice(0, i) };
|
|
327
|
+
const message = reversedOperandRefusal(pattern, applied.arg?.[slot]);
|
|
328
|
+
if (message !== null) {
|
|
329
|
+
return { filter: applied.name, slot, subject: applied.arg?.[slot]?.value ?? "", message };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
function intLiteral(n) {
|
|
335
|
+
if (typeof n === "bigint") return String(n);
|
|
336
|
+
if (typeof n !== "number" && typeof n !== "string") {
|
|
337
|
+
throw new Error(`c.int() takes an integer, got ${describeEntry(n)}.`);
|
|
338
|
+
}
|
|
339
|
+
if (typeof n === "string") {
|
|
340
|
+
if (!/^[+-]?\d+$/.test(n)) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`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").`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return n;
|
|
346
|
+
}
|
|
347
|
+
if (!Number.isInteger(n)) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`c.int() takes an integer, got ${n}. Use c.decimal(${n}) for a fractional number.`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
if (!Number.isSafeInteger(n)) throw new Error(`c.int() was given ${lossyIntegerProblem(n)}`);
|
|
353
|
+
return String(n);
|
|
354
|
+
}
|
|
355
|
+
var INT64_MAX = 9223372036854775807n;
|
|
356
|
+
function lossyIntegerProblem(n) {
|
|
357
|
+
const rounded = BigInt(n);
|
|
358
|
+
const range = rounded > INT64_MAX || rounded < -INT64_MAX - 1n ? ", outside the 64-bit integer range" : "";
|
|
359
|
+
return `${n}, past Number.MAX_SAFE_INTEGER: a JavaScript number cannot hold it exactly, so the literal was rounded before reaching here (it now reads ${rounded}${range}). Write the exact digits you meant as a string or a bigint literal \u2014 c.int("<digits>") or c.int(<digits>n).`;
|
|
360
|
+
}
|
|
361
|
+
var c = {
|
|
362
|
+
/**
|
|
363
|
+
* Plain string constant → `tag:"const"`.
|
|
364
|
+
*
|
|
365
|
+
* `null` is accepted alongside a string because the engine stores both: 47
|
|
366
|
+
* values in the sweep are a bare `const` holding `null` rather than `""`,
|
|
367
|
+
* mostly ignored statement-input entries the engine never reads. They are
|
|
368
|
+
* distinct bytes — `normalize` keeps them apart, unlike the `const:obj` blanks
|
|
369
|
+
* it does canonicalize — so a pull has to be able to spell the null form, and
|
|
370
|
+
* `c.text(null)` did not type-check.
|
|
371
|
+
*
|
|
372
|
+
* Write `c.text("")` for an empty string. The null form is here so a pulled
|
|
373
|
+
* workspace round-trips, not to be authored.
|
|
374
|
+
*/
|
|
375
|
+
text(s) {
|
|
376
|
+
if (typeof s !== "string" && s !== null) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`c.text() takes a string, got ${describeEntry(s)}. ` + (typeof s === "number" ? `A number is c.int()/c.decimal(); as text it is c.text("${String(s)}").` : "A record is c.obj({ \u2026 }), a list c.array([ \u2026 ]).")
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
return textConst(s);
|
|
382
|
+
},
|
|
383
|
+
/**
|
|
384
|
+
* Integer constant → `tag:"const:int"`, value stringified (e.g. `"123"`).
|
|
385
|
+
*
|
|
386
|
+
* Pass a **string** or `bigint` for an integer outside JavaScript's safe
|
|
387
|
+
* range: `c.int("9223372036854775807")` is exact, where the number literal
|
|
388
|
+
* for it silently rounds. The engine's integers are signed 64-bit and it
|
|
389
|
+
* clamps a value past that range with no error, so the export warns on one
|
|
390
|
+
* (`value.int-out-of-range`); the string form still stores the digits as
|
|
391
|
+
* written, which a pulled workspace relies on.
|
|
392
|
+
*
|
|
393
|
+
* A `number` that is not an integer, or is past `Number.MAX_SAFE_INTEGER`,
|
|
394
|
+
* **throws**: by the time such a literal reaches here its precision is already
|
|
395
|
+
* gone, so encoding it would write a value the caller did not type. The throw
|
|
396
|
+
* names the string form.
|
|
397
|
+
*/
|
|
398
|
+
int(n) {
|
|
399
|
+
return val(intLiteral(n), "const:int");
|
|
400
|
+
},
|
|
401
|
+
/**
|
|
402
|
+
* Decimal constant → `tag:"const:decimal"`.
|
|
403
|
+
*
|
|
404
|
+
* Pass a **string** to preserve a stored spelling a number literal cannot
|
|
405
|
+
* reproduce — `c.decimal("10.00")` keeps its trailing zeros, where
|
|
406
|
+
* `c.decimal(10)` writes `"10"`. The engine stores decimals as strings either
|
|
407
|
+
* way, so this is exactness rather than a workaround; prefer the number form
|
|
408
|
+
* whenever it reproduces the value you want.
|
|
409
|
+
*/
|
|
410
|
+
decimal(n) {
|
|
411
|
+
if (typeof n !== "number" && typeof n !== "string" || typeof n === "number" && !Number.isFinite(n)) {
|
|
412
|
+
throw new Error(`c.decimal() takes a finite number (or its string spelling), got ${describeEntry(n)}.`);
|
|
413
|
+
}
|
|
414
|
+
if (typeof n === "string" && !DECIMAL_LITERAL.test(n)) {
|
|
415
|
+
throw new Error(
|
|
416
|
+
`c.decimal() takes a number, or a decimal literal as a string (digits, optional sign, point and exponent), got ${JSON.stringify(n)}. For the editor's empty value box use c.blank("const:decimal").`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
return val(String(n), "const:decimal");
|
|
420
|
+
},
|
|
421
|
+
/**
|
|
422
|
+
* The editor's **unconfigured value box** — a value cell added and never
|
|
423
|
+
* filled in, stored as `{value: "", tag}`.
|
|
424
|
+
*
|
|
425
|
+
* This is not a zero, an empty string, or an empty collection. The engine
|
|
426
|
+
* reads `""` and `"0"` differently, so `c.blank("const:int")` and `c.int(0)`
|
|
427
|
+
* are different stored values and the SDK will not canonicalize one into the
|
|
428
|
+
* other. It exists because 13 real values in the survey corpus are in this
|
|
429
|
+
* state and had no authoring form, so a pull emitted them as annotated
|
|
430
|
+
* literals with a warning attached — describing a workspace that was fine.
|
|
431
|
+
*
|
|
432
|
+
* Constant tags only. A blank `var` or `input` is an unbound REFERENCE, which
|
|
433
|
+
* is a different defect with a different fix, and is deliberately not
|
|
434
|
+
* spellable here. `const` and `const:obj` are excluded too: they already have
|
|
435
|
+
* exact blank forms in `c.text("")` and `c.obj(null)`, and a second spelling
|
|
436
|
+
* for the same bytes is how two constructors start disagreeing.
|
|
437
|
+
*/
|
|
438
|
+
blank(tag) {
|
|
439
|
+
if (typeof tag !== "string" || !tag.startsWith("const:") || tag === "const:obj" || !TAGS.includes(tag)) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`c.blank() takes a constant tag with a blank form \u2014 "const:int", "const:decimal", "const:bool", \u2026 \u2014 got ${describeEntry(tag)}. A blank text is c.text(""), a blank object c.obj(null).`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
return val("", tag);
|
|
445
|
+
},
|
|
446
|
+
/** Boolean constant → `"true"`/`"false"` with `tag:"const:bool"`. */
|
|
447
|
+
bool(b) {
|
|
448
|
+
if (typeof b === "string" && (b === "true" || b === "false")) return val(b, "const:bool");
|
|
449
|
+
if (typeof b !== "boolean") {
|
|
450
|
+
throw new Error(`c.bool() takes true or false, got ${describeEntry(b)}.`);
|
|
451
|
+
}
|
|
452
|
+
return val(b ? "true" : "false", "const:bool");
|
|
453
|
+
},
|
|
454
|
+
/**
|
|
455
|
+
* Null constant → value `"null"` (per engine fixture).
|
|
456
|
+
*
|
|
457
|
+
* Bare `c.null()` is the plain `const:null`. Pass a tag for the **object-typed
|
|
458
|
+
* null** the engine writes into a `db.*` statement's `@meta` slot —
|
|
459
|
+
* `c.null("const:obj")` → `{tag:"const:obj", value:"null"}`, 16 of them across
|
|
460
|
+
* eight ordinary queries in the survey corpus, which had no spelling and came
|
|
461
|
+
* back as `rawValue`.
|
|
462
|
+
*
|
|
463
|
+
* ⚠ Not the same bytes as `c.obj(null)`, which is the BLANK object
|
|
464
|
+
* (`value: ""`). Both evaluate to null — the engine JSON-decodes the stored
|
|
465
|
+
* string and `""` and `"null"` both decode to it — but they are two stored
|
|
466
|
+
* spellings, and the SDK writes back the one it was given rather than
|
|
467
|
+
* re-pointing a pulled workspace at the other.
|
|
468
|
+
*/
|
|
469
|
+
null(tag = "const:null") {
|
|
470
|
+
if (tag !== "const:null" && tag !== "const:obj") {
|
|
471
|
+
throw new Error(`c.null() takes no argument, or the tag "const:obj" for an object-typed null \u2014 got ${describeEntry(tag)}.`);
|
|
472
|
+
}
|
|
473
|
+
return val("null", tag);
|
|
474
|
+
},
|
|
475
|
+
/**
|
|
476
|
+
* Build a **regex pattern value** for the pattern-piped regex filters
|
|
477
|
+
* (`fl.regex_test`/`regex_match`/`regex_replace`/…). Xano runs PHP `preg_*`, so
|
|
478
|
+
* the pattern MUST be delimiter-wrapped — a bare `c.text("^…$")` is an invalid
|
|
479
|
+
* PCRE and the filter then matches *nothing* for every input, so a precondition
|
|
480
|
+
* built on it silently rejects all values. This wraps the raw
|
|
481
|
+
* pattern in `/…/`, escapes any interior `/`, and appends `flags`, so the result
|
|
482
|
+
* is always valid; `withFilters` rejects a bare `c.text` pattern and points here.
|
|
483
|
+
*
|
|
484
|
+
* Pass a JS `RegExp` (source + flags used directly, minus JS-only `g`/`y`/`d`):
|
|
485
|
+
* `c.regex(/^[^@\s]+@[^@\s]+\.[^@\s]+$/i)`, or a raw body + optional PCRE flags:
|
|
486
|
+
* `c.regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", "i")`. The pattern is the piped
|
|
487
|
+
* value; the filter's `subject` arg is the text tested against it.
|
|
488
|
+
*/
|
|
489
|
+
regex(pattern, flags) {
|
|
490
|
+
if (typeof pattern !== "string" && !(pattern instanceof RegExp)) {
|
|
491
|
+
throw new Error(`c.regex() takes a pattern string or a RegExp \u2014 got ${describeEntry(pattern)}.`);
|
|
492
|
+
}
|
|
493
|
+
if (flags === null) flags = void 0;
|
|
494
|
+
if (flags !== void 0 && typeof flags !== "string") {
|
|
495
|
+
throw new Error(`c.regex: flags must be letters (e.g. "i", "im"), got ${describeEntry(flags)}.`);
|
|
496
|
+
}
|
|
497
|
+
const body = typeof pattern === "string" ? pattern : pattern.source;
|
|
498
|
+
const raw2 = flags ?? (typeof pattern === "string" ? "" : [...pattern.flags].filter((ch) => PCRE_JS_FLAGS.includes(ch)).join(""));
|
|
499
|
+
if (!/^[a-zA-Z]*$/.test(raw2)) {
|
|
500
|
+
throw new Error(`c.regex: flags must be letters (e.g. "i", "im"), got ${JSON.stringify(raw2)}`);
|
|
501
|
+
}
|
|
502
|
+
return val(`/${escapeRegexSlashes(body)}/${raw2}`, "const");
|
|
503
|
+
},
|
|
504
|
+
/**
|
|
505
|
+
* Current time as an **epoch-milliseconds** value — the engine's native
|
|
506
|
+
* `const:epochms` constant. Xano has no `$now` *setting*, so this lives on
|
|
507
|
+
* `c.*` (a current-time literal) rather than {@link sys} (which emits
|
|
508
|
+
* `setting("$…")` and would reference a dead `$now` var).
|
|
509
|
+
*
|
|
510
|
+
* Emits `{tag:"const:epochms", value:"now"}` — an UNFILTERED value, valid
|
|
511
|
+
* inline as a `where`/`cmp` operand. The equivalent `text("now") |to_epoch_ms`
|
|
512
|
+
* chain evaluates to the same epoch-ms number on a live engine, and both
|
|
513
|
+
* persist verbatim, but the native tag is what the editor writes and needs no
|
|
514
|
+
* filter to get there. Chain math onto it as usual:
|
|
515
|
+
* `withFilters(c.now(), fl.epochms_add_ms(c.int(-maxAgeMs)))`.
|
|
516
|
+
*
|
|
517
|
+
* Inside an `obj()` the tag is flattened into an expression string, where the
|
|
518
|
+
* identifier `now` alone is the STRING "now" — so `obj()` writes the
|
|
519
|
+
* `now|to_epochms` coercion that restores this tag's meaning. A `c.now()` used
|
|
520
|
+
* as a FILTER ARGUMENT there is refused: the coercion cannot be written in
|
|
521
|
+
* that position. Hoist it with `s.set_var` and `ref()` it.
|
|
522
|
+
*/
|
|
523
|
+
now() {
|
|
524
|
+
return val("now", "const:epochms");
|
|
525
|
+
},
|
|
526
|
+
/**
|
|
527
|
+
* Object constant → JSON-string value with `tag:"const:obj"`. Takes **plain
|
|
528
|
+
* JSON literals only**: nesting a tagged value (`inp`/`ref`/`auth`/`c.*`) is a
|
|
529
|
+
* compile error and throws at runtime — it would serialize as internal
|
|
530
|
+
* representation the engine can't decode. For a computed/multi-key object
|
|
531
|
+
* response use a record of values (`response: { key: value }`), not `c.obj`.
|
|
532
|
+
*
|
|
533
|
+
* Called with no argument it is the **empty object**, `{}` — the same default
|
|
534
|
+
* the editor gives a new object variable, and the only empty form current
|
|
535
|
+
* editors write.
|
|
536
|
+
*
|
|
537
|
+
* Called with an explicit `null` it is the **blank** form (stored `value: ""`),
|
|
538
|
+
* which the engine evaluates to `null` rather than `{}` — it JSON-decodes the
|
|
539
|
+
* stored string, and decoding `""` yields null. That is a real difference in
|
|
540
|
+
* what the statement sees, so the two are separate spellings rather than one
|
|
541
|
+
* "empty object": `c.obj()` is `{}`, `c.obj(null)` is null.
|
|
542
|
+
*
|
|
543
|
+
* ⚠ **Prefer `c.obj()`.** The blank form is legacy — no current editor path
|
|
544
|
+
* writes it — and it exists here so a pulled workspace round-trips to the
|
|
545
|
+
* same bytes instead of being quietly re-pointed at `{}`.
|
|
546
|
+
*
|
|
547
|
+
* A **populated** object is stored the way the editor stores one: an empty
|
|
548
|
+
* `{}` base carrying one `set` filter per key. It is NOT a
|
|
549
|
+
* populated JSON string — that form fails the request at runtime, see
|
|
550
|
+
* {@link objSetFilters}.
|
|
551
|
+
*
|
|
552
|
+
* ⚠ **Zero-based numeric keys come back as a LIST**, not an object:
|
|
553
|
+
* `c.obj({ "0": "a" })` evaluates to `["a"]`. That is the engine's data model
|
|
554
|
+
* — a numeric key IS an index there, and the same object decoded from JSON
|
|
555
|
+
* anywhere else behaves identically — not something this encoding introduces.
|
|
556
|
+
* A non-zero-based numeric key (`{ "2": … }`) survives as a key. Live-verified.
|
|
557
|
+
* `export()` warns `value.obj-zero-based-numeric-keys` on the def that carries one.
|
|
558
|
+
*/
|
|
559
|
+
obj(o) {
|
|
560
|
+
if (o === null) return val("", "const:obj");
|
|
561
|
+
const j = o ?? {};
|
|
562
|
+
if (typeof j !== "object" || Array.isArray(j)) {
|
|
563
|
+
throw new Error(
|
|
564
|
+
`c.obj() takes a { key: value } record of plain JSON \u2014 got ${describeEntry(j)}. A number is c.int()/c.decimal(), text c.text(), a list c.array([ \u2026 ]).`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
assertPlainJson(j, "c.obj");
|
|
568
|
+
if (!isPlainRecord(j)) return val(JSON.stringify(j), "const:obj");
|
|
569
|
+
return val("{}", "const:obj", objSetFilters(j));
|
|
570
|
+
},
|
|
571
|
+
/**
|
|
572
|
+
* Array constant → JSON-string value with `tag:"const:array"`. Takes **plain
|
|
573
|
+
* JSON literals only** — like {@link obj}, a nested tagged value is a compile
|
|
574
|
+
* error and throws at runtime.
|
|
575
|
+
*/
|
|
576
|
+
array(a) {
|
|
577
|
+
if (!Array.isArray(a)) {
|
|
578
|
+
throw new Error(
|
|
579
|
+
`c.array() takes a list of plain JSON \u2014 got ${describeEntry(a)}. A record is c.obj({ \u2026 }), an empty list c.array([]).`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
assertPlainJson(a, "c.array");
|
|
583
|
+
return val(JSON.stringify(a), "const:array");
|
|
584
|
+
},
|
|
585
|
+
/**
|
|
586
|
+
* A **Xano Expression Engine** expression, passed through verbatim →
|
|
587
|
+
* `tag:"const:expr2"`. The string IS the expression, exactly as it would be
|
|
588
|
+
* typed into the expression editor:
|
|
589
|
+
*
|
|
590
|
+
* c.expression('"Hello, " ~ $input.name')
|
|
591
|
+
* c.expression("$var.price * $var.qty")
|
|
592
|
+
* c.expression("{ id: $var.user.id, tier: $var.plan }")
|
|
593
|
+
*
|
|
594
|
+
* ⚠️ **THE STRING IS NOT VALIDATED.** Xano SDK does not parse it, does not
|
|
595
|
+
* type-check it, and cannot tell a working expression from a typo — the whole
|
|
596
|
+
* string is handed to the engine as-is. Nothing here participates in
|
|
597
|
+
* `InferResponse`, so a var referenced inside it is invisible to the type
|
|
598
|
+
* system, and a rename that updates every typed `ref()` will NOT update this.
|
|
599
|
+
* A malformed expression surfaces at RUNTIME, and one that is merely wrong
|
|
600
|
+
* (`$var.tota1`) surfaces as a wrong answer, not an error. Play at your own
|
|
601
|
+
* risk until validation exists.
|
|
602
|
+
*
|
|
603
|
+
* Prefer the typed surfaces whenever they cover the case: `ref`/`inp`/`col`
|
|
604
|
+
* for references, `withFilters(..., fl.*)` for transforms, {@link obj} for a
|
|
605
|
+
* dynamic object (it BUILDS a checked expression for you). Reach for this only
|
|
606
|
+
* for expression-engine syntax the typed surfaces cannot express — string
|
|
607
|
+
* concatenation with `~`, inline arithmetic, conditionals.
|
|
608
|
+
*
|
|
609
|
+
* NOT the `expr()` condition builder. `expr(col("id"), "=", inp("id"))` builds
|
|
610
|
+
* a comparison for a `where`; this builds a VALUE from raw expression source.
|
|
611
|
+
*/
|
|
612
|
+
expression(source) {
|
|
613
|
+
assertExpressionSource(source, "c.expression");
|
|
614
|
+
return val(source, "const:expr2");
|
|
615
|
+
},
|
|
616
|
+
/**
|
|
617
|
+
* The **older** expression form → `tag:"const:expr"`, kept because real
|
|
618
|
+
* workspaces still hold values stored that way and codegen has to bring them
|
|
619
|
+
* back as something readable. New code wants {@link expression}, which is what
|
|
620
|
+
* the expression editor writes today.
|
|
621
|
+
*
|
|
622
|
+
* ⚠️ Unvalidated passthrough, exactly like {@link expression} — see the
|
|
623
|
+
* warning there. This one additionally uses the older syntax generation, so an
|
|
624
|
+
* expression copied out of the current editor may not mean the same thing
|
|
625
|
+
* here.
|
|
626
|
+
*/
|
|
627
|
+
expressionLegacy(source) {
|
|
628
|
+
assertExpressionSource(source, "c.expressionLegacy");
|
|
629
|
+
return val(source, "const:expr");
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
function assertExpressionSource(source, fn2) {
|
|
633
|
+
if (typeof source !== "string") {
|
|
634
|
+
const tagged = isTaggedValue(source);
|
|
635
|
+
throw new Error(
|
|
636
|
+
`${fn2}() takes the expression SOURCE as a string, got ${tagged ? "a Value" : describeEntry(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').`)
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
if (source === "") {
|
|
640
|
+
throw new Error(
|
|
641
|
+
`${fn2}() was given an empty string, which the engine cannot evaluate. Pass expression source, or use c.text("") for an empty string constant.`
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function ref(name, opts) {
|
|
646
|
+
assertRefName("ref", name, "the variable name");
|
|
647
|
+
assertNamed("ref", name, 'the variable to read, e.g. ref("total")');
|
|
648
|
+
const dot = name.indexOf(".");
|
|
649
|
+
if (opts?.safe && dot !== -1) {
|
|
650
|
+
const base = name.slice(0, dot);
|
|
651
|
+
const path = name.slice(dot + 1);
|
|
652
|
+
return withFilters(val(base, "var"), filter("get", c.text(path), c.null()));
|
|
653
|
+
}
|
|
654
|
+
return val(name, "var");
|
|
655
|
+
}
|
|
656
|
+
function assertNoRefOptions(creator, opts) {
|
|
657
|
+
if (opts === void 0) return;
|
|
658
|
+
const safe = typeof opts === "object" && opts !== null && "safe" in opts;
|
|
659
|
+
throw new Error(
|
|
660
|
+
`${creator}() takes only a name \u2014 ${safe ? "`safe`" : "a second argument"} is a ref() option and would be silently dropped here. 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.`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
function assertRefName(creator, name, what) {
|
|
664
|
+
if (typeof name === "string") return;
|
|
665
|
+
throw new Error(`${creator}() takes ${what} as a string \u2014 got ${describeEntry(name)}.`);
|
|
666
|
+
}
|
|
667
|
+
function assertNamed(creator, name, what) {
|
|
668
|
+
if (name === "") throw new Error(`${creator}() was given an empty name \u2014 pass ${what}.`);
|
|
669
|
+
}
|
|
670
|
+
function refPath(creator, path) {
|
|
671
|
+
if (path === void 0 || path === null) return "";
|
|
672
|
+
assertRefName(creator, path, "a dotted path");
|
|
673
|
+
return path;
|
|
674
|
+
}
|
|
675
|
+
function inp(name, opts) {
|
|
676
|
+
assertRefName("inp", name, "the input name");
|
|
677
|
+
assertNoRefOptions("inp", opts);
|
|
678
|
+
return val(name, "input");
|
|
679
|
+
}
|
|
680
|
+
function col(name, opts) {
|
|
681
|
+
assertRefName("col", name, "the column name");
|
|
682
|
+
assertNoRefOptions("col", opts);
|
|
683
|
+
return val(name, "col");
|
|
684
|
+
}
|
|
685
|
+
function auth(path = "", opts) {
|
|
686
|
+
assertNoRefOptions("auth", opts);
|
|
687
|
+
return val(refPath("auth", path), "auth");
|
|
688
|
+
}
|
|
689
|
+
function drill(base, path) {
|
|
690
|
+
if (path === "") return base;
|
|
691
|
+
return path.split(".").reduce((out2, seg) => BARE_SET_PATH.test(seg) ? `${out2}.${seg}` : out2 + setPath(seg), base);
|
|
692
|
+
}
|
|
693
|
+
function resp(path = "", opts) {
|
|
694
|
+
assertNoRefOptions("resp", opts);
|
|
695
|
+
return val(drill("response", refPath("resp", path)), "response");
|
|
696
|
+
}
|
|
697
|
+
function caught(path, opts) {
|
|
698
|
+
assertNoRefOptions("caught", opts);
|
|
699
|
+
return val(refPath("caught", path ?? ""), "trycatch");
|
|
700
|
+
}
|
|
701
|
+
function isToolsetPath(value) {
|
|
702
|
+
return value === "token" || value === "params" || value.startsWith("params.");
|
|
703
|
+
}
|
|
704
|
+
function toolset(path, opts) {
|
|
705
|
+
assertRefName("toolset", path, 'a path ("token", "params" or "params.<name>")');
|
|
706
|
+
assertNoRefOptions("toolset", opts);
|
|
707
|
+
return val(path, "toolset");
|
|
708
|
+
}
|
|
709
|
+
function env(name, opts) {
|
|
710
|
+
assertRefName("env", name, "the environment variable name");
|
|
711
|
+
assertNamed("env", name, 'the environment variable to read, e.g. env("STRIPE_KEY")');
|
|
712
|
+
assertNoRefOptions("env", opts);
|
|
713
|
+
return val(name, "setting");
|
|
714
|
+
}
|
|
715
|
+
function setting(name, opts) {
|
|
716
|
+
assertRefName("setting", name, "the setting name");
|
|
717
|
+
assertNamed("setting", name, 'the setting to read, e.g. setting("$remote_ip")');
|
|
718
|
+
assertNoRefOptions("setting", opts);
|
|
719
|
+
return val(name, "setting");
|
|
720
|
+
}
|
|
721
|
+
var sys = {
|
|
722
|
+
/** Client IP address (`$remote_ip`, text). Best public-endpoint rate-limit key. */
|
|
723
|
+
remoteIp: () => val("$remote_ip", "setting"),
|
|
724
|
+
/** HTTP method of the request — `GET`, `POST`, … (`$request_method`, text). */
|
|
725
|
+
requestMethod: () => val("$request_method", "setting"),
|
|
726
|
+
/** Full request URI/path (`$request_uri`, text). */
|
|
727
|
+
requestUri: () => val("$request_uri", "setting"),
|
|
728
|
+
/** Raw query-string portion of the URL (`$request_querystring`, text). */
|
|
729
|
+
requestQueryString: () => val("$request_querystring", "setting"),
|
|
730
|
+
/** Request headers as an object/map (`$http_headers`, object). */
|
|
731
|
+
/**
|
|
732
|
+
* The request's headers, keyed Title-Cased whatever the client sent — a
|
|
733
|
+
* client's `x-sync-secret` is read as `X-Sync-Secret`, so read that spelling.
|
|
734
|
+
* `s.api.call` sends header names the same way.
|
|
735
|
+
*/
|
|
736
|
+
httpHeaders: () => val("$http_headers", "setting"),
|
|
737
|
+
/** The caller's `Authorization` bearer token, if present (`$request_auth_token`, text). */
|
|
738
|
+
requestAuthToken: () => val("$request_auth_token", "setting"),
|
|
739
|
+
/** API base URL for the request (`$api_baseurl`, text). */
|
|
740
|
+
apiBaseUrl: () => val("$api_baseurl", "setting"),
|
|
741
|
+
/** Active data source name — e.g. `live` or a branch source (`$datasource`, text). */
|
|
742
|
+
datasource: () => val("$datasource", "setting"),
|
|
743
|
+
/** Active branch name (`$branch`, text). */
|
|
744
|
+
branch: () => val("$branch", "setting"),
|
|
745
|
+
/** Tenant identifier for multi-tenant instances (`$tenant`, text). */
|
|
746
|
+
tenant: () => val("$tenant", "setting"),
|
|
747
|
+
/** Current release number (`$release`, int). */
|
|
748
|
+
release: () => val("$release", "setting"),
|
|
749
|
+
/** Platform identifier (`$platform`, int). */
|
|
750
|
+
platform: () => val("$platform", "setting"),
|
|
751
|
+
/** `true` when the request is running under the debugger (`$debugger`, bool). */
|
|
752
|
+
isDebugger: () => val("$debugger", "setting")
|
|
753
|
+
};
|
|
754
|
+
function out(name, opts) {
|
|
755
|
+
assertRefName("out", name, "the output name");
|
|
756
|
+
assertNoRefOptions("out", opts);
|
|
757
|
+
return val(name, "output");
|
|
758
|
+
}
|
|
759
|
+
function filter(name, ...args) {
|
|
760
|
+
const last = args.reduce((acc, a, i) => a !== void 0 ? i : acc, -1);
|
|
761
|
+
const hole = args.slice(0, last).findIndex((a) => a === void 0);
|
|
762
|
+
if (hole !== -1) {
|
|
763
|
+
throw new Error(
|
|
764
|
+
`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.`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
assertLambdaFilterArgs(name, args);
|
|
768
|
+
assertExpressionFilterArgs(name, args);
|
|
769
|
+
assertEnumFilterArgs(name, args);
|
|
770
|
+
return { name, disabled: false, arg: args.filter((a) => a !== void 0).map(toPlainValue) };
|
|
771
|
+
}
|
|
772
|
+
function withFilters(value, ...filters) {
|
|
773
|
+
if (!isTaggedValue(value)) {
|
|
774
|
+
throw new Error(
|
|
775
|
+
`withFilters: the first argument must be a tagged value (\`c.*\`, \`ref()\`, \`inp()\`, \u2026) \u2014 got ${describeEntry(value)}. It is the value the chain is applied to; the filters follow it.`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
const added = filters.flat(Infinity);
|
|
779
|
+
const malformed = added.findIndex(
|
|
780
|
+
(f) => f === null || typeof f !== "object" || typeof f.name !== "string"
|
|
781
|
+
);
|
|
782
|
+
if (malformed !== -1) {
|
|
783
|
+
const got = added[malformed];
|
|
784
|
+
throw new Error(
|
|
785
|
+
`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.`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
const reversed = reversedRegexOperands({
|
|
789
|
+
value: value.value,
|
|
790
|
+
tag: value.tag,
|
|
791
|
+
filters: [...value.filters, ...added]
|
|
792
|
+
});
|
|
793
|
+
if (reversed) {
|
|
794
|
+
throw new Error(
|
|
795
|
+
`Regex filter \`${reversed.filter}\`: ${reversed.message} \u2014 \`withFilters(c.regex(\u2026), fl.${reversed.filter}(${reversed.slot === 0 ? "" : "\u2026, "}subject))\`. Naming the argument says which slot is which: \`fl.${reversed.filter}({ subject: \u2026 })\`.`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
const pipedIsBarePattern = barePatternRefusal(value) !== null;
|
|
799
|
+
const offending = pipedIsBarePattern ? added.find((filter2) => REGEX_PATTERN_FILTERS.has(filter2.name)) : void 0;
|
|
800
|
+
if (offending) {
|
|
801
|
+
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.`;
|
|
802
|
+
throw new Error(
|
|
803
|
+
`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}`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
return { ...value, filters: [...value.filters, ...added] };
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/values/lambda.ts
|
|
810
|
+
var LAMBDA_BINDINGS = {
|
|
811
|
+
// Runs once, over no element and no piped value.
|
|
812
|
+
"s.lambda": ["$env", "$input", "$var", "$auth"],
|
|
813
|
+
// Runs once, over the piped value — which it binds as `$this` (NOT `$parent`).
|
|
814
|
+
"fl.lambda": ["$env", "$input", "$var", "$auth", "$this"],
|
|
815
|
+
// The array-iterating filters: one run per element.
|
|
816
|
+
map: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
817
|
+
filter: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
818
|
+
some: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
819
|
+
every: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
820
|
+
find: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
821
|
+
findIndex: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent"],
|
|
822
|
+
// …plus the accumulator, which is `$result`.
|
|
823
|
+
reduce: ["$env", "$input", "$var", "$auth", "$this", "$index", "$parent", "$result"]
|
|
824
|
+
};
|
|
825
|
+
var LAMBDA_GLOBALS = ["console", "crypto"];
|
|
826
|
+
var LAMBDA_MODULE_GLOBALS = [
|
|
827
|
+
"_",
|
|
828
|
+
"aws4",
|
|
829
|
+
"axios",
|
|
830
|
+
"cryptojs",
|
|
831
|
+
"DateTime",
|
|
832
|
+
"ethers",
|
|
833
|
+
"fastXmlParser",
|
|
834
|
+
"jose",
|
|
835
|
+
"luxon",
|
|
836
|
+
"mailparser",
|
|
837
|
+
"math",
|
|
838
|
+
"moment",
|
|
839
|
+
"nodemailer",
|
|
840
|
+
"socks",
|
|
841
|
+
"uuid",
|
|
842
|
+
"utils"
|
|
843
|
+
];
|
|
844
|
+
var LAMBDA_CODE_FILTERS = {
|
|
845
|
+
lambda: { slot: 0, surface: "fl.lambda" },
|
|
846
|
+
map: { slot: 0, surface: "map" },
|
|
847
|
+
filter: { slot: 0, surface: "filter" },
|
|
848
|
+
some: { slot: 0, surface: "some" },
|
|
849
|
+
every: { slot: 0, surface: "every" },
|
|
850
|
+
find: { slot: 0, surface: "find" },
|
|
851
|
+
findIndex: { slot: 0, surface: "findIndex" },
|
|
852
|
+
reduce: { slot: 1, surface: "reduce" }
|
|
853
|
+
};
|
|
854
|
+
function assertLambdaFilterArgs(name, args) {
|
|
855
|
+
const site = Object.hasOwn(LAMBDA_CODE_FILTERS, name) ? LAMBDA_CODE_FILTERS[name] : void 0;
|
|
856
|
+
if (site === void 0) return;
|
|
857
|
+
const code = args[site.slot];
|
|
858
|
+
if (!isInspectableBody(code) || code.value.trim() === "") return;
|
|
859
|
+
assertLambdaBody(code.value, site.surface, `fl.${name}`);
|
|
860
|
+
}
|
|
861
|
+
var LAMBDA_STATEMENTS = {
|
|
862
|
+
"mvp:lambda": { field: "code", surface: "s.lambda" }
|
|
863
|
+
};
|
|
864
|
+
function coerceLambdaFields(storedName, authored) {
|
|
865
|
+
const site = Object.hasOwn(LAMBDA_STATEMENTS, storedName) ? LAMBDA_STATEMENTS[storedName] : void 0;
|
|
866
|
+
if (site === void 0 || typeof authored[site.field] !== "function") return authored;
|
|
867
|
+
return { ...authored, [site.field]: toLambdaValue(authored[site.field], site.surface, storedName === "mvp:lambda" ? "s.lambda" : storedName) };
|
|
868
|
+
}
|
|
869
|
+
function assertLambdaStatement(storedName, authored) {
|
|
870
|
+
const site = Object.hasOwn(LAMBDA_STATEMENTS, storedName) ? LAMBDA_STATEMENTS[storedName] : void 0;
|
|
871
|
+
if (site === void 0) return;
|
|
872
|
+
const code = authored[site.field];
|
|
873
|
+
if (!isInspectableBody(code) || code.value.trim() === "") return;
|
|
874
|
+
assertLambdaBody(code.value, site.surface, "s.lambda");
|
|
875
|
+
}
|
|
876
|
+
function isInspectableBody(code) {
|
|
877
|
+
return typeof code === "object" && code !== null && code.tag === "const" && typeof code.value === "string" && (code.filters?.length ?? 0) === 0;
|
|
878
|
+
}
|
|
879
|
+
function toLambdaValue(x, surface, source) {
|
|
880
|
+
if (typeof x !== "function") return x;
|
|
881
|
+
const { body, captureReads } = extractFunction(String(x), source);
|
|
882
|
+
return lambdaValue(body, { surface }, source, true, captureReads);
|
|
883
|
+
}
|
|
884
|
+
var PREFIX = "lam";
|
|
885
|
+
function extractFunction(source, caller) {
|
|
886
|
+
const src = source.trim();
|
|
887
|
+
if (src.includes("[native code]")) {
|
|
888
|
+
throw new Error(
|
|
889
|
+
`${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.`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
const fn2 = readFunction(src, caller);
|
|
893
|
+
const [ps, pe] = fn2.params;
|
|
894
|
+
const [bs, be] = fn2.body;
|
|
895
|
+
const params = decodeIdentifierEscapes(src.slice(ps, pe));
|
|
896
|
+
const captureReads = assertDestructuredParams(params, maskNonCode(params), maskNonCode(decodeIdentifierEscapes(src.slice(bs, be))), caller);
|
|
897
|
+
const text = src.slice(bs, be);
|
|
898
|
+
if (fn2.block) return { body: stripKeepNames(dedent(text.trim())), captureReads };
|
|
899
|
+
const lead = /[ \t]*$/.exec(src.slice(0, bs))?.[0] ?? "";
|
|
900
|
+
const ownLine = /(^|\n)[ \t]*$/.test(src.slice(0, bs));
|
|
901
|
+
const expr = ownLine ? dedent(`
|
|
902
|
+
${lead}${text}`).slice(1).trimStart() : dedent(text);
|
|
903
|
+
const body = `return ${expr};`;
|
|
904
|
+
return { body: stripKeepNames(body), captureReads };
|
|
905
|
+
}
|
|
906
|
+
function readFunction(src, caller) {
|
|
907
|
+
const reader = new SourceReader(tokenize(src));
|
|
908
|
+
const refuse = (reason) => {
|
|
909
|
+
throw new Error(
|
|
910
|
+
`${caller}: could not read ${JSON.stringify(src.slice(0, 80))} as one arrow function or function expression (${reason}), so the body cannot be located with certainty. Write the function itself \u2014 \`({ $var }: \u2026) => \u2026\` or \`function ({ $var }: \u2026) { \u2026 }\` \u2014 and nothing else. A class method or an object shorthand method does not extract; write it as an arrow function.`
|
|
911
|
+
);
|
|
912
|
+
};
|
|
913
|
+
try {
|
|
914
|
+
const { fn: fn2, end } = reader.parseFunctionExpression(0);
|
|
915
|
+
let k = end;
|
|
916
|
+
while (reader.isP(k, ";")) k++;
|
|
917
|
+
if (k < reader.toks.length) reader.fail(`\`${reader.toks[k].text}\` after the function`, k);
|
|
918
|
+
return fn2;
|
|
919
|
+
} catch (e) {
|
|
920
|
+
if (e instanceof SourceShapeError) return refuse(`${e.message} at line ${lineOf(src, e.offset)}`);
|
|
921
|
+
throw e;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
var KEEP_NAMES_CALL = new RegExp(String.raw`()(?<!${ID_CHAR}|[\\.])__name\s*\(`, "u");
|
|
925
|
+
function stripKeepNames(body) {
|
|
926
|
+
let out2 = body;
|
|
927
|
+
for (; ; ) {
|
|
928
|
+
const mask = maskNonCode(out2);
|
|
929
|
+
const call = KEEP_NAMES_CALL.exec(mask);
|
|
930
|
+
if (call === null) break;
|
|
931
|
+
const start = call.index + (call[1] ?? "").length;
|
|
932
|
+
const open = mask.indexOf("(", start);
|
|
933
|
+
const close = closingParen(mask, start) - 1;
|
|
934
|
+
if (close <= open) break;
|
|
935
|
+
const args = out2.slice(open + 1, close);
|
|
936
|
+
const comma = splitTopLevel(maskNonCode(args))[0]?.length ?? args.length;
|
|
937
|
+
const first = args.slice(0, comma).trim();
|
|
938
|
+
const before = mask.slice(0, start);
|
|
939
|
+
const isStatement = /(^|[;{}])\s*$/.test(before) && isIdentifierName(first);
|
|
940
|
+
const trailing = isStatement ? /^\s*;?/.exec(out2.slice(close + 1))?.[0].length ?? 0 : 0;
|
|
941
|
+
out2 = out2.slice(0, start) + (isStatement ? "" : first) + out2.slice(close + 1 + trailing);
|
|
942
|
+
}
|
|
943
|
+
return out2.trim();
|
|
944
|
+
}
|
|
945
|
+
function dedent(body) {
|
|
946
|
+
if (body.includes("`")) return body;
|
|
947
|
+
const lines = body.split("\n");
|
|
948
|
+
const indents = lines.slice(1).filter((l) => l.trim() !== "").map((l) => /^[ \t]*/.exec(l)?.[0].length ?? 0);
|
|
949
|
+
const common = indents.length ? Math.min(...indents) : 0;
|
|
950
|
+
if (common === 0) return body;
|
|
951
|
+
return [lines[0], ...lines.slice(1).map((l) => l.slice(common))].join("\n");
|
|
952
|
+
}
|
|
953
|
+
function assertDestructuredParams(params, paramsMask, bodyMask, caller) {
|
|
954
|
+
const open = paramsMask.indexOf("(");
|
|
955
|
+
const close = open === -1 ? -1 : closingParen(paramsMask, open) - 1;
|
|
956
|
+
const [inner, innerMask] = open === -1 || close <= open ? [params, paramsMask] : [params.slice(open + 1, close), paramsMask.slice(open + 1, close)];
|
|
957
|
+
let at = 0;
|
|
958
|
+
const captureReads = [];
|
|
959
|
+
splitTopLevel(innerMask).forEach((partMask, index) => {
|
|
960
|
+
const part = inner.slice(at, at + partMask.length);
|
|
961
|
+
at += partMask.length + 1;
|
|
962
|
+
const lead = /^\s*/.exec(partMask)?.[0].length ?? 0;
|
|
963
|
+
if (partMask[lead] === "{" || partMask[lead] === "[") {
|
|
964
|
+
assertPlainPattern(part, partMask, lead, index, bodyMask, caller);
|
|
965
|
+
if (index === 1) {
|
|
966
|
+
const end = closingBracket(partMask, lead);
|
|
967
|
+
captureReads.push(...patternNames(partMask.slice(lead, end), part.slice(lead, end)).filter((n) => readsName(bodyMask, n)));
|
|
968
|
+
}
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const name = new RegExp(String.raw`^\s*(?:\.\.\.)?\s*(${IDENT})`, "u").exec(partMask)?.[1];
|
|
972
|
+
if (name === void 0) return;
|
|
973
|
+
if (!readsName(bodyMask, name)) return;
|
|
974
|
+
const fix = index === 0 ? `DESTRUCTURE the bindings instead: \`({ ${name.startsWith("$") ? name : "$this"} }) => \u2026\` rather than \`(${name}) => \u2026 ${name}.$this \u2026\`.` : index === 1 ? `The second parameter carries \`capture\` only: DESTRUCTURE the keys you captured (\`(_, { rate }) => \u2026 rate \u2026\`), or declare \`${name}\` inside the body (\`const ${name} = \u2026\`).` : `Nothing arrives there: a lambda receives its bindings and its capture only. Declare \`${name}\` inside the body (\`const ${name} = \u2026\`), or bring a value in through \`capture\`.`;
|
|
975
|
+
throw new Error(
|
|
976
|
+
`${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. ${fix} As written, \`${name}\` is undefined at runtime and the engine returns that failure as text in the value slot.`
|
|
977
|
+
);
|
|
978
|
+
});
|
|
979
|
+
return captureReads;
|
|
980
|
+
}
|
|
981
|
+
function readsName(bodyMask, name) {
|
|
982
|
+
return new RegExp(`(?<!${ID_CHAR}|\\.)${name.replace(/\$/g, "\\$")}(?!${ID_CHAR})`, "u").test(bodyMask);
|
|
983
|
+
}
|
|
984
|
+
function assertPlainPattern(part, partMask, lead, index, bodyMask, caller) {
|
|
985
|
+
const end = closingBracket(partMask, lead);
|
|
986
|
+
const pattern = part.slice(lead, end);
|
|
987
|
+
const patternMask = partMask.slice(lead, end);
|
|
988
|
+
const notReal = `a lambda's parameters are not real \u2014 only the BODY is sent, and the engine injects each ` + (index === 0 ? "binding" : "capture key") + ` under its OWN name`;
|
|
989
|
+
if (index >= 2) {
|
|
990
|
+
const read = patternNames(patternMask, pattern).find((n) => readsName(bodyMask, n));
|
|
991
|
+
if (read === void 0) return;
|
|
992
|
+
throw new Error(
|
|
993
|
+
`${caller}: the body reads \`${read}\`, but ${notReal}, and nothing arrives in a third parameter. Declare \`${read}\` inside the body (\`const ${read} = \u2026\`), or bring a value in through \`capture\`.`
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
const plain = `({ $this }) => $this.price`;
|
|
997
|
+
const fix = index === 0 ? `Destructure the bindings by their own names, with no renaming, nesting, default or rest \u2014 \`${plain}\`, \`({ $var }) => $var.subtotal\` \u2014 and read deeper values in the body.` : `Destructure the capture keys by their own names \u2014 \`(_, { rate }) => rate\` \u2014 and pick the name in \`capture\` itself (\`capture: { r: rate }\`, read as \`(_, { r }) => r\`).`;
|
|
998
|
+
if (patternMask.startsWith("[")) {
|
|
999
|
+
throw new Error(
|
|
1000
|
+
`${caller}: the ${index === 0 ? "first" : "second"} parameter is an array pattern \`${pattern}\`, but ${notReal}, so nothing it names exists at runtime. ${fix}`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
let at = 1;
|
|
1004
|
+
for (const propMask of splitTopLevel(patternMask.slice(1, -1))) {
|
|
1005
|
+
const prop = pattern.slice(at, at + propMask.length).trim();
|
|
1006
|
+
at += propMask.length + 1;
|
|
1007
|
+
if (propMask.trim() === "" || isIdentifierName(propMask.trim()) || isQuotedShorthand(prop)) continue;
|
|
1008
|
+
const what = /^\s*\.\.\./.test(propMask) ? "a rest element" : hasTopLevel(propMask, ":") ? "a renamed or nested property" : hasTopLevel(propMask, "=") ? "a default (it never applies)" : "a computed or quoted key";
|
|
1009
|
+
const alias = hasTopLevel(propMask, ":") ? patternNames(`{${propMask}}`, `{${prop}}`)[0] : void 0;
|
|
1010
|
+
throw new Error(
|
|
1011
|
+
`${caller}: \`${prop}\` in the parameter pattern \`${pattern}\` is ${what}, but ${notReal}` + (alias === void 0 ? `, so the pattern does nothing at runtime.` : isFreeIdentifier(alias) ? `, so \`${alias}\` is never defined: the body throws, and the engine returns that failure as text in the value slot with HTTP 200.` : `, so \`${alias}\` reads the global of that name instead \u2014 a wrong value with HTTP 200.`) + ` ${fix}`
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function closingBracket(mask, from) {
|
|
1016
|
+
let depth = 0;
|
|
1017
|
+
for (let i = from; i < mask.length; i++) {
|
|
1018
|
+
const ch = mask[i];
|
|
1019
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
1020
|
+
else if ((ch === ")" || ch === "]" || ch === "}") && --depth === 0) return i + 1;
|
|
1021
|
+
}
|
|
1022
|
+
return mask.length;
|
|
1023
|
+
}
|
|
1024
|
+
function hasTopLevel(mask, ch) {
|
|
1025
|
+
let depth = 0;
|
|
1026
|
+
for (let i = 0; i < mask.length; i++) {
|
|
1027
|
+
const c2 = mask[i];
|
|
1028
|
+
if (c2 === "(" || c2 === "[" || c2 === "{") depth++;
|
|
1029
|
+
else if (c2 === ")" || c2 === "]" || c2 === "}") depth--;
|
|
1030
|
+
else if (depth === 0 && c2 === ch && !(ch === "=" && /[=>]/.test(mask[i + 1] ?? ""))) return true;
|
|
1031
|
+
}
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
function patternNames(mask, text) {
|
|
1035
|
+
const m = mask.trim();
|
|
1036
|
+
const offset = mask.indexOf(m);
|
|
1037
|
+
const t = text.slice(offset, offset + m.length);
|
|
1038
|
+
if (m.startsWith("{") || m.startsWith("[")) {
|
|
1039
|
+
const end = closingBracket(m, 0);
|
|
1040
|
+
const names = [];
|
|
1041
|
+
let at = 1;
|
|
1042
|
+
for (const elMask of splitTopLevel(m.slice(1, end - 1))) {
|
|
1043
|
+
const el = t.slice(at, at + elMask.length);
|
|
1044
|
+
at += elMask.length + 1;
|
|
1045
|
+
let cut = elMask.length;
|
|
1046
|
+
let depth = 0;
|
|
1047
|
+
let colon = -1;
|
|
1048
|
+
for (let i = 0; i < elMask.length; i++) {
|
|
1049
|
+
const c2 = elMask[i];
|
|
1050
|
+
if (c2 === "(" || c2 === "[" || c2 === "{") depth++;
|
|
1051
|
+
else if (c2 === ")" || c2 === "]" || c2 === "}") depth--;
|
|
1052
|
+
else if (depth === 0 && c2 === ":" && colon === -1 && m.startsWith("{")) colon = i;
|
|
1053
|
+
else if (depth === 0 && c2 === "=" && !/[=>]/.test(elMask[i + 1] ?? "")) {
|
|
1054
|
+
cut = i;
|
|
1055
|
+
break;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
const from = colon === -1 ? 0 : colon + 1;
|
|
1059
|
+
const target = elMask.slice(from, cut).replace(/^(\s*)\.\.\./, "$1 ");
|
|
1060
|
+
names.push(...patternNames(target, el.slice(from, cut)));
|
|
1061
|
+
}
|
|
1062
|
+
return names;
|
|
1063
|
+
}
|
|
1064
|
+
const id = new RegExp(`^${IDENT}`, "u").exec(m)?.[0];
|
|
1065
|
+
return id === void 0 ? [] : [id];
|
|
1066
|
+
}
|
|
1067
|
+
function splitTopLevel(text) {
|
|
1068
|
+
const parts = [];
|
|
1069
|
+
let depth = 0;
|
|
1070
|
+
let start = 0;
|
|
1071
|
+
for (let i = 0; i < text.length; i++) {
|
|
1072
|
+
const ch = text[i];
|
|
1073
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
1074
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
1075
|
+
else if (ch === "," && depth === 0) {
|
|
1076
|
+
parts.push(text.slice(start, i));
|
|
1077
|
+
start = i + 1;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
parts.push(text.slice(start));
|
|
1081
|
+
return parts;
|
|
1082
|
+
}
|
|
1083
|
+
function closingParen(mask, from) {
|
|
1084
|
+
const open = mask.indexOf("(", from);
|
|
1085
|
+
if (open === -1) return from;
|
|
1086
|
+
let depth = 0;
|
|
1087
|
+
for (let i = open; i < mask.length; i++) {
|
|
1088
|
+
if (mask[i] === "(") depth++;
|
|
1089
|
+
else if (mask[i] === ")" && --depth === 0) return i + 1;
|
|
1090
|
+
}
|
|
1091
|
+
return from;
|
|
1092
|
+
}
|
|
1093
|
+
function declaredLocals(mask, pattern = new RegExp(String.raw`\$${ID_CHAR}*`, "gu")) {
|
|
1094
|
+
const declared = /* @__PURE__ */ new Set();
|
|
1095
|
+
const add = (text) => {
|
|
1096
|
+
for (const m of (text ?? "").matchAll(pattern)) declared.add(m[0]);
|
|
1097
|
+
};
|
|
1098
|
+
for (const m of mask.matchAll(/\b(?:const|let|var)\s+([^;\n]+)/g)) {
|
|
1099
|
+
for (const declarator of (m[1] ?? "").split(",")) add(declarator.split("=")[0]);
|
|
1100
|
+
}
|
|
1101
|
+
for (const m of mask.matchAll(new RegExp(String.raw`\b(?:function|class)\s+(${IDENT})`, "gu"))) add(m[1]);
|
|
1102
|
+
for (const m of mask.matchAll(/\bcatch\s*\(([^)]*)\)/g)) add(m[1]);
|
|
1103
|
+
for (const m of mask.matchAll(new RegExp(String.raw`(\([^()]*\)|${IDENT})\s*=>`, "gu"))) add(m[1]);
|
|
1104
|
+
for (const m of mask.matchAll(/\bfunction\b[^(]*\(([^)]*)\)/g)) add(m[1]);
|
|
1105
|
+
return declared;
|
|
1106
|
+
}
|
|
1107
|
+
function dollarTokens(mask) {
|
|
1108
|
+
return tokens(mask, new RegExp(String.raw`(^|\.\.\.|[^\p{ID_Continue}$.])(\$${ID_CHAR}+)`, "gu"));
|
|
1109
|
+
}
|
|
1110
|
+
function tokens(mask, pattern, skipKeys = false) {
|
|
1111
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1112
|
+
const out2 = [];
|
|
1113
|
+
for (const m of mask.matchAll(pattern)) {
|
|
1114
|
+
const name = m[2] ?? "";
|
|
1115
|
+
if (name === "" || seen.has(name)) continue;
|
|
1116
|
+
if (skipKeys) {
|
|
1117
|
+
const before = mask.slice(0, m.index + (m[1] ?? "").length).trimEnd().slice(-1);
|
|
1118
|
+
const after = mask.slice(m.index + m[0].length).trimStart().slice(0, 1);
|
|
1119
|
+
if (after === ":" && (before === "{" || before === ",")) continue;
|
|
1120
|
+
}
|
|
1121
|
+
seen.add(name);
|
|
1122
|
+
out2.push(name);
|
|
1123
|
+
}
|
|
1124
|
+
return out2;
|
|
1125
|
+
}
|
|
1126
|
+
var BUNDLER_HELPERS = {
|
|
1127
|
+
__name: "a named inner function",
|
|
1128
|
+
__publicField: "a class field",
|
|
1129
|
+
__privateGet: "a #private field",
|
|
1130
|
+
__privateSet: "a #private field",
|
|
1131
|
+
__privateAdd: "a #private field",
|
|
1132
|
+
__privateMethod: "a #private method",
|
|
1133
|
+
__decorateClass: "a decorator",
|
|
1134
|
+
__decorateParam: "a parameter decorator",
|
|
1135
|
+
__async: "async/await",
|
|
1136
|
+
__generator: "a generator",
|
|
1137
|
+
__await: "await",
|
|
1138
|
+
__forAwait: "for await",
|
|
1139
|
+
__yieldStar: "yield*",
|
|
1140
|
+
__spreadValues: "an object spread",
|
|
1141
|
+
__spreadProps: "an object spread",
|
|
1142
|
+
__objRest: "a rest destructuring",
|
|
1143
|
+
__restKey: "a rest destructuring",
|
|
1144
|
+
__pow: "the `**` operator",
|
|
1145
|
+
__using: "`using`",
|
|
1146
|
+
__callDispose: "`using`",
|
|
1147
|
+
__toESM: "an import",
|
|
1148
|
+
__toCommonJS: "an export",
|
|
1149
|
+
__require: "a require",
|
|
1150
|
+
__awaiter: "async/await",
|
|
1151
|
+
__assign: "an object spread",
|
|
1152
|
+
__rest: "a rest destructuring",
|
|
1153
|
+
__decorate: "a decorator",
|
|
1154
|
+
__extends: "a class",
|
|
1155
|
+
__spreadArray: "an array spread"
|
|
1156
|
+
};
|
|
1157
|
+
var HELPER_LOOKALIKES = /* @__PURE__ */ new Set(["__proto__", "__dirname", "__filename"]);
|
|
1158
|
+
function suggestion(token, surface) {
|
|
1159
|
+
const ACCUMULATOR_GUESSES = ["$acc", "$accumulator", "$carry", "$memo", "$total", "$prev", "$previous"];
|
|
1160
|
+
if (ACCUMULATOR_GUESSES.includes(token) && LAMBDA_BINDINGS[surface].includes("$result")) {
|
|
1161
|
+
return `\`$result\` is the accumulator at this surface`;
|
|
1162
|
+
}
|
|
1163
|
+
if (ACCUMULATOR_GUESSES.includes(token)) {
|
|
1164
|
+
return `\`$result\` is the accumulator, and only \`reduce\` binds it`;
|
|
1165
|
+
}
|
|
1166
|
+
const elsewhere = Object.keys(LAMBDA_BINDINGS).filter(
|
|
1167
|
+
(s) => LAMBDA_BINDINGS[s].includes(token)
|
|
1168
|
+
);
|
|
1169
|
+
if (elsewhere.length) return `\`${token}\` is bound at ${elsewhere.join(", ")}, but not here`;
|
|
1170
|
+
if (token === "$parent" || token === "$this") {
|
|
1171
|
+
return `the value a filter is applied to is \`$this\` in \`fl.lambda\`, and \`$parent\` in the array-iterating filters`;
|
|
1172
|
+
}
|
|
1173
|
+
return void 0;
|
|
1174
|
+
}
|
|
1175
|
+
function assertLambdaBody(body, surface, source = `${PREFIX}.fn`) {
|
|
1176
|
+
try {
|
|
1177
|
+
assertBody(body, surface, source);
|
|
1178
|
+
} catch (err) {
|
|
1179
|
+
if (collector === null) throw err;
|
|
1180
|
+
collector(err instanceof Error ? err.message : String(err));
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
var collector = null;
|
|
1184
|
+
function collectLambdaBodyProblems(onProblem, fn2) {
|
|
1185
|
+
const previous = collector;
|
|
1186
|
+
collector = onProblem;
|
|
1187
|
+
try {
|
|
1188
|
+
return fn2();
|
|
1189
|
+
} finally {
|
|
1190
|
+
collector = previous;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
var SYNTAX_CHECK = /* @__PURE__ */ Symbol.for("xanosdk.lambda.syntax-check");
|
|
1194
|
+
var FREE_READS = /* @__PURE__ */ Symbol.for("xanosdk.lambda.free-reads");
|
|
1195
|
+
function installLambdaFreeReads(reader) {
|
|
1196
|
+
globalThis[FREE_READS] = reader;
|
|
1197
|
+
}
|
|
1198
|
+
function installLambdaSyntaxCheck(check) {
|
|
1199
|
+
globalThis[SYNTAX_CHECK] = check;
|
|
1200
|
+
}
|
|
1201
|
+
function lambdaSyntaxError(body) {
|
|
1202
|
+
const check = globalThis[SYNTAX_CHECK];
|
|
1203
|
+
return typeof check === "function" ? check(body) : void 0;
|
|
1204
|
+
}
|
|
1205
|
+
function assertBody(body, surface, source) {
|
|
1206
|
+
if (body.trim() === "") {
|
|
1207
|
+
throw new Error(
|
|
1208
|
+
`${source}: the lambda body is empty. A lambda must return a value \u2014 the engine refuses a statement with no code.`
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
const mask = maskNonCode(decodeIdentifierEscapes(body));
|
|
1212
|
+
const legal = LAMBDA_BINDINGS[surface];
|
|
1213
|
+
const scoped = globalThis[FREE_READS];
|
|
1214
|
+
const candidates = typeof scoped === "function" ? scoped(mask).filter((n) => n.startsWith("$")) : (() => {
|
|
1215
|
+
const locals = declaredLocals(mask);
|
|
1216
|
+
return dollarTokens(mask).filter((n) => !locals.has(n));
|
|
1217
|
+
})();
|
|
1218
|
+
for (const token of candidates) {
|
|
1219
|
+
if (legal.includes(token)) continue;
|
|
1220
|
+
const hint = suggestion(token, surface);
|
|
1221
|
+
throw new Error(
|
|
1222
|
+
`${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\`.`
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
assertNoBundlerHelpers(mask, source);
|
|
1226
|
+
const moduleSyntax = /(^|[;{}\n])\s*(import|export)\b(?![\s]*[.(])/.exec(mask);
|
|
1227
|
+
if (moduleSyntax) {
|
|
1228
|
+
const kw = moduleSyntax[2];
|
|
1229
|
+
throw new Error(
|
|
1230
|
+
`${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.`
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
const syntax = lambdaSyntaxError(body);
|
|
1234
|
+
if (syntax !== void 0) {
|
|
1235
|
+
throw new Error(
|
|
1236
|
+
`${source}: the lambda body does not parse \u2014 ${syntax.message} (body line ${syntax.line}, column ${syntax.column}: \`${syntax.lineText.trim()}\`). A body that does not parse cannot run.`
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
var KNOWN_NAMES = /* @__PURE__ */ new Set([
|
|
1241
|
+
// keywords and literals
|
|
1242
|
+
"await",
|
|
1243
|
+
"async",
|
|
1244
|
+
"break",
|
|
1245
|
+
"case",
|
|
1246
|
+
"catch",
|
|
1247
|
+
"class",
|
|
1248
|
+
"const",
|
|
1249
|
+
"continue",
|
|
1250
|
+
"debugger",
|
|
1251
|
+
"default",
|
|
1252
|
+
"delete",
|
|
1253
|
+
"do",
|
|
1254
|
+
"else",
|
|
1255
|
+
"export",
|
|
1256
|
+
"extends",
|
|
1257
|
+
"false",
|
|
1258
|
+
"finally",
|
|
1259
|
+
"for",
|
|
1260
|
+
"function",
|
|
1261
|
+
"if",
|
|
1262
|
+
"import",
|
|
1263
|
+
"in",
|
|
1264
|
+
"instanceof",
|
|
1265
|
+
"let",
|
|
1266
|
+
"new",
|
|
1267
|
+
"null",
|
|
1268
|
+
"of",
|
|
1269
|
+
"return",
|
|
1270
|
+
"static",
|
|
1271
|
+
"super",
|
|
1272
|
+
"switch",
|
|
1273
|
+
"this",
|
|
1274
|
+
"throw",
|
|
1275
|
+
"true",
|
|
1276
|
+
"try",
|
|
1277
|
+
"typeof",
|
|
1278
|
+
"undefined",
|
|
1279
|
+
"var",
|
|
1280
|
+
"void",
|
|
1281
|
+
"while",
|
|
1282
|
+
"with",
|
|
1283
|
+
"yield",
|
|
1284
|
+
"get",
|
|
1285
|
+
"set",
|
|
1286
|
+
"arguments",
|
|
1287
|
+
"NaN",
|
|
1288
|
+
"Infinity",
|
|
1289
|
+
"as",
|
|
1290
|
+
// ECMAScript globals
|
|
1291
|
+
"globalThis",
|
|
1292
|
+
"Object",
|
|
1293
|
+
"Array",
|
|
1294
|
+
"String",
|
|
1295
|
+
"Number",
|
|
1296
|
+
"Boolean",
|
|
1297
|
+
"Symbol",
|
|
1298
|
+
"BigInt",
|
|
1299
|
+
"Math",
|
|
1300
|
+
"JSON",
|
|
1301
|
+
"Date",
|
|
1302
|
+
"RegExp",
|
|
1303
|
+
"Error",
|
|
1304
|
+
"TypeError",
|
|
1305
|
+
"RangeError",
|
|
1306
|
+
"SyntaxError",
|
|
1307
|
+
"ReferenceError",
|
|
1308
|
+
"EvalError",
|
|
1309
|
+
"URIError",
|
|
1310
|
+
"AggregateError",
|
|
1311
|
+
"Promise",
|
|
1312
|
+
"Map",
|
|
1313
|
+
"Set",
|
|
1314
|
+
"WeakMap",
|
|
1315
|
+
"WeakSet",
|
|
1316
|
+
"WeakRef",
|
|
1317
|
+
"FinalizationRegistry",
|
|
1318
|
+
"Proxy",
|
|
1319
|
+
"Reflect",
|
|
1320
|
+
"ArrayBuffer",
|
|
1321
|
+
"SharedArrayBuffer",
|
|
1322
|
+
"DataView",
|
|
1323
|
+
"Int8Array",
|
|
1324
|
+
"Uint8Array",
|
|
1325
|
+
"Uint8ClampedArray",
|
|
1326
|
+
"Int16Array",
|
|
1327
|
+
"Uint16Array",
|
|
1328
|
+
"Int32Array",
|
|
1329
|
+
"Uint32Array",
|
|
1330
|
+
"Float32Array",
|
|
1331
|
+
"Float64Array",
|
|
1332
|
+
"BigInt64Array",
|
|
1333
|
+
"BigUint64Array",
|
|
1334
|
+
"Intl",
|
|
1335
|
+
"Atomics",
|
|
1336
|
+
"Iterator",
|
|
1337
|
+
"Function",
|
|
1338
|
+
"parseInt",
|
|
1339
|
+
"parseFloat",
|
|
1340
|
+
"isNaN",
|
|
1341
|
+
"isFinite",
|
|
1342
|
+
"encodeURI",
|
|
1343
|
+
"encodeURIComponent",
|
|
1344
|
+
"decodeURI",
|
|
1345
|
+
"decodeURIComponent",
|
|
1346
|
+
"escape",
|
|
1347
|
+
"unescape",
|
|
1348
|
+
"eval",
|
|
1349
|
+
// web / runtime globals a body's host provides (live-probed; see LAMBDA_MODULE_GLOBALS)
|
|
1350
|
+
"fetch",
|
|
1351
|
+
"Request",
|
|
1352
|
+
"Response",
|
|
1353
|
+
"Headers",
|
|
1354
|
+
"URL",
|
|
1355
|
+
"URLSearchParams",
|
|
1356
|
+
"TextEncoder",
|
|
1357
|
+
"TextDecoder",
|
|
1358
|
+
"Buffer",
|
|
1359
|
+
"atob",
|
|
1360
|
+
"btoa",
|
|
1361
|
+
"setTimeout",
|
|
1362
|
+
"clearTimeout",
|
|
1363
|
+
"setInterval",
|
|
1364
|
+
"clearInterval",
|
|
1365
|
+
"queueMicrotask",
|
|
1366
|
+
"structuredClone",
|
|
1367
|
+
"AbortController",
|
|
1368
|
+
"AbortSignal",
|
|
1369
|
+
"Blob",
|
|
1370
|
+
"File",
|
|
1371
|
+
"FormData",
|
|
1372
|
+
"ReadableStream",
|
|
1373
|
+
"WritableStream",
|
|
1374
|
+
"TransformStream",
|
|
1375
|
+
"performance",
|
|
1376
|
+
"Deno",
|
|
1377
|
+
"process",
|
|
1378
|
+
"require",
|
|
1379
|
+
"self",
|
|
1380
|
+
"navigator",
|
|
1381
|
+
"EventSource",
|
|
1382
|
+
"WebSocket",
|
|
1383
|
+
"Event",
|
|
1384
|
+
"EventTarget",
|
|
1385
|
+
"CompressionStream",
|
|
1386
|
+
"DecompressionStream",
|
|
1387
|
+
"reportError",
|
|
1388
|
+
"promisify",
|
|
1389
|
+
"dns",
|
|
1390
|
+
"http",
|
|
1391
|
+
"https",
|
|
1392
|
+
"net",
|
|
1393
|
+
"url",
|
|
1394
|
+
"zeebe",
|
|
1395
|
+
"azure",
|
|
1396
|
+
"rjsf",
|
|
1397
|
+
...LAMBDA_GLOBALS,
|
|
1398
|
+
...LAMBDA_MODULE_GLOBALS
|
|
1399
|
+
]);
|
|
1400
|
+
function isFreeIdentifier(name) {
|
|
1401
|
+
if (KNOWN_NAMES.has(name)) return false;
|
|
1402
|
+
return !(name in globalThis);
|
|
1403
|
+
}
|
|
1404
|
+
function closureDeclarations(mask) {
|
|
1405
|
+
const declared = /* @__PURE__ */ new Set();
|
|
1406
|
+
const bind = (target) => {
|
|
1407
|
+
const t = target.replace(/^\s*\.\.\./, "");
|
|
1408
|
+
for (const name of patternNames(t, t)) declared.add(name);
|
|
1409
|
+
};
|
|
1410
|
+
for (const m of mask.matchAll(new RegExp(String.raw`(?<!${ID_CHAR}|\.)(?:const|let|var)(?!${ID_CHAR})`, "gu"))) {
|
|
1411
|
+
const from = m.index + m[0].length;
|
|
1412
|
+
for (const declarator of splitTopLevel(mask.slice(from, listEnd(mask, from)))) {
|
|
1413
|
+
bind(declarator.slice(0, topLevelIndex(declarator, DECLARATOR_END)));
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
for (const m of mask.matchAll(new RegExp(String.raw`\b(?:function\*?|class)\s*\*?\s*(${IDENT})`, "gu"))) {
|
|
1417
|
+
if (m[1] !== void 0) declared.add(m[1]);
|
|
1418
|
+
}
|
|
1419
|
+
for (let i = mask.indexOf("("); i !== -1; i = mask.indexOf("(", i + 1)) {
|
|
1420
|
+
const word = new RegExp(String.raw`(${IDENT})\s*$`, "u").exec(mask.slice(Math.max(0, i - 64), i))?.[1];
|
|
1421
|
+
if (word !== void 0 && /^(if|while|for|switch|with)$/.test(word)) continue;
|
|
1422
|
+
const close = closingParen(mask, i);
|
|
1423
|
+
if (close === i) continue;
|
|
1424
|
+
if (!/^\s*(?:\{|=>)/.test(mask.slice(close))) continue;
|
|
1425
|
+
for (const param of splitTopLevel(mask.slice(i + 1, close - 1))) {
|
|
1426
|
+
bind(param.slice(0, topLevelIndex(param, PARAM_DEFAULT)));
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
for (const m of mask.matchAll(new RegExp(String.raw`(?<!${ID_CHAR}|\.)(${IDENT})\s*=>`, "gu"))) {
|
|
1430
|
+
if (m[1] !== void 0) declared.add(m[1]);
|
|
1431
|
+
}
|
|
1432
|
+
return declared;
|
|
1433
|
+
}
|
|
1434
|
+
function listEnd(mask, from) {
|
|
1435
|
+
let depth = 0;
|
|
1436
|
+
for (let i = from; i < mask.length; i++) {
|
|
1437
|
+
const ch = mask[i];
|
|
1438
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
1439
|
+
else if (ch === ")" || ch === "]" || ch === "}") {
|
|
1440
|
+
if (--depth < 0) return i;
|
|
1441
|
+
} else if (ch === ";" && depth === 0) return i;
|
|
1442
|
+
}
|
|
1443
|
+
return mask.length;
|
|
1444
|
+
}
|
|
1445
|
+
function topLevelIndex(text, at) {
|
|
1446
|
+
let depth = 0;
|
|
1447
|
+
for (let i = 0; i < text.length; i++) {
|
|
1448
|
+
const ch = text[i];
|
|
1449
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
1450
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
1451
|
+
else if (depth === 0) {
|
|
1452
|
+
at.lastIndex = i;
|
|
1453
|
+
if (at.test(text)) return i;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return text.length;
|
|
1457
|
+
}
|
|
1458
|
+
var PARAM_DEFAULT = /(?<![=!<>+\-*/%&|^?])=(?![=>])/uy;
|
|
1459
|
+
var DECLARATOR_END = new RegExp(String.raw`${PARAM_DEFAULT.source}|(?<![\p{ID_Continue}$])(?:of|in)(?![\p{ID_Continue}$])`, "uy");
|
|
1460
|
+
var CLOSURE_CANDIDATE = new RegExp(
|
|
1461
|
+
// A `.` before a name is a member access — unless it ends a spread `...`.
|
|
1462
|
+
String.raw`(^|\.\.\.|[^\p{ID_Continue}$\u200C\u200D.#])([\p{ID_Start}_][\p{ID_Continue}\u200C\u200D]*)(?!${ID_CHAR})`,
|
|
1463
|
+
"gu"
|
|
1464
|
+
);
|
|
1465
|
+
function assertNoClosure(code, source) {
|
|
1466
|
+
const mask = maskNonCode(decodeIdentifierEscapes(code));
|
|
1467
|
+
const declared = closureDeclarations(mask);
|
|
1468
|
+
const scoped = globalThis[FREE_READS];
|
|
1469
|
+
const free = typeof scoped === "function" ? new Set(scoped(mask)) : void 0;
|
|
1470
|
+
for (const m of mask.matchAll(CLOSURE_CANDIDATE)) {
|
|
1471
|
+
const name = m[2];
|
|
1472
|
+
if (name.startsWith("__") || !isFreeIdentifier(name)) continue;
|
|
1473
|
+
if (free !== void 0 ? !free.has(name) : declared.has(name)) continue;
|
|
1474
|
+
const start = m.index + (m[1] ?? "").length;
|
|
1475
|
+
const before = mask.slice(0, start).trimEnd();
|
|
1476
|
+
const after = mask.slice(start + name.length).trimStart();
|
|
1477
|
+
if (/\btypeof$/.test(before)) continue;
|
|
1478
|
+
if (after.startsWith(":") && !after.startsWith("::") && /[{,;]$|^$/.test(before)) continue;
|
|
1479
|
+
if (/^:\s*(?:(?:for|while|do|switch)\b|\{)/.test(after) && !/[-+*/%&|^!~?<>=,.([]$/.test(before)) continue;
|
|
1480
|
+
if (/\b(break|continue)$/.test(before)) continue;
|
|
1481
|
+
if (after.startsWith("(") && !before.endsWith(")") && mask.slice(closingParen(mask, start)).trimStart().startsWith("{")) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
if (/^=(?![=>])/.test(after) && /[{};]$|^$/.test(before)) continue;
|
|
1485
|
+
if (isClassMemberName(mask, start, name.length)) continue;
|
|
1486
|
+
throw new Error(
|
|
1487
|
+
`${source}: the lambda body reads \`${name}\`, which it does not declare \u2014 it comes from the scope the function was WRITTEN in, and only the body's text reaches the engine. There \`${name}\` is not defined: 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. Inline the value as a literal, pass it through the stack (\`$var.${name}\` after a \`s.set_var\`, or an input as \`$input.${name}\`), or carry it in with \`lam.fn(body, { capture: { ${name} } })\`.`
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
function isClassMemberName(mask, start, length) {
|
|
1492
|
+
let depth = 0;
|
|
1493
|
+
let open = -1;
|
|
1494
|
+
for (let i = start - 1; i >= 0; i--) {
|
|
1495
|
+
const ch = mask[i];
|
|
1496
|
+
if (ch === ")" || ch === "]" || ch === "}") depth++;
|
|
1497
|
+
else if (ch === "(" || ch === "[" || ch === "{") {
|
|
1498
|
+
if (depth === 0) {
|
|
1499
|
+
open = i;
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
depth--;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
if (open === -1 || mask[open] !== "{") return false;
|
|
1506
|
+
const header = new RegExp(String.raw`(?<!${ID_CHAR}|\.)class(?:\s+${IDENT})?(?:\s+extends(?!${ID_CHAR})[^{};]*)?\s*$`, "u");
|
|
1507
|
+
if (!header.test(mask.slice(Math.max(0, open - 256), open))) return false;
|
|
1508
|
+
const lead = mask.slice(open + 1, start);
|
|
1509
|
+
const before = lead.trimEnd();
|
|
1510
|
+
const memberStart = before === "" || /[{};]$/.test(before) || new RegExp(String.raw`(?<!${ID_CHAR}|\.)(?:static|accessor|async|get|set)$`, "u").test(before) || // A line break ends a field whose initializer does not continue past it.
|
|
1511
|
+
/\n\s*$/.test(lead) && !/[-+*/%&|^!~?:<>=,.([]$/.test(before);
|
|
1512
|
+
if (!memberStart) return false;
|
|
1513
|
+
const after = mask.slice(start + length);
|
|
1514
|
+
return /^\s*(?:=(?![=>])|;|\}|\(|$)/.test(after) || /^[ \t]*\n/.test(after) || new RegExp(String.raw`^\s+${IDENT}`, "u").test(after);
|
|
1515
|
+
}
|
|
1516
|
+
function assertNoBundlerHelpers(mask, source) {
|
|
1517
|
+
const declared = declaredLocals(mask, new RegExp(String.raw`__${ID_CHAR}*`, "gu"));
|
|
1518
|
+
for (const token of tokens(mask, new RegExp(String.raw`(^|\.\.\.|[^\p{ID_Continue}$\u200C\u200D.])(__\p{ID_Start}${ID_CHAR}*)`, "gu"), true)) {
|
|
1519
|
+
if (declared.has(token) || HELPER_LOOKALIKES.has(token)) continue;
|
|
1520
|
+
const wrote = Object.hasOwn(BUNDLER_HELPERS, token) ? BUNDLER_HELPERS[token] : void 0;
|
|
1521
|
+
throw new Error(
|
|
1522
|
+
`${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.`
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
var UNREPRESENTABLE_CLASSES = [
|
|
1527
|
+
{
|
|
1528
|
+
ctor: Date,
|
|
1529
|
+
name: "Date",
|
|
1530
|
+
fix: "capture the epoch number (`d.getTime()`) or the ISO string and rebuild it in the body (`new Date(d)`)"
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
ctor: Map,
|
|
1534
|
+
name: "Map",
|
|
1535
|
+
fix: "capture a plain object (`Object.fromEntries(m)`) \u2014 or the entries array, and rebuild with `new Map(entries)` in the body"
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
ctor: Set,
|
|
1539
|
+
name: "Set",
|
|
1540
|
+
fix: "capture an array (`[...s]`) \u2014 and rebuild with `new Set(arr)` in the body if you need set semantics"
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
ctor: RegExp,
|
|
1544
|
+
name: "RegExp",
|
|
1545
|
+
fix: "capture the source and flags as strings and rebuild with `new RegExp(src, flags)` in the body"
|
|
1546
|
+
}
|
|
1547
|
+
];
|
|
1548
|
+
function assertJsonCapture(name, value, source) {
|
|
1549
|
+
if (value === void 0) {
|
|
1550
|
+
throw new Error(
|
|
1551
|
+
`${source}: capture \`${name}\` is undefined, 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).`
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
assertJsonValue(name, value, source, /* @__PURE__ */ new Map());
|
|
1555
|
+
}
|
|
1556
|
+
function assertJsonValue(path, value, source, ancestors) {
|
|
1557
|
+
const refuse = (what, becomes, fix = "Capture the plain JSON form and rebuild it in the body.") => {
|
|
1558
|
+
throw new Error(
|
|
1559
|
+
`${source}: capture \`${path}\` is ${what}, which cannot cross into the engine \u2014 the body is sent as TEXT, and JSON-encoding it yields ${becomes}. The body would read a different value than the one TypeScript gave it, and the engine returns what it computes from that with HTTP 200 \u2014 a wrong value rather than an error. ${fix}`
|
|
1560
|
+
);
|
|
1561
|
+
};
|
|
1562
|
+
const type = typeof value;
|
|
1563
|
+
if (type === "function" || type === "symbol" || type === "bigint") {
|
|
1564
|
+
refuse(`${article(type)} ${type}`, type === "bigint" ? "an error" : "nothing", "Capture JSON data; for behaviour, inline it in the body.");
|
|
1565
|
+
}
|
|
1566
|
+
if (type === "number" && !Number.isFinite(value)) {
|
|
1567
|
+
refuse(`\`${String(value)}\``, "`null`", "Capture it as a string (`String(n)`) and rebuild with `Number(s)` in the body.");
|
|
1568
|
+
}
|
|
1569
|
+
if (value === null || type !== "object") return;
|
|
1570
|
+
const object = value;
|
|
1571
|
+
for (const entry of UNREPRESENTABLE_CLASSES) {
|
|
1572
|
+
if (!(object instanceof entry.ctor)) continue;
|
|
1573
|
+
refuse(
|
|
1574
|
+
`${article(entry.name)} ${entry.name}`,
|
|
1575
|
+
entry.name === "Date" ? "a string" : "`{}`",
|
|
1576
|
+
`Instead, ${entry.fix}.`
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
if (ArrayBuffer.isView(object) || object instanceof ArrayBuffer) {
|
|
1580
|
+
refuse(`a binary buffer (\`${object.constructor.name}\`)`, "an index-keyed object or `{}`", "Capture a plain array (`Array.from(bytes)`) and rebuild it in the body.");
|
|
1581
|
+
}
|
|
1582
|
+
const cycleAt = ancestors.get(object);
|
|
1583
|
+
if (cycleAt !== void 0) {
|
|
1584
|
+
throw new Error(
|
|
1585
|
+
`${source}: capture \`${path}\` is \`${cycleAt}\` itself \u2014 a cycle, which JSON cannot encode. Capture an acyclic copy and rebuild the link in the body.`
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
ancestors.set(object, path);
|
|
1589
|
+
if (Array.isArray(object)) {
|
|
1590
|
+
for (let i = 0; i < object.length; i++) {
|
|
1591
|
+
if (!(i in object)) refuse(`a sparse array (no element at [${i}])`, "`null` in every hole", "Fill the holes explicitly.");
|
|
1592
|
+
if (object[i] === void 0) refuse(`an array holding \`undefined\` at [${i}]`, "`null` there", "Use `null` explicitly, or leave the element out.");
|
|
1593
|
+
assertJsonValue(`${path}[${i}]`, object[i], source, ancestors);
|
|
1594
|
+
}
|
|
1595
|
+
ancestors.delete(object);
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const proto = Object.getPrototypeOf(object);
|
|
1599
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
1600
|
+
const name = object.constructor?.name;
|
|
1601
|
+
refuse(
|
|
1602
|
+
`an instance of \`${typeof name === "string" && name !== "" ? name : "a class"}\``,
|
|
1603
|
+
"`{}` or whatever its `toJSON` returns",
|
|
1604
|
+
"Capture a plain object of its JSON data and rebuild the instance in the body."
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
for (const [key, item] of Object.entries(object)) {
|
|
1608
|
+
if (key === "toJSON" && typeof item === "function") refuse("an object with a `toJSON` method", "what `toJSON` returns", "Capture that value itself.");
|
|
1609
|
+
if (item === void 0) continue;
|
|
1610
|
+
assertJsonValue(`${path}.${key}`, item, source, ancestors);
|
|
1611
|
+
}
|
|
1612
|
+
ancestors.delete(object);
|
|
1613
|
+
}
|
|
1614
|
+
function capturePrelude(capture, source) {
|
|
1615
|
+
if (capture === void 0) return "";
|
|
1616
|
+
const lines = [];
|
|
1617
|
+
for (const [name, value] of Object.entries(capture)) {
|
|
1618
|
+
if (!new RegExp(`^${IDENT}$`, "u").test(name)) {
|
|
1619
|
+
throw new Error(`${source}: capture key ${JSON.stringify(name)} is not a valid JavaScript identifier.`);
|
|
1620
|
+
}
|
|
1621
|
+
assertJsonCapture(name, value, source);
|
|
1622
|
+
lines.push(`const ${name} = ${JSON.stringify(value)};`);
|
|
1623
|
+
}
|
|
1624
|
+
return lines.length ? lines.join("\n") + "\n" : "";
|
|
1625
|
+
}
|
|
1626
|
+
function assertCaptureNotRenamed(body, capture, source) {
|
|
1627
|
+
if (capture === void 0) return;
|
|
1628
|
+
const keys = Object.keys(capture);
|
|
1629
|
+
if (keys.length === 0) return;
|
|
1630
|
+
const mask = maskNonCode(decodeIdentifierEscapes(body));
|
|
1631
|
+
const declared = declaredLocals(mask, new RegExp(IDENT, "gu"));
|
|
1632
|
+
for (const token of tokens(mask, new RegExp(String.raw`(^|\.\.\.|[^\p{ID_Continue}$\u200C\u200D.])(${IDENT}[0-9])(?!${ID_CHAR})`, "gu"), true)) {
|
|
1633
|
+
if (declared.has(token)) continue;
|
|
1634
|
+
const key = keys.find((k) => new RegExp(`^${k.replace(/\$/g, "\\$")}[0-9]+$`).test(token));
|
|
1635
|
+
if (key === void 0) continue;
|
|
1636
|
+
throw new Error(
|
|
1637
|
+
`${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(...)\`.`
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
function assertCaptureSupplied(reads, capture, source) {
|
|
1642
|
+
const missing = reads.find((name) => capture === void 0 || !Object.hasOwn(capture, name));
|
|
1643
|
+
if (missing === void 0) return;
|
|
1644
|
+
const how = capture === void 0 ? `there is no \`capture\` here${source.startsWith(`${PREFIX}.`) ? "" : " (an inline body takes none)"}` : `\`capture\` has no \`${missing}\` key`;
|
|
1645
|
+
const file = /^lam\.file\((.*)\)$/s.exec(source)?.[1];
|
|
1646
|
+
const remedy = file === void 0 ? `lam.fn(body, { capture: { ${missing} } })` : `lam.file(${JSON.stringify(file)}, { capture: { ${missing} } })`;
|
|
1647
|
+
throw new Error(
|
|
1648
|
+
`${source}: the body reads \`${missing}\` from its second parameter, but ${how}, so nothing declares it at runtime \u2014 the body reads any global of that name instead, or throws. Supply it with \`${remedy}\`, or declare it inside the body.`
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
function lambdaValue(body, opts, source, fromFunction = false, captureReads = []) {
|
|
1652
|
+
const code = capturePrelude(opts?.capture, source) + body;
|
|
1653
|
+
assertCaptureSupplied(captureReads, opts?.capture, source);
|
|
1654
|
+
assertCaptureNotRenamed(body, opts?.capture, source);
|
|
1655
|
+
if (fromFunction) assertNoClosure(code, source);
|
|
1656
|
+
if (opts?.surface !== void 0) {
|
|
1657
|
+
if (!Object.hasOwn(LAMBDA_BINDINGS, opts.surface)) {
|
|
1658
|
+
throw new Error(
|
|
1659
|
+
`${source}: surface ${JSON.stringify(opts.surface)} is not a lambda surface \u2014 use one of ${Object.keys(LAMBDA_BINDINGS).map((k) => JSON.stringify(k)).join(", ")}.`
|
|
1660
|
+
);
|
|
1661
|
+
}
|
|
1662
|
+
assertLambdaBody(code, opts.surface, source);
|
|
1663
|
+
}
|
|
1664
|
+
return c.text(code);
|
|
1665
|
+
}
|
|
1666
|
+
function fn(body, opts) {
|
|
1667
|
+
const extracted = extractFunction(String(body), `${PREFIX}.fn`);
|
|
1668
|
+
return lambdaValue(extracted.body, opts, `${PREFIX}.fn`, true, extracted.captureReads);
|
|
1669
|
+
}
|
|
1670
|
+
function raw(code, opts) {
|
|
1671
|
+
return lambdaValue(code, opts, `${PREFIX}.raw`);
|
|
1672
|
+
}
|
|
1673
|
+
function fileOnNodeEntryOnly() {
|
|
1674
|
+
throw new Error(
|
|
1675
|
+
`${PREFIX}.file: this entry has no filesystem. It ships on the Node entry only \u2014 \`import { lam } from "@xano/sdk/node"\` (that \`lam\` carries \`fn\` and \`raw\` too). In a browser bundle, use \`${PREFIX}.raw(code, { surface })\` with the body as text.`
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
var lam = Object.assign({ fn, raw }, { file: fileOnNodeEntryOnly });
|
|
1679
|
+
|
|
1680
|
+
export {
|
|
1681
|
+
TAGS,
|
|
1682
|
+
IDENT,
|
|
1683
|
+
decodeIdentifier,
|
|
1684
|
+
decodeIdentifierEscapes,
|
|
1685
|
+
LAMBDA_BINDINGS,
|
|
1686
|
+
LAMBDA_GLOBALS,
|
|
1687
|
+
LAMBDA_MODULE_GLOBALS,
|
|
1688
|
+
LAMBDA_CODE_FILTERS,
|
|
1689
|
+
LAMBDA_STATEMENTS,
|
|
1690
|
+
coerceLambdaFields,
|
|
1691
|
+
assertLambdaStatement,
|
|
1692
|
+
toLambdaValue,
|
|
1693
|
+
extractFunction,
|
|
1694
|
+
readFunction,
|
|
1695
|
+
assertLambdaBody,
|
|
1696
|
+
collectLambdaBodyProblems,
|
|
1697
|
+
installLambdaFreeReads,
|
|
1698
|
+
installLambdaSyntaxCheck,
|
|
1699
|
+
lambdaSyntaxError,
|
|
1700
|
+
lambdaValue,
|
|
1701
|
+
lam,
|
|
1702
|
+
isTaggedValue,
|
|
1703
|
+
objTaggedList,
|
|
1704
|
+
toPlainValue,
|
|
1705
|
+
barePatternRefusal,
|
|
1706
|
+
reversedOperandRefusal,
|
|
1707
|
+
reversedRegexOperands,
|
|
1708
|
+
lossyIntegerProblem,
|
|
1709
|
+
c,
|
|
1710
|
+
ref,
|
|
1711
|
+
inp,
|
|
1712
|
+
col,
|
|
1713
|
+
auth,
|
|
1714
|
+
resp,
|
|
1715
|
+
caught,
|
|
1716
|
+
isToolsetPath,
|
|
1717
|
+
toolset,
|
|
1718
|
+
env,
|
|
1719
|
+
setting,
|
|
1720
|
+
sys,
|
|
1721
|
+
out,
|
|
1722
|
+
filter,
|
|
1723
|
+
withFilters
|
|
1724
|
+
};
|
|
1725
|
+
//# sourceMappingURL=chunk-OXA6PSZT.js.map
|