@nodegx/mcp 0.2.2 → 0.2.3
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/README.md +10 -0
- package/dist/backend/cli.js +317 -233
- package/dist/cloud-bundle.cjs +898 -18
- package/dist/kit-extract.cjs +343 -210
- package/dist/kit-extract.inputs.json +1 -0
- package/dist/node-catalog.json +45 -1
- package/dist/noodl-mcp.cjs +2562 -462
- package/dist/package.json +1 -1
- package/dist/render-harness/data/node-catalog-enriched.json +45 -1
- package/dist/render-harness/data/node-catalog.json +45 -1
- package/dist/render-harness/drive-steps.js +5 -0
- package/dist/render-harness/measure-from-disk.js +5 -0
- package/dist/src/external/viewer/global.d.ts.keep +13 -0
- package/dist/src/external/viewer/noodl.viewer.js +1 -1
- package/package.json +1 -1
package/dist/noodl-mcp.cjs
CHANGED
|
@@ -47137,6 +47137,18 @@ var require_node_catalog_enriched = __commonJS({
|
|
|
47137
47137
|
isSignal: false,
|
|
47138
47138
|
description: "Object path inside the bucket, such as avatars/me.png. Supabase only. Leave blank to use the file's own name"
|
|
47139
47139
|
},
|
|
47140
|
+
{
|
|
47141
|
+
name: "photoQuality",
|
|
47142
|
+
displayName: "Photo Quality",
|
|
47143
|
+
group: "Shrink Photos",
|
|
47144
|
+
plug: "input",
|
|
47145
|
+
type: {
|
|
47146
|
+
name: "number"
|
|
47147
|
+
},
|
|
47148
|
+
isSignal: false,
|
|
47149
|
+
default: 80,
|
|
47150
|
+
description: "How much detail a shrunk JPEG or WebP keeps, from 1 to 100; PNG keeps every pixel and its transparency, GIF and SVG are never shrunk"
|
|
47151
|
+
},
|
|
47140
47152
|
{
|
|
47141
47153
|
name: "private",
|
|
47142
47154
|
displayName: "Private",
|
|
@@ -47149,6 +47161,17 @@ var require_node_catalog_enriched = __commonJS({
|
|
|
47149
47161
|
default: false,
|
|
47150
47162
|
description: "Restricts the stored file to whoever uploaded it, so reading it later needs a Sign File URL node"
|
|
47151
47163
|
},
|
|
47164
|
+
{
|
|
47165
|
+
name: "readers",
|
|
47166
|
+
displayName: "Readers",
|
|
47167
|
+
group: "General",
|
|
47168
|
+
plug: "input",
|
|
47169
|
+
type: {
|
|
47170
|
+
name: "string"
|
|
47171
|
+
},
|
|
47172
|
+
isSignal: false,
|
|
47173
|
+
description: "Roles that may read the file as well as whoever uploaded it, such as role:member, separated by commas; setting it makes the file private. NodeGX backends only"
|
|
47174
|
+
},
|
|
47152
47175
|
{
|
|
47153
47176
|
name: "recordId",
|
|
47154
47177
|
displayName: "Record ID (PocketBase)",
|
|
@@ -47160,6 +47183,18 @@ var require_node_catalog_enriched = __commonJS({
|
|
|
47160
47183
|
isSignal: false,
|
|
47161
47184
|
description: "Existing record to attach the file to. PocketBase only. Leave blank to create a new record as part of the upload"
|
|
47162
47185
|
},
|
|
47186
|
+
{
|
|
47187
|
+
name: "shrinkTo",
|
|
47188
|
+
displayName: "Shrink To",
|
|
47189
|
+
group: "Shrink Photos",
|
|
47190
|
+
plug: "input",
|
|
47191
|
+
type: {
|
|
47192
|
+
name: "number"
|
|
47193
|
+
},
|
|
47194
|
+
isSignal: false,
|
|
47195
|
+
default: 0,
|
|
47196
|
+
description: "Makes a photo smaller in the browser before sending it, so its longer side is at most this many pixels; 0 sends every file as picked"
|
|
47197
|
+
},
|
|
47163
47198
|
{
|
|
47164
47199
|
name: "upload",
|
|
47165
47200
|
displayName: "Upload",
|
|
@@ -47302,6 +47337,15 @@ var require_node_catalog_enriched = __commonJS({
|
|
|
47302
47337
|
private: [
|
|
47303
47338
|
"always: held as handed; read when the next Upload goes"
|
|
47304
47339
|
],
|
|
47340
|
+
readers: [
|
|
47341
|
+
"always: held as handed; read when the next Upload goes \u2014 set, those roles may read the file as well as whoever uploaded it, and the file is private"
|
|
47342
|
+
],
|
|
47343
|
+
shrinkTo: [
|
|
47344
|
+
"always: held as handed; read when the next Upload goes \u2014 above 0, a photo (not a GIF or an SVG) is drawn smaller in the browser first, its longer side at most this many pixels, and the smaller file is what goes; it goes as picked when it is already that small, when the smaller one would weigh more, or when the browser cannot read it"
|
|
47345
|
+
],
|
|
47346
|
+
photoQuality: [
|
|
47347
|
+
"always: held as handed; read when the next Upload goes, and only when a photo is shrunk \u2014 1 to 100 for a JPEG or WebP, anything else is 80; a PNG keeps every pixel"
|
|
47348
|
+
],
|
|
47305
47349
|
bucket: [
|
|
47306
47350
|
"always: held as handed; read when the next Upload goes \u2014 set, the file goes to this bucket at Path (or under the file's own name)"
|
|
47307
47351
|
],
|
|
@@ -47318,7 +47362,7 @@ var require_node_catalog_enriched = __commonJS({
|
|
|
47318
47362
|
"always: held as handed; read when the next Upload goes, and only with Collection \u2014 a half-filled group is no location at all"
|
|
47319
47363
|
],
|
|
47320
47364
|
upload: [
|
|
47321
|
-
"always: its own upload at the frame's end \u2014 two presses are two uploads \u2014 with File, Private and the File Location held then: no File, or a Backend the project does not have, is Error, Error Status Code 0 and Failure at once; otherwise the backend answers with Cloud File then Done, or Error, Error Status Code and Failure"
|
|
47365
|
+
"always: its own upload at the frame's end \u2014 two presses are two uploads \u2014 with File, Private, Readers and the File Location held then: no File, or a Backend the project does not have, is Error, Error Status Code 0 and Failure at once; otherwise the backend answers with Cloud File then Done, or Error, Error Status Code and Failure"
|
|
47322
47366
|
]
|
|
47323
47367
|
},
|
|
47324
47368
|
enrichment: {
|
|
@@ -105466,7 +105510,7 @@ var require_package = __commonJS({
|
|
|
105466
105510
|
"package.json"(exports2, module2) {
|
|
105467
105511
|
module2.exports = {
|
|
105468
105512
|
name: "@nodegx/mcp",
|
|
105469
|
-
version: "0.2.
|
|
105513
|
+
version: "0.2.3",
|
|
105470
105514
|
description: "Model Context Protocol server exposing NodeGX (OpenNoodl) v2 projects to external AI agents.",
|
|
105471
105515
|
license: "GPL-3.0-only",
|
|
105472
105516
|
homepage: "https://nodegx.io",
|
|
@@ -156645,6 +156689,18 @@ var node_catalog_default = {
|
|
|
156645
156689
|
isSignal: false,
|
|
156646
156690
|
description: "Object path inside the bucket, such as avatars/me.png. Supabase only. Leave blank to use the file's own name"
|
|
156647
156691
|
},
|
|
156692
|
+
{
|
|
156693
|
+
name: "photoQuality",
|
|
156694
|
+
displayName: "Photo Quality",
|
|
156695
|
+
group: "Shrink Photos",
|
|
156696
|
+
plug: "input",
|
|
156697
|
+
type: {
|
|
156698
|
+
name: "number"
|
|
156699
|
+
},
|
|
156700
|
+
isSignal: false,
|
|
156701
|
+
default: 80,
|
|
156702
|
+
description: "How much detail a shrunk JPEG or WebP keeps, from 1 to 100; PNG keeps every pixel and its transparency, GIF and SVG are never shrunk"
|
|
156703
|
+
},
|
|
156648
156704
|
{
|
|
156649
156705
|
name: "private",
|
|
156650
156706
|
displayName: "Private",
|
|
@@ -156657,6 +156713,17 @@ var node_catalog_default = {
|
|
|
156657
156713
|
default: false,
|
|
156658
156714
|
description: "Restricts the stored file to whoever uploaded it, so reading it later needs a Sign File URL node"
|
|
156659
156715
|
},
|
|
156716
|
+
{
|
|
156717
|
+
name: "readers",
|
|
156718
|
+
displayName: "Readers",
|
|
156719
|
+
group: "General",
|
|
156720
|
+
plug: "input",
|
|
156721
|
+
type: {
|
|
156722
|
+
name: "string"
|
|
156723
|
+
},
|
|
156724
|
+
isSignal: false,
|
|
156725
|
+
description: "Roles that may read the file as well as whoever uploaded it, such as role:member, separated by commas; setting it makes the file private. NodeGX backends only"
|
|
156726
|
+
},
|
|
156660
156727
|
{
|
|
156661
156728
|
name: "recordId",
|
|
156662
156729
|
displayName: "Record ID (PocketBase)",
|
|
@@ -156668,6 +156735,18 @@ var node_catalog_default = {
|
|
|
156668
156735
|
isSignal: false,
|
|
156669
156736
|
description: "Existing record to attach the file to. PocketBase only. Leave blank to create a new record as part of the upload"
|
|
156670
156737
|
},
|
|
156738
|
+
{
|
|
156739
|
+
name: "shrinkTo",
|
|
156740
|
+
displayName: "Shrink To",
|
|
156741
|
+
group: "Shrink Photos",
|
|
156742
|
+
plug: "input",
|
|
156743
|
+
type: {
|
|
156744
|
+
name: "number"
|
|
156745
|
+
},
|
|
156746
|
+
isSignal: false,
|
|
156747
|
+
default: 0,
|
|
156748
|
+
description: "Makes a photo smaller in the browser before sending it, so its longer side is at most this many pixels; 0 sends every file as picked"
|
|
156749
|
+
},
|
|
156671
156750
|
{
|
|
156672
156751
|
name: "upload",
|
|
156673
156752
|
displayName: "Upload",
|
|
@@ -156810,6 +156889,15 @@ var node_catalog_default = {
|
|
|
156810
156889
|
private: [
|
|
156811
156890
|
"always: held as handed; read when the next Upload goes"
|
|
156812
156891
|
],
|
|
156892
|
+
readers: [
|
|
156893
|
+
"always: held as handed; read when the next Upload goes \u2014 set, those roles may read the file as well as whoever uploaded it, and the file is private"
|
|
156894
|
+
],
|
|
156895
|
+
shrinkTo: [
|
|
156896
|
+
"always: held as handed; read when the next Upload goes \u2014 above 0, a photo (not a GIF or an SVG) is drawn smaller in the browser first, its longer side at most this many pixels, and the smaller file is what goes; it goes as picked when it is already that small, when the smaller one would weigh more, or when the browser cannot read it"
|
|
156897
|
+
],
|
|
156898
|
+
photoQuality: [
|
|
156899
|
+
"always: held as handed; read when the next Upload goes, and only when a photo is shrunk \u2014 1 to 100 for a JPEG or WebP, anything else is 80; a PNG keeps every pixel"
|
|
156900
|
+
],
|
|
156813
156901
|
bucket: [
|
|
156814
156902
|
"always: held as handed; read when the next Upload goes \u2014 set, the file goes to this bucket at Path (or under the file's own name)"
|
|
156815
156903
|
],
|
|
@@ -156826,7 +156914,7 @@ var node_catalog_default = {
|
|
|
156826
156914
|
"always: held as handed; read when the next Upload goes, and only with Collection \u2014 a half-filled group is no location at all"
|
|
156827
156915
|
],
|
|
156828
156916
|
upload: [
|
|
156829
|
-
"always: its own upload at the frame's end \u2014 two presses are two uploads \u2014 with File, Private and the File Location held then: no File, or a Backend the project does not have, is Error, Error Status Code 0 and Failure at once; otherwise the backend answers with Cloud File then Done, or Error, Error Status Code and Failure"
|
|
156917
|
+
"always: its own upload at the frame's end \u2014 two presses are two uploads \u2014 with File, Private, Readers and the File Location held then: no File, or a Backend the project does not have, is Error, Error Status Code 0 and Failure at once; otherwise the backend answers with Cloud File then Done, or Error, Error Status Code and Failure"
|
|
156830
156918
|
]
|
|
156831
156919
|
}
|
|
156832
156920
|
},
|
|
@@ -200422,8 +200510,8 @@ var nodegxDescriptor = {
|
|
|
200422
200510
|
// The header works. What it cannot do is make a file private on behalf of
|
|
200423
200511
|
// nobody, and an author reading "supported" would not expect that.
|
|
200424
200512
|
"files.private": degraded(
|
|
200425
|
-
"A private upload is locked to the person who made it, so a file uploaded by anything other than a signed-in user \u2014 an admin tool, a cloud function \u2014 ends up private with no owner, and stays readable by everyone.",
|
|
200426
|
-
"BCN-007 live, security enforced: uploaded as a signed-in user, the plain URL is 403 for anonymous AND for a second signed-in user. Uploaded with the master key, the identical request with X-NodeGX-File-Private: true serves 200 to anonymous \u2014 files.ts::upload sets owner=null for a non-user principal"
|
|
200513
|
+
"A private upload is locked to the person who made it, so a file uploaded by anything other than a signed-in user \u2014 an admin tool, a cloud function \u2014 ends up private with no owner, and stays readable by everyone. Upload File's Readers names roles that may read it too, such as role:member; a file with Readers is never readable by everyone.",
|
|
200514
|
+
"BCN-007 live, security enforced: uploaded as a signed-in user, the plain URL is 403 for anonymous AND for a second signed-in user. Uploaded with the master key, the identical request with X-NodeGX-File-Private: true serves 200 to anonymous \u2014 files.ts::upload sets owner=null for a non-user principal. P121-TWO001-MEMBERSPHOTO: X-NodeGX-File-Readers adds read-only role entries to the file's ACL; uploaded with the master key and role:crew, anonymous and a user without the role are 403 (files-http.test.ts)"
|
|
200427
200515
|
),
|
|
200428
200516
|
"files.progress": supported("XHR upload path reports progress"),
|
|
200429
200517
|
"auth.password": supported("nodegx-backend/src/server/users.ts"),
|
|
@@ -219762,13 +219850,25 @@ var UploadFile = defineNode({
|
|
|
219762
219850
|
// Error always a sentence; v1 read `code || status || 0` and put a message-less envelope on Error.
|
|
219763
219851
|
// v3 (P109 ISL-030, row C52 ruled "Keep the real name"): the Cloud File keeps the name it was uploaded as (the backend's
|
|
219764
219852
|
// `filename`, else the sent file's name); v2's dropped `filename` and knew only the stored name.
|
|
219765
|
-
|
|
219853
|
+
// v4 (P121 TWO-001 / TWO-008): three inputs, Readers (roles that may read a private file), Shrink To and Photo Quality.
|
|
219854
|
+
version: 4,
|
|
219855
|
+
changes: [
|
|
219856
|
+
{
|
|
219857
|
+
version: 4,
|
|
219858
|
+
says: "Three new inputs: Readers names the roles that may read a private file as well as whoever uploaded it, and Shrink To and Photo Quality make a photo smaller in the browser before it is sent. Left empty, each sends the file as v3 did",
|
|
219859
|
+
row: "P121-TWO001-MEMBERSPHOTO, P121-TWO008-UPLOADSHRINK",
|
|
219860
|
+
migration: { none: "Each new input is empty in an app built on v3, and an empty one changes nothing: the upload is the same request" }
|
|
219861
|
+
}
|
|
219862
|
+
],
|
|
219766
219863
|
source: "packages/noodl-viewer-react/src/nodes/std-library/uploadfile.ts; noodl-runtime api/cloudstore.js, api/cloudfile.ts",
|
|
219767
219864
|
needs: ["backend"],
|
|
219768
219865
|
worldPool: { backends: [...pool2("uploadFile", FILE_REFS[0]), ...pool2("uploadFile", FILE_REFS[1]).slice(0, 1), ...pool2("uploadFile", FILE_REFS[2]).slice(2, 3)] },
|
|
219769
219866
|
state: {
|
|
219770
219867
|
file: void 0,
|
|
219771
219868
|
private: void 0,
|
|
219869
|
+
readers: void 0,
|
|
219870
|
+
shrinkTo: void 0,
|
|
219871
|
+
photoQuality: void 0,
|
|
219772
219872
|
bucket: void 0,
|
|
219773
219873
|
path: void 0,
|
|
219774
219874
|
collection: void 0,
|
|
@@ -219800,6 +219900,35 @@ var UploadFile = defineNode({
|
|
|
219800
219900
|
description: "Restricts the stored file to whoever uploaded it, so reading it later needs a Sign File URL node",
|
|
219801
219901
|
examples: [true, false]
|
|
219802
219902
|
},
|
|
219903
|
+
// P121-TWO001-MEMBERSPHOTO — handed to the store with Private; the backend reads it (files.ts)
|
|
219904
|
+
readers: {
|
|
219905
|
+
type: "string",
|
|
219906
|
+
coerce: "none",
|
|
219907
|
+
displayName: "Readers",
|
|
219908
|
+
group: "General",
|
|
219909
|
+
description: "Roles that may read the file as well as whoever uploaded it, such as role:member, separated by commas; setting it makes the file private. NodeGX backends only",
|
|
219910
|
+
examples: ["role:member, role:admin", ""]
|
|
219911
|
+
},
|
|
219912
|
+
// P121-TWO008-UPLOADSHRINK — the Shrink Photos group. The canvas step (shrinkphoto.ts) is the browser's: this world
|
|
219913
|
+
// has no decoder, so a photo goes as it was picked, which is what the runtime does when the browser cannot read it.
|
|
219914
|
+
shrinkTo: {
|
|
219915
|
+
type: "number",
|
|
219916
|
+
coerce: "none",
|
|
219917
|
+
default: 0,
|
|
219918
|
+
displayName: "Shrink To",
|
|
219919
|
+
group: "Shrink Photos",
|
|
219920
|
+
description: "Makes a photo smaller in the browser before sending it, so its longer side is at most this many pixels; 0 sends every file as picked",
|
|
219921
|
+
examples: [0, 1600]
|
|
219922
|
+
},
|
|
219923
|
+
photoQuality: {
|
|
219924
|
+
type: "number",
|
|
219925
|
+
coerce: "none",
|
|
219926
|
+
default: 80,
|
|
219927
|
+
displayName: "Photo Quality",
|
|
219928
|
+
group: "Shrink Photos",
|
|
219929
|
+
description: "How much detail a shrunk JPEG or WebP keeps, from 1 to 100; PNG keeps every pixel and its transparency, GIF and SVG are never shrunk",
|
|
219930
|
+
examples: [80, 50]
|
|
219931
|
+
},
|
|
219803
219932
|
// :111-158
|
|
219804
219933
|
bucket: {
|
|
219805
219934
|
...stringInput("Bucket (Supabase)", "Supabase Storage bucket to upload into. Supabase only; there is no default, and a bucket that does not exist fails with an error that does not say so", ["avatars", ""]),
|
|
@@ -219839,6 +219968,21 @@ var UploadFile = defineNode({
|
|
|
219839
219968
|
{
|
|
219840
219969
|
file: (_s, v) => ({ says: "always: held as handed; read when the next Upload goes \u2014 an upload already running keeps the file it was sent with", set: { file: v }, send: [] }),
|
|
219841
219970
|
private: (_s, v) => ({ says: "always: held as handed; read when the next Upload goes", set: { private: v }, send: [] }),
|
|
219971
|
+
readers: (_s, v) => ({
|
|
219972
|
+
says: "always: held as handed; read when the next Upload goes \u2014 set, those roles may read the file as well as whoever uploaded it, and the file is private",
|
|
219973
|
+
set: { readers: v },
|
|
219974
|
+
send: []
|
|
219975
|
+
}),
|
|
219976
|
+
shrinkTo: (_s, v) => ({
|
|
219977
|
+
says: "always: held as handed; read when the next Upload goes \u2014 above 0, a photo (not a GIF or an SVG) is drawn smaller in the browser first, its longer side at most this many pixels, and the smaller file is what goes; it goes as picked when it is already that small, when the smaller one would weigh more, or when the browser cannot read it",
|
|
219978
|
+
set: { shrinkTo: v },
|
|
219979
|
+
send: []
|
|
219980
|
+
}),
|
|
219981
|
+
photoQuality: (_s, v) => ({
|
|
219982
|
+
says: "always: held as handed; read when the next Upload goes, and only when a photo is shrunk \u2014 1 to 100 for a JPEG or WebP, anything else is 80; a PNG keeps every pixel",
|
|
219983
|
+
set: { photoQuality: v },
|
|
219984
|
+
send: []
|
|
219985
|
+
}),
|
|
219842
219986
|
bucket: (_s, v) => ({ says: "always: held as handed; read when the next Upload goes \u2014 set, the file goes to this bucket at Path (or under the file's own name)", set: { bucket: v }, send: [] }),
|
|
219843
219987
|
path: (_s, v) => ({ says: "always: held as handed; read when the next Upload goes, and only with a Bucket", set: { path: v }, send: [] }),
|
|
219844
219988
|
collection: (_s, v) => ({ says: "always: held as handed; read when the next Upload goes \u2014 with Field set and no Bucket, the file attaches to a record of this collection", set: { collection: v }, send: [] }),
|
|
@@ -219846,7 +219990,7 @@ var UploadFile = defineNode({
|
|
|
219846
219990
|
field: (_s, v) => ({ says: "always: held as handed; read when the next Upload goes, and only with Collection \u2014 a half-filled group is no location at all", set: { field: v }, send: [] }),
|
|
219847
219991
|
// :170-172 — a token now, the work at the frame end
|
|
219848
219992
|
upload: (s) => ({
|
|
219849
|
-
says: "always: its own upload at the frame's end \u2014 two presses are two uploads \u2014 with File, Private and the File Location held then: no File, or a Backend the project does not have, is Error, Error Status Code 0 and Failure at once; otherwise the backend answers with Cloud File then Done, or Error, Error Status Code and Failure",
|
|
219993
|
+
says: "always: its own upload at the frame's end \u2014 two presses are two uploads \u2014 with File, Private, Readers and the File Location held then: no File, or a Backend the project does not have, is Error, Error Status Code 0 and Failure at once; otherwise the backend answers with Cloud File then Done, or Error, Error Status Code and Failure",
|
|
219850
219994
|
set: { presses: s.presses + 1 },
|
|
219851
219995
|
send: [],
|
|
219852
219996
|
outcome: "pending"
|
|
@@ -219863,7 +220007,7 @@ var UploadFile = defineNode({
|
|
|
219863
220007
|
},
|
|
219864
220008
|
// :172-216
|
|
219865
220009
|
afterInputs: (s, _i, w) => {
|
|
219866
|
-
const r = frame(s, w, !!s.file, "uploadFile", () => ({ file: s.file, private: s.private, target: fileTarget(s) }), UPLOAD_ERROR_CODE, "upload");
|
|
220010
|
+
const r = frame(s, w, !!s.file, "uploadFile", () => ({ file: s.file, private: s.private, readers: s.readers, target: fileTarget(s) }), UPLOAD_ERROR_CODE, "upload");
|
|
219867
220011
|
if (!("backend" in r) || !r.backend) return r;
|
|
219868
220012
|
const f = s.file;
|
|
219869
220013
|
const sent2 = f && typeof f.name === "string" ? f.name : void 0;
|
|
@@ -224468,6 +224612,113 @@ function settingsDiagnostics(spec, params, wired = []) {
|
|
|
224468
224612
|
return [...shown].map(([key2, message2]) => ({ key: key2, says: spec.diagnostics[key2].says, message: message2 }));
|
|
224469
224613
|
}
|
|
224470
224614
|
|
|
224615
|
+
// ../nodegx-node-spec/src/honesty.ts
|
|
224616
|
+
var SCRIPT_TYPES = Object.freeze(["Expression", "JavaScriptFunction", "Javascript2", "Logic Builder"]);
|
|
224617
|
+
var EXEMPT_TYPES = Object.freeze(["CSS Definition", "Component Children"]);
|
|
224618
|
+
var GRAPH_GRADED_TYPES = Object.freeze([
|
|
224619
|
+
"Component Inputs",
|
|
224620
|
+
"Component Outputs",
|
|
224621
|
+
"Event Receiver",
|
|
224622
|
+
"Event Sender",
|
|
224623
|
+
"For Each Actions",
|
|
224624
|
+
"On App Error",
|
|
224625
|
+
"RunTasks",
|
|
224626
|
+
"net.noodl.ActionDispatcher",
|
|
224627
|
+
"net.noodl.ActionHandler",
|
|
224628
|
+
"net.noodl.ComponentObject",
|
|
224629
|
+
"net.noodl.GlobalStore",
|
|
224630
|
+
"net.noodl.GlobalStore.Set",
|
|
224631
|
+
"net.noodl.GlobalStore.Subscribe",
|
|
224632
|
+
"net.noodl.OptimisticUpdate",
|
|
224633
|
+
"net.noodl.ParentComponentObject",
|
|
224634
|
+
"net.noodl.SetComponentObjectProperties",
|
|
224635
|
+
"net.noodl.SetParentComponentObjectProperties",
|
|
224636
|
+
"net.noodl.StateHistory",
|
|
224637
|
+
"net.noodl.StateHistory.Undo",
|
|
224638
|
+
"net.noodl.StateSnapshot"
|
|
224639
|
+
]);
|
|
224640
|
+
var SCRIPT_TEXT_PARAMETER = Object.freeze({
|
|
224641
|
+
JavaScriptFunction: "functionScript",
|
|
224642
|
+
Expression: "expression",
|
|
224643
|
+
Javascript2: "code",
|
|
224644
|
+
"Logic Builder": "generatedCode"
|
|
224645
|
+
});
|
|
224646
|
+
function honestyClassifier(options) {
|
|
224647
|
+
const core = new Set(options.coreTypes);
|
|
224648
|
+
const components = new Set(options.components ?? []);
|
|
224649
|
+
const exempt = new Set(EXEMPT_TYPES);
|
|
224650
|
+
const scriptTypes = new Set(SCRIPT_TYPES);
|
|
224651
|
+
const specs2 = options.specs ?? specs;
|
|
224652
|
+
const graph = new Set(options.graphGraded ?? GRAPH_GRADED_TYPES);
|
|
224653
|
+
const known = (t) => {
|
|
224654
|
+
const spec = Object.prototype.hasOwnProperty.call(specs2, t) ? specs2[t] : void 0;
|
|
224655
|
+
const contract = spec && typeof spec.contract === "string" && spec.contract.length > 0;
|
|
224656
|
+
return !!spec && !contract || graph.has(t);
|
|
224657
|
+
};
|
|
224658
|
+
return (type) => {
|
|
224659
|
+
if (typeof type !== "string") return "unspecced";
|
|
224660
|
+
if (type.startsWith("/") || type.startsWith("#") || components.has(type)) return "instance";
|
|
224661
|
+
if (exempt.has(type)) return "exempt";
|
|
224662
|
+
if (scriptTypes.has(type)) return "script";
|
|
224663
|
+
if (known(type)) return "known";
|
|
224664
|
+
if (!core.has(type)) return "script";
|
|
224665
|
+
return "unspecced";
|
|
224666
|
+
};
|
|
224667
|
+
}
|
|
224668
|
+
function scriptLinesOf(node) {
|
|
224669
|
+
const param = SCRIPT_TEXT_PARAMETER[node.type];
|
|
224670
|
+
const text2 = param && node.parameters ? node.parameters[param] : void 0;
|
|
224671
|
+
return typeof text2 === "string" && text2.length > 0 ? text2.split("\n").length : 0;
|
|
224672
|
+
}
|
|
224673
|
+
var percentOf2 = (n, d) => d > 0 ? Math.round(1e3 * n / d) / 10 : null;
|
|
224674
|
+
function tally(nodes, classify) {
|
|
224675
|
+
const h = { known: 0, script: 0, unspecced: 0, instances: 0, exempt: 0, percent: null, scriptLines: 0, percentByLines: null, scriptParts: [], unspeccedTypes: {} };
|
|
224676
|
+
let weight = 0;
|
|
224677
|
+
for (const node of nodes) {
|
|
224678
|
+
const cls = classify(node.type);
|
|
224679
|
+
if (cls === "known") h.known++;
|
|
224680
|
+
else if (cls === "instance") h.instances++;
|
|
224681
|
+
else if (cls === "exempt") h.exempt++;
|
|
224682
|
+
else if (cls === "unspecced") {
|
|
224683
|
+
h.unspecced++;
|
|
224684
|
+
h.unspeccedTypes[node.type] = (h.unspeccedTypes[node.type] ?? 0) + 1;
|
|
224685
|
+
} else {
|
|
224686
|
+
h.script++;
|
|
224687
|
+
const lines = scriptLinesOf(node);
|
|
224688
|
+
h.scriptLines += lines;
|
|
224689
|
+
weight += Math.max(1, lines);
|
|
224690
|
+
h.scriptParts.push({ id: node.id, type: node.type, ...node.label ? { label: node.label } : {}, lines, kit: !SCRIPT_TYPES.includes(node.type) });
|
|
224691
|
+
}
|
|
224692
|
+
}
|
|
224693
|
+
h.percent = percentOf2(h.known, h.known + h.script);
|
|
224694
|
+
h.percentByLines = percentOf2(h.known, h.known + weight);
|
|
224695
|
+
return h;
|
|
224696
|
+
}
|
|
224697
|
+
function componentHonesty(component, options, classify = honestyClassifier(options)) {
|
|
224698
|
+
return { component: component.name, ...tally(component.nodes, classify) };
|
|
224699
|
+
}
|
|
224700
|
+
function projectHonesty(components, options) {
|
|
224701
|
+
const classify = honestyClassifier({ ...options, components: [...options.components ?? [], ...components.map((c) => c.name)] });
|
|
224702
|
+
const rows = components.map((c) => componentHonesty(c, options, classify));
|
|
224703
|
+
const all = tally(
|
|
224704
|
+
components.flatMap((c) => c.nodes),
|
|
224705
|
+
classify
|
|
224706
|
+
);
|
|
224707
|
+
return { ...all, components: rows };
|
|
224708
|
+
}
|
|
224709
|
+
function scriptPartName(part) {
|
|
224710
|
+
return part.label ? part.label : `${part.type} ${part.id}`;
|
|
224711
|
+
}
|
|
224712
|
+
function honestyLine(h, maxNames = 5) {
|
|
224713
|
+
const head = h.percent === null ? `no known or script nodes (${h.unspecced} unspecced)` : `${h.percent}% known (${h.known} known, ${h.script} script${h.unspecced ? `, ${h.unspecced} unspecced` : ""})`;
|
|
224714
|
+
if (!h.scriptParts.length || maxNames <= 0) return head;
|
|
224715
|
+
const parts = [...h.scriptParts].sort((a8, b) => b.lines - a8.lines);
|
|
224716
|
+
const names2 = parts.slice(0, maxNames).map((p) => "`" + scriptPartName(p) + "`");
|
|
224717
|
+
const more = parts.length > maxNames ? ` +${parts.length - maxNames} more` : "";
|
|
224718
|
+
return `${head}; script: ${names2.join(", ")}${more}`;
|
|
224719
|
+
}
|
|
224720
|
+
var HONESTY_SENTENCE = "The honesty number is the share of an app's nodes whose behaviour is specced and graded (known) against the nodes that hold code nobody has checked: Function, Script, Expression and Visual Function nodes, and kit nodes with no spec (script). It is known \xF7 (known + script), counted by node instance; component instances are counted inside their component, and a shipped node still waiting for its spec is reported apart as unspecced. It is not a quality score: a Function written on purpose is still script. It says how much of the app can be explained without reading code, and the script list says which parts cannot.";
|
|
224721
|
+
|
|
224471
224722
|
// ../nodegx-project-contract/port-context.ts
|
|
224472
224723
|
function portProjectFromStore(store2) {
|
|
224473
224724
|
const registry2 = store2.readRegistry().components;
|
|
@@ -226067,6 +226318,234 @@ var V2ProjectStore = class {
|
|
|
226067
226318
|
}
|
|
226068
226319
|
};
|
|
226069
226320
|
|
|
226321
|
+
// ../nodegx-project-contract/run-on-value-change-migration.ts
|
|
226322
|
+
var RUN_ON_CHANGE_PREFIX2 = "runOnChange-";
|
|
226323
|
+
var RUN_ON_CHANGE_FAMILIES = {
|
|
226324
|
+
// variables/variablebase.ts — `Set` is the control signal on all four types.
|
|
226325
|
+
String: { controlSignal: "saveValue", declared: ["value"] },
|
|
226326
|
+
Number: { controlSignal: "saveValue", declared: ["value"] },
|
|
226327
|
+
Boolean: { controlSignal: "saveValue", declared: ["value"] },
|
|
226328
|
+
Color: { controlSignal: "saveValue", declared: ["value"] },
|
|
226329
|
+
// std-library/condition.ts
|
|
226330
|
+
Condition: { controlSignal: "eval", declared: ["condition"] },
|
|
226331
|
+
// std-library/data/collectionnode2.ts — `array` is a subscription, not a port.
|
|
226332
|
+
Collection2: { controlSignal: "fetch", declared: ["collectionId", "array"] },
|
|
226333
|
+
// std-library/data/modelnode2.ts
|
|
226334
|
+
Model2: { controlSignal: "fetch", declared: ["modelId", "object"] },
|
|
226335
|
+
// std-library/data/dbmodelnode2.ts
|
|
226336
|
+
DbModel2: { controlSignal: "fetch", declared: ["modelId", "record"] },
|
|
226337
|
+
// std-library/data/variablenode2.ts
|
|
226338
|
+
Variable2: { controlSignal: "fetch", declared: ["name", "variable"] },
|
|
226339
|
+
// std-library/data/filtercollectionnode.ts
|
|
226340
|
+
"Filter Collection": {
|
|
226341
|
+
controlSignal: "filter",
|
|
226342
|
+
declared: ["items", "enabled", "array", "filterSettings"]
|
|
226343
|
+
},
|
|
226344
|
+
// std-library/data/filterdbmodelsnode.ts — plus one `fp-<name>` port per visual filter input.
|
|
226345
|
+
FilterDBModels: {
|
|
226346
|
+
controlSignal: "filter",
|
|
226347
|
+
declared: ["items", "enabled", "records", "filterSettings"],
|
|
226348
|
+
discoveredPrefixes: ["fp-"]
|
|
226349
|
+
},
|
|
226350
|
+
// std-library/data/dbcollectionnode2.ts — every governed name is registered at runtime
|
|
226351
|
+
// (`initialize` and `registerInputIfNeeded`), so none of them reaches the catalog; plus one
|
|
226352
|
+
// `qp-<name>` port per visual query input.
|
|
226353
|
+
DbCollection2: {
|
|
226354
|
+
controlSignal: "storageFetch",
|
|
226355
|
+
declared: ["records", "querySettings", "collectionName", "search"],
|
|
226356
|
+
discoveredPrefixes: ["qp-"]
|
|
226357
|
+
},
|
|
226358
|
+
// std-library/componentutils/componentobject.ts
|
|
226359
|
+
"net.noodl.ComponentObject": { controlSignal: "fetch", declared: ["object"] },
|
|
226360
|
+
// noodl-viewer-react/nodes/std-library/componentutils/parentcomponentobject.ts
|
|
226361
|
+
"net.noodl.ParentComponentObject": { controlSignal: "fetch", declared: ["object"] },
|
|
226362
|
+
// std-library/user/user.ts
|
|
226363
|
+
"net.noodl.user.User": { controlSignal: "fetch", declared: ["user"] },
|
|
226364
|
+
// noodl-viewer-react/nodes/controls/text-input.ts — `Set` is the control signal and `Text` the
|
|
226365
|
+
// one value input it silenced.
|
|
226366
|
+
//
|
|
226367
|
+
// ✅ P88 GAM-019 (2026-09-14): this family's checkbox port is now declared. Until then
|
|
226368
|
+
// `createNodeFromReactComponent` built its definition field by field and never copied
|
|
226369
|
+
// `runOnValueChange`, so `defineNode` synthesised nothing and the catalog and the panel carried
|
|
226370
|
+
// no `runOnChange-startValue`, though a saved parameter still reached `registerInputIfNeeded`,
|
|
226371
|
+
// where `defineNode`'s wrapper claimed it and the untick landed. The field is forwarded now, so
|
|
226372
|
+
// the port is in the catalog and the panel, and the validator's parameter check accepts it.
|
|
226373
|
+
"net.noodl.controls.textinput": { controlSignal: "set", declared: ["startValue"] },
|
|
226374
|
+
// std-library/simplejavascript.ts — script inputs are registered as `in-<name>`, so the
|
|
226375
|
+
// checkbox is `runOnChange-in-<name>`. `functionScript` is the definition port and keeps the
|
|
226376
|
+
// old guard deliberately; it is not governed.
|
|
226377
|
+
JavaScriptFunction: { controlSignal: "run", declared: [], discoveredPrefixes: ["in-"] },
|
|
226378
|
+
// std-library/expression.ts — every free identifier in `expression` becomes an input.
|
|
226379
|
+
// `expression` is the definition port and keeps the old guard deliberately.
|
|
226380
|
+
//
|
|
226381
|
+
// 🔴 `staticInputs` must list EVERY declared input: anything else observed on a saved instance
|
|
226382
|
+
// reads as a free variable. P88 GAM-002 (`89e533625`) declared `evaluateAtLoad`, and until it
|
|
226383
|
+
// was listed here a saved `evaluateAtLoad: false` on an Expression whose `run` is wired was
|
|
226384
|
+
// handed `runOnChange-evaluateAtLoad: false`, a checkbox for a port no expression feeds.
|
|
226385
|
+
Expression: {
|
|
226386
|
+
controlSignal: "run",
|
|
226387
|
+
declared: [],
|
|
226388
|
+
discoveredBareIdentifiers: true,
|
|
226389
|
+
staticInputs: ["evaluateAtLoad", "expression", "run"]
|
|
226390
|
+
}
|
|
226391
|
+
};
|
|
226392
|
+
function emptyPlan() {
|
|
226393
|
+
return { writes: [], familyNodes: 0, signalDrivenNodes: 0, preserved: 0 };
|
|
226394
|
+
}
|
|
226395
|
+
function runOnChangePortName(inputName) {
|
|
226396
|
+
return RUN_ON_CHANGE_PREFIX2 + inputName;
|
|
226397
|
+
}
|
|
226398
|
+
function governedInputsFor(node, family, incomingPorts) {
|
|
226399
|
+
const governed = [];
|
|
226400
|
+
const seen = /* @__PURE__ */ new Set();
|
|
226401
|
+
const add = (name) => {
|
|
226402
|
+
if (name.length === 0 || seen.has(name)) return;
|
|
226403
|
+
seen.add(name);
|
|
226404
|
+
governed.push(name);
|
|
226405
|
+
};
|
|
226406
|
+
for (const name of family.declared) add(name);
|
|
226407
|
+
const wantsDiscovery = !!family.discoveredPrefixes || !!family.discoveredBareIdentifiers;
|
|
226408
|
+
if (!wantsDiscovery) return governed;
|
|
226409
|
+
const statics = new Set(family.staticInputs ?? []);
|
|
226410
|
+
const observed = [...incomingPorts];
|
|
226411
|
+
for (const name of Object.keys(node.parameters ?? {})) observed.push(name);
|
|
226412
|
+
for (const port5 of node.ports ?? []) if (typeof port5?.name === "string") observed.push(port5.name);
|
|
226413
|
+
for (const port5 of node.dynamicports ?? []) if (typeof port5?.name === "string") observed.push(port5.name);
|
|
226414
|
+
for (const name of observed) {
|
|
226415
|
+
if (name.indexOf(RUN_ON_CHANGE_PREFIX2) === 0) continue;
|
|
226416
|
+
if (statics.has(name)) continue;
|
|
226417
|
+
if (family.discoveredPrefixes) {
|
|
226418
|
+
if (family.discoveredPrefixes.some((prefix) => name.indexOf(prefix) === 0)) add(name);
|
|
226419
|
+
continue;
|
|
226420
|
+
}
|
|
226421
|
+
add(name);
|
|
226422
|
+
}
|
|
226423
|
+
return governed;
|
|
226424
|
+
}
|
|
226425
|
+
function eachNode2(roots, visit) {
|
|
226426
|
+
for (const node of roots) {
|
|
226427
|
+
if (!node || typeof node !== "object") continue;
|
|
226428
|
+
visit(node);
|
|
226429
|
+
if (Array.isArray(node.children)) eachNode2(node.children, visit);
|
|
226430
|
+
}
|
|
226431
|
+
}
|
|
226432
|
+
function planRunOnValueChangeMigration(project2) {
|
|
226433
|
+
const plan = emptyPlan();
|
|
226434
|
+
if (!project2 || !Array.isArray(project2.components)) return plan;
|
|
226435
|
+
for (const component of project2.components) {
|
|
226436
|
+
const graph = component?.graph;
|
|
226437
|
+
if (!graph || !Array.isArray(graph.roots)) continue;
|
|
226438
|
+
const componentName = typeof component.name === "string" ? component.name : "";
|
|
226439
|
+
const connections = Array.isArray(graph.connections) ? graph.connections : [];
|
|
226440
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
226441
|
+
eachNode2(graph.roots, (node) => {
|
|
226442
|
+
if (typeof node.id === "string") nodeIds.add(node.id);
|
|
226443
|
+
});
|
|
226444
|
+
const incomingByNode = /* @__PURE__ */ new Map();
|
|
226445
|
+
for (const connection of connections) {
|
|
226446
|
+
if (!connection || typeof connection.toId !== "string" || typeof connection.toProperty !== "string") continue;
|
|
226447
|
+
if (!nodeIds.has(connection.fromId)) continue;
|
|
226448
|
+
const ports = incomingByNode.get(connection.toId);
|
|
226449
|
+
if (ports) ports.push(connection.toProperty);
|
|
226450
|
+
else incomingByNode.set(connection.toId, [connection.toProperty]);
|
|
226451
|
+
}
|
|
226452
|
+
eachNode2(graph.roots, (node) => {
|
|
226453
|
+
const family = RUN_ON_CHANGE_FAMILIES[node.type];
|
|
226454
|
+
if (!family) return;
|
|
226455
|
+
plan.familyNodes++;
|
|
226456
|
+
const incomingPorts = incomingByNode.get(node.id) ?? [];
|
|
226457
|
+
if (incomingPorts.indexOf(family.controlSignal) === -1) return;
|
|
226458
|
+
plan.signalDrivenNodes++;
|
|
226459
|
+
const parameters = node.parameters ?? {};
|
|
226460
|
+
for (const input of governedInputsFor(node, family, incomingPorts)) {
|
|
226461
|
+
const parameter = runOnChangePortName(input);
|
|
226462
|
+
if (Object.prototype.hasOwnProperty.call(parameters, parameter)) {
|
|
226463
|
+
plan.preserved++;
|
|
226464
|
+
continue;
|
|
226465
|
+
}
|
|
226466
|
+
plan.writes.push({
|
|
226467
|
+
component: componentName,
|
|
226468
|
+
nodeId: node.id,
|
|
226469
|
+
nodeType: node.type,
|
|
226470
|
+
input,
|
|
226471
|
+
parameter
|
|
226472
|
+
});
|
|
226473
|
+
}
|
|
226474
|
+
});
|
|
226475
|
+
}
|
|
226476
|
+
return plan;
|
|
226477
|
+
}
|
|
226478
|
+
var RUN_ON_VALUE_CHANGE_FORMAT_VERSION = "5";
|
|
226479
|
+
function projectPredatesRunOnValueChange(version2) {
|
|
226480
|
+
if (version2 === void 0 || version2 === null || version2 === "") return true;
|
|
226481
|
+
const n = Number(version2);
|
|
226482
|
+
return !Number.isFinite(n) || n < Number(RUN_ON_VALUE_CHANGE_FORMAT_VERSION);
|
|
226483
|
+
}
|
|
226484
|
+
function applyRunOnValueChangeMigration(project2) {
|
|
226485
|
+
return writeGovernedCheckboxes(project2, false);
|
|
226486
|
+
}
|
|
226487
|
+
function writeGovernedCheckboxes(project2, value) {
|
|
226488
|
+
const plan = planRunOnValueChangeMigration(project2);
|
|
226489
|
+
if (plan.writes.length === 0) return plan;
|
|
226490
|
+
const writesByNode = /* @__PURE__ */ new Map();
|
|
226491
|
+
for (const write of plan.writes) {
|
|
226492
|
+
const list5 = writesByNode.get(write.nodeId);
|
|
226493
|
+
if (list5) list5.push(write);
|
|
226494
|
+
else writesByNode.set(write.nodeId, [write]);
|
|
226495
|
+
}
|
|
226496
|
+
for (const component of project2.components ?? []) {
|
|
226497
|
+
const roots = component?.graph?.roots;
|
|
226498
|
+
if (!Array.isArray(roots)) continue;
|
|
226499
|
+
eachNode2(roots, (node) => {
|
|
226500
|
+
const writes = writesByNode.get(node.id);
|
|
226501
|
+
if (!writes) return;
|
|
226502
|
+
const existing = node.parameters ?? {};
|
|
226503
|
+
const rebuilt = {};
|
|
226504
|
+
for (const write of writes) rebuilt[write.parameter] = value;
|
|
226505
|
+
for (const name of Object.keys(existing)) {
|
|
226506
|
+
if (!Object.prototype.hasOwnProperty.call(rebuilt, name)) rebuilt[name] = existing[name];
|
|
226507
|
+
}
|
|
226508
|
+
node.parameters = rebuilt;
|
|
226509
|
+
});
|
|
226510
|
+
}
|
|
226511
|
+
return plan;
|
|
226512
|
+
}
|
|
226513
|
+
|
|
226514
|
+
// ../nodegx-project-contract/format/run-on-change-pins.ts
|
|
226515
|
+
var projectWillBeCleanedUp = (version2) => projectPredatesRunOnValueChange(version2);
|
|
226516
|
+
var incomingOf = (connections, id) => JSON.stringify(
|
|
226517
|
+
(connections ?? []).filter((c) => c.toId === id).map((c) => `${c.fromId}.${c.fromProperty}>${c.toProperty}`).sort()
|
|
226518
|
+
);
|
|
226519
|
+
function pinAuthoredRunOnChange(key2, files, onDisk) {
|
|
226520
|
+
const nodes = files.nodes.nodes ?? [];
|
|
226521
|
+
const plan = planRunOnValueChangeMigration({
|
|
226522
|
+
components: [{ name: key2, graph: { roots: nodes, connections: files.connections.connections } }]
|
|
226523
|
+
});
|
|
226524
|
+
if (plan.writes.length === 0) return [];
|
|
226525
|
+
const before = /* @__PURE__ */ new Map();
|
|
226526
|
+
for (const n of onDisk?.nodes.nodes ?? []) before.set(n.id, n);
|
|
226527
|
+
const authored = (n) => {
|
|
226528
|
+
const old = before.get(n.id);
|
|
226529
|
+
if (!old) return true;
|
|
226530
|
+
if (JSON.stringify(old) !== JSON.stringify(n)) return true;
|
|
226531
|
+
return incomingOf(onDisk?.connections.connections, n.id) !== incomingOf(files.connections.connections, n.id);
|
|
226532
|
+
};
|
|
226533
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
226534
|
+
for (const w of plan.writes) byNode.set(w.nodeId, [...byNode.get(w.nodeId) ?? [], w.parameter]);
|
|
226535
|
+
const written2 = [];
|
|
226536
|
+
for (const node of nodes) {
|
|
226537
|
+
const parameters = byNode.get(node.id);
|
|
226538
|
+
if (!parameters || !authored(node)) continue;
|
|
226539
|
+
const existing = node.parameters ?? {};
|
|
226540
|
+
const rebuilt = {};
|
|
226541
|
+
for (const p of parameters) rebuilt[p] = true;
|
|
226542
|
+
for (const name of Object.keys(existing)) if (!(name in rebuilt)) rebuilt[name] = existing[name];
|
|
226543
|
+
node.parameters = rebuilt;
|
|
226544
|
+
for (const p of parameters) written2.push({ node: node.id, parameter: p });
|
|
226545
|
+
}
|
|
226546
|
+
return written2;
|
|
226547
|
+
}
|
|
226548
|
+
|
|
226070
226549
|
// ../noodl-editor/src/editor/src/models/AiAssistant/authoring/pageRegistration.ts
|
|
226071
226550
|
var ROUTER_NODE_TYPES = /* @__PURE__ */ new Set(["Router", "Page Stack"]);
|
|
226072
226551
|
function isSamePage(a8, b) {
|
|
@@ -236237,199 +236716,6 @@ function loadCatalog() {
|
|
|
236237
236716
|
var fs6 = __toESM(require("fs"));
|
|
236238
236717
|
var path5 = __toESM(require("path"));
|
|
236239
236718
|
|
|
236240
|
-
// ../nodegx-project-contract/run-on-value-change-migration.ts
|
|
236241
|
-
var RUN_ON_CHANGE_PREFIX2 = "runOnChange-";
|
|
236242
|
-
var RUN_ON_CHANGE_FAMILIES = {
|
|
236243
|
-
// variables/variablebase.ts — `Set` is the control signal on all four types.
|
|
236244
|
-
String: { controlSignal: "saveValue", declared: ["value"] },
|
|
236245
|
-
Number: { controlSignal: "saveValue", declared: ["value"] },
|
|
236246
|
-
Boolean: { controlSignal: "saveValue", declared: ["value"] },
|
|
236247
|
-
Color: { controlSignal: "saveValue", declared: ["value"] },
|
|
236248
|
-
// std-library/condition.ts
|
|
236249
|
-
Condition: { controlSignal: "eval", declared: ["condition"] },
|
|
236250
|
-
// std-library/data/collectionnode2.ts — `array` is a subscription, not a port.
|
|
236251
|
-
Collection2: { controlSignal: "fetch", declared: ["collectionId", "array"] },
|
|
236252
|
-
// std-library/data/modelnode2.ts
|
|
236253
|
-
Model2: { controlSignal: "fetch", declared: ["modelId", "object"] },
|
|
236254
|
-
// std-library/data/dbmodelnode2.ts
|
|
236255
|
-
DbModel2: { controlSignal: "fetch", declared: ["modelId", "record"] },
|
|
236256
|
-
// std-library/data/variablenode2.ts
|
|
236257
|
-
Variable2: { controlSignal: "fetch", declared: ["name", "variable"] },
|
|
236258
|
-
// std-library/data/filtercollectionnode.ts
|
|
236259
|
-
"Filter Collection": {
|
|
236260
|
-
controlSignal: "filter",
|
|
236261
|
-
declared: ["items", "enabled", "array", "filterSettings"]
|
|
236262
|
-
},
|
|
236263
|
-
// std-library/data/filterdbmodelsnode.ts — plus one `fp-<name>` port per visual filter input.
|
|
236264
|
-
FilterDBModels: {
|
|
236265
|
-
controlSignal: "filter",
|
|
236266
|
-
declared: ["items", "enabled", "records", "filterSettings"],
|
|
236267
|
-
discoveredPrefixes: ["fp-"]
|
|
236268
|
-
},
|
|
236269
|
-
// std-library/data/dbcollectionnode2.ts — every governed name is registered at runtime
|
|
236270
|
-
// (`initialize` and `registerInputIfNeeded`), so none of them reaches the catalog; plus one
|
|
236271
|
-
// `qp-<name>` port per visual query input.
|
|
236272
|
-
DbCollection2: {
|
|
236273
|
-
controlSignal: "storageFetch",
|
|
236274
|
-
declared: ["records", "querySettings", "collectionName", "search"],
|
|
236275
|
-
discoveredPrefixes: ["qp-"]
|
|
236276
|
-
},
|
|
236277
|
-
// std-library/componentutils/componentobject.ts
|
|
236278
|
-
"net.noodl.ComponentObject": { controlSignal: "fetch", declared: ["object"] },
|
|
236279
|
-
// noodl-viewer-react/nodes/std-library/componentutils/parentcomponentobject.ts
|
|
236280
|
-
"net.noodl.ParentComponentObject": { controlSignal: "fetch", declared: ["object"] },
|
|
236281
|
-
// std-library/user/user.ts
|
|
236282
|
-
"net.noodl.user.User": { controlSignal: "fetch", declared: ["user"] },
|
|
236283
|
-
// noodl-viewer-react/nodes/controls/text-input.ts — `Set` is the control signal and `Text` the
|
|
236284
|
-
// one value input it silenced.
|
|
236285
|
-
//
|
|
236286
|
-
// ✅ P88 GAM-019 (2026-09-14): this family's checkbox port is now declared. Until then
|
|
236287
|
-
// `createNodeFromReactComponent` built its definition field by field and never copied
|
|
236288
|
-
// `runOnValueChange`, so `defineNode` synthesised nothing and the catalog and the panel carried
|
|
236289
|
-
// no `runOnChange-startValue`, though a saved parameter still reached `registerInputIfNeeded`,
|
|
236290
|
-
// where `defineNode`'s wrapper claimed it and the untick landed. The field is forwarded now, so
|
|
236291
|
-
// the port is in the catalog and the panel, and the validator's parameter check accepts it.
|
|
236292
|
-
"net.noodl.controls.textinput": { controlSignal: "set", declared: ["startValue"] },
|
|
236293
|
-
// std-library/simplejavascript.ts — script inputs are registered as `in-<name>`, so the
|
|
236294
|
-
// checkbox is `runOnChange-in-<name>`. `functionScript` is the definition port and keeps the
|
|
236295
|
-
// old guard deliberately; it is not governed.
|
|
236296
|
-
JavaScriptFunction: { controlSignal: "run", declared: [], discoveredPrefixes: ["in-"] },
|
|
236297
|
-
// std-library/expression.ts — every free identifier in `expression` becomes an input.
|
|
236298
|
-
// `expression` is the definition port and keeps the old guard deliberately.
|
|
236299
|
-
//
|
|
236300
|
-
// 🔴 `staticInputs` must list EVERY declared input: anything else observed on a saved instance
|
|
236301
|
-
// reads as a free variable. P88 GAM-002 (`89e533625`) declared `evaluateAtLoad`, and until it
|
|
236302
|
-
// was listed here a saved `evaluateAtLoad: false` on an Expression whose `run` is wired was
|
|
236303
|
-
// handed `runOnChange-evaluateAtLoad: false`, a checkbox for a port no expression feeds.
|
|
236304
|
-
Expression: {
|
|
236305
|
-
controlSignal: "run",
|
|
236306
|
-
declared: [],
|
|
236307
|
-
discoveredBareIdentifiers: true,
|
|
236308
|
-
staticInputs: ["evaluateAtLoad", "expression", "run"]
|
|
236309
|
-
}
|
|
236310
|
-
};
|
|
236311
|
-
function emptyPlan() {
|
|
236312
|
-
return { writes: [], familyNodes: 0, signalDrivenNodes: 0, preserved: 0 };
|
|
236313
|
-
}
|
|
236314
|
-
function runOnChangePortName(inputName) {
|
|
236315
|
-
return RUN_ON_CHANGE_PREFIX2 + inputName;
|
|
236316
|
-
}
|
|
236317
|
-
function governedInputsFor(node, family, incomingPorts) {
|
|
236318
|
-
const governed = [];
|
|
236319
|
-
const seen = /* @__PURE__ */ new Set();
|
|
236320
|
-
const add = (name) => {
|
|
236321
|
-
if (name.length === 0 || seen.has(name)) return;
|
|
236322
|
-
seen.add(name);
|
|
236323
|
-
governed.push(name);
|
|
236324
|
-
};
|
|
236325
|
-
for (const name of family.declared) add(name);
|
|
236326
|
-
const wantsDiscovery = !!family.discoveredPrefixes || !!family.discoveredBareIdentifiers;
|
|
236327
|
-
if (!wantsDiscovery) return governed;
|
|
236328
|
-
const statics = new Set(family.staticInputs ?? []);
|
|
236329
|
-
const observed = [...incomingPorts];
|
|
236330
|
-
for (const name of Object.keys(node.parameters ?? {})) observed.push(name);
|
|
236331
|
-
for (const port5 of node.ports ?? []) if (typeof port5?.name === "string") observed.push(port5.name);
|
|
236332
|
-
for (const port5 of node.dynamicports ?? []) if (typeof port5?.name === "string") observed.push(port5.name);
|
|
236333
|
-
for (const name of observed) {
|
|
236334
|
-
if (name.indexOf(RUN_ON_CHANGE_PREFIX2) === 0) continue;
|
|
236335
|
-
if (statics.has(name)) continue;
|
|
236336
|
-
if (family.discoveredPrefixes) {
|
|
236337
|
-
if (family.discoveredPrefixes.some((prefix) => name.indexOf(prefix) === 0)) add(name);
|
|
236338
|
-
continue;
|
|
236339
|
-
}
|
|
236340
|
-
add(name);
|
|
236341
|
-
}
|
|
236342
|
-
return governed;
|
|
236343
|
-
}
|
|
236344
|
-
function eachNode2(roots, visit) {
|
|
236345
|
-
for (const node of roots) {
|
|
236346
|
-
if (!node || typeof node !== "object") continue;
|
|
236347
|
-
visit(node);
|
|
236348
|
-
if (Array.isArray(node.children)) eachNode2(node.children, visit);
|
|
236349
|
-
}
|
|
236350
|
-
}
|
|
236351
|
-
function planRunOnValueChangeMigration(project2) {
|
|
236352
|
-
const plan = emptyPlan();
|
|
236353
|
-
if (!project2 || !Array.isArray(project2.components)) return plan;
|
|
236354
|
-
for (const component of project2.components) {
|
|
236355
|
-
const graph = component?.graph;
|
|
236356
|
-
if (!graph || !Array.isArray(graph.roots)) continue;
|
|
236357
|
-
const componentName = typeof component.name === "string" ? component.name : "";
|
|
236358
|
-
const connections = Array.isArray(graph.connections) ? graph.connections : [];
|
|
236359
|
-
const nodeIds = /* @__PURE__ */ new Set();
|
|
236360
|
-
eachNode2(graph.roots, (node) => {
|
|
236361
|
-
if (typeof node.id === "string") nodeIds.add(node.id);
|
|
236362
|
-
});
|
|
236363
|
-
const incomingByNode = /* @__PURE__ */ new Map();
|
|
236364
|
-
for (const connection of connections) {
|
|
236365
|
-
if (!connection || typeof connection.toId !== "string" || typeof connection.toProperty !== "string") continue;
|
|
236366
|
-
if (!nodeIds.has(connection.fromId)) continue;
|
|
236367
|
-
const ports = incomingByNode.get(connection.toId);
|
|
236368
|
-
if (ports) ports.push(connection.toProperty);
|
|
236369
|
-
else incomingByNode.set(connection.toId, [connection.toProperty]);
|
|
236370
|
-
}
|
|
236371
|
-
eachNode2(graph.roots, (node) => {
|
|
236372
|
-
const family = RUN_ON_CHANGE_FAMILIES[node.type];
|
|
236373
|
-
if (!family) return;
|
|
236374
|
-
plan.familyNodes++;
|
|
236375
|
-
const incomingPorts = incomingByNode.get(node.id) ?? [];
|
|
236376
|
-
if (incomingPorts.indexOf(family.controlSignal) === -1) return;
|
|
236377
|
-
plan.signalDrivenNodes++;
|
|
236378
|
-
const parameters = node.parameters ?? {};
|
|
236379
|
-
for (const input of governedInputsFor(node, family, incomingPorts)) {
|
|
236380
|
-
const parameter = runOnChangePortName(input);
|
|
236381
|
-
if (Object.prototype.hasOwnProperty.call(parameters, parameter)) {
|
|
236382
|
-
plan.preserved++;
|
|
236383
|
-
continue;
|
|
236384
|
-
}
|
|
236385
|
-
plan.writes.push({
|
|
236386
|
-
component: componentName,
|
|
236387
|
-
nodeId: node.id,
|
|
236388
|
-
nodeType: node.type,
|
|
236389
|
-
input,
|
|
236390
|
-
parameter
|
|
236391
|
-
});
|
|
236392
|
-
}
|
|
236393
|
-
});
|
|
236394
|
-
}
|
|
236395
|
-
return plan;
|
|
236396
|
-
}
|
|
236397
|
-
var RUN_ON_VALUE_CHANGE_FORMAT_VERSION = "5";
|
|
236398
|
-
function projectPredatesRunOnValueChange(version2) {
|
|
236399
|
-
if (version2 === void 0 || version2 === null || version2 === "") return true;
|
|
236400
|
-
const n = Number(version2);
|
|
236401
|
-
return !Number.isFinite(n) || n < Number(RUN_ON_VALUE_CHANGE_FORMAT_VERSION);
|
|
236402
|
-
}
|
|
236403
|
-
function applyRunOnValueChangeMigration(project2) {
|
|
236404
|
-
return writeGovernedCheckboxes(project2, false);
|
|
236405
|
-
}
|
|
236406
|
-
function writeGovernedCheckboxes(project2, value) {
|
|
236407
|
-
const plan = planRunOnValueChangeMigration(project2);
|
|
236408
|
-
if (plan.writes.length === 0) return plan;
|
|
236409
|
-
const writesByNode = /* @__PURE__ */ new Map();
|
|
236410
|
-
for (const write of plan.writes) {
|
|
236411
|
-
const list5 = writesByNode.get(write.nodeId);
|
|
236412
|
-
if (list5) list5.push(write);
|
|
236413
|
-
else writesByNode.set(write.nodeId, [write]);
|
|
236414
|
-
}
|
|
236415
|
-
for (const component of project2.components ?? []) {
|
|
236416
|
-
const roots = component?.graph?.roots;
|
|
236417
|
-
if (!Array.isArray(roots)) continue;
|
|
236418
|
-
eachNode2(roots, (node) => {
|
|
236419
|
-
const writes = writesByNode.get(node.id);
|
|
236420
|
-
if (!writes) return;
|
|
236421
|
-
const existing = node.parameters ?? {};
|
|
236422
|
-
const rebuilt = {};
|
|
236423
|
-
for (const write of writes) rebuilt[write.parameter] = value;
|
|
236424
|
-
for (const name of Object.keys(existing)) {
|
|
236425
|
-
if (!Object.prototype.hasOwnProperty.call(rebuilt, name)) rebuilt[name] = existing[name];
|
|
236426
|
-
}
|
|
236427
|
-
node.parameters = rebuilt;
|
|
236428
|
-
});
|
|
236429
|
-
}
|
|
236430
|
-
return plan;
|
|
236431
|
-
}
|
|
236432
|
-
|
|
236433
236719
|
// ../nodegx-export/src/scriptFiles.ts
|
|
236434
236720
|
function normalizeScriptPath2(path43) {
|
|
236435
236721
|
if (typeof path43 !== "string") return void 0;
|
|
@@ -237114,6 +237400,7 @@ function emitScaffold(ir, shell) {
|
|
|
237114
237400
|
function routedPages(ir) {
|
|
237115
237401
|
const router = ir.project.routers[0];
|
|
237116
237402
|
if (!router) return [];
|
|
237403
|
+
const prefix = routerPrefix(ir, router);
|
|
237117
237404
|
const byLegacyPath = /* @__PURE__ */ new Map();
|
|
237118
237405
|
for (const component of ir.components) byLegacyPath.set(`/${component.path}`, component);
|
|
237119
237406
|
const usedNames = /* @__PURE__ */ new Set();
|
|
@@ -237122,7 +237409,7 @@ function routedPages(ir) {
|
|
|
237122
237409
|
const component = byLegacyPath.get(legacyPath);
|
|
237123
237410
|
if (!component) continue;
|
|
237124
237411
|
const fileBase = dedupe2(pascalCase(lastSegment(component.path)), usedNames);
|
|
237125
|
-
const urlPath = `/${trimUrlPart2(pageUrlPath(component))}`;
|
|
237412
|
+
const urlPath = `/${prefix}${trimUrlPart2(pageUrlPath(component))}`;
|
|
237126
237413
|
pages.push({
|
|
237127
237414
|
componentPath: component.path,
|
|
237128
237415
|
fileBase,
|
|
@@ -237135,6 +237422,59 @@ function routedPages(ir) {
|
|
|
237135
237422
|
}
|
|
237136
237423
|
return pages;
|
|
237137
237424
|
}
|
|
237425
|
+
function routerNodeOf(ir, router) {
|
|
237426
|
+
const component = ir.components.find((c) => c.path === router.componentPath);
|
|
237427
|
+
const node = component?.nodes.find((n) => n.id === router.nodeId);
|
|
237428
|
+
return component && node ? { component, node } : void 0;
|
|
237429
|
+
}
|
|
237430
|
+
function routerPrefix(ir, router) {
|
|
237431
|
+
const value = routerNodeOf(ir, router)?.node.parameters.find((p) => p.name === "urlPath")?.value;
|
|
237432
|
+
if (value?.kind !== "literal" || typeof value.value !== "string") return "";
|
|
237433
|
+
const trimmed = trimUrlPart2(value.value);
|
|
237434
|
+
return trimmed === "" ? "" : `${trimmed}/`;
|
|
237435
|
+
}
|
|
237436
|
+
var ROUTER_OWN_STYLE = [
|
|
237437
|
+
{ port: "backgroundColor", label: "Background Color", paints: (t) => t.trim() !== "" && t.trim() !== "transparent" },
|
|
237438
|
+
{ port: "clip", label: "Clip Behavior", paints: (t) => t === "scroll" || t === "clip" },
|
|
237439
|
+
{ port: "cssClassName", label: "CSS Class", paints: (t) => t.trim() !== "" },
|
|
237440
|
+
// The port's default is a comment, `/* background-color: red; */`: it declares nothing.
|
|
237441
|
+
{ port: "styleCss", label: "CSS Style", paints: (t) => t.replace(/\/\*[\s\S]*?\*\//g, "").trim() !== "" }
|
|
237442
|
+
];
|
|
237443
|
+
var paramText = (value) => value === void 0 ? void 0 : value.kind === "literal" ? String(value.value) : value.kind === "script" || value.kind === "expression" ? value.source : value.kind === "json" ? JSON.stringify(value.value) : `${value.value}${value.unit}`;
|
|
237444
|
+
function routerNotes(ir) {
|
|
237445
|
+
const lines = [];
|
|
237446
|
+
for (const router of ir.project.routers) {
|
|
237447
|
+
const found2 = routerNodeOf(ir, router);
|
|
237448
|
+
if (!found2) continue;
|
|
237449
|
+
const { component, node } = found2;
|
|
237450
|
+
const wired = (port5) => component.connections.some((c) => c.toId === node.id && c.toProperty === port5);
|
|
237451
|
+
const value = (port5) => node.parameters.find((p) => p.name === port5)?.value;
|
|
237452
|
+
const at = `${router.componentPath}: node ${router.nodeId} (Router "${router.name}")`;
|
|
237453
|
+
if (wired("mounted")) {
|
|
237454
|
+
lines.push(`${at}: its Mounted is wired \u2014 the running app shows no page while the value is false; this export always routes`);
|
|
237455
|
+
} else {
|
|
237456
|
+
const mounted = value("mounted");
|
|
237457
|
+
if (mounted?.kind === "literal" && mounted.value === false) {
|
|
237458
|
+
lines.push(`${at}: its Mounted is unticked \u2014 the running app renders no Router, so it shows no page at all; this export routes every page`);
|
|
237459
|
+
}
|
|
237460
|
+
}
|
|
237461
|
+
if (wired("urlPath")) {
|
|
237462
|
+
lines.push(`${at}: its Url Path is wired \u2014 the running app puts the value in front of every page's path; the exported routes carry none`);
|
|
237463
|
+
}
|
|
237464
|
+
const own2 = ROUTER_OWN_STYLE.filter(({ port: port5, paints: paints2 }) => {
|
|
237465
|
+
if (wired(port5)) return true;
|
|
237466
|
+
const text2 = paramText(value(port5));
|
|
237467
|
+
return text2 !== void 0 && paints2(text2);
|
|
237468
|
+
}).map((s) => s.label);
|
|
237469
|
+
if (own2.length > 0) {
|
|
237470
|
+
const names2 = own2.length === 1 ? own2[0] : `${own2.slice(0, -1).join(", ")} and ${own2[own2.length - 1]}`;
|
|
237471
|
+
lines.push(
|
|
237472
|
+
`${at}: its own ${names2} ${own2.length === 1 ? "is" : "are"} not carried \u2014 the export renders the pages with no Router element to put ${own2.length === 1 ? "it" : "them"} on`
|
|
237473
|
+
);
|
|
237474
|
+
}
|
|
237475
|
+
}
|
|
237476
|
+
return lines;
|
|
237477
|
+
}
|
|
237138
237478
|
function pageUrlPath(component) {
|
|
237139
237479
|
const pageNode = component.nodes.find((n) => n.type === "Page");
|
|
237140
237480
|
const urlPath = pageNode?.parameters.find((p) => p.name === "urlPath")?.value;
|
|
@@ -237459,8 +237799,10 @@ var RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
|
237459
237799
|
"with",
|
|
237460
237800
|
"yield"
|
|
237461
237801
|
]);
|
|
237802
|
+
var REACT_WITHHELD_PROPS = /* @__PURE__ */ new Set(["key"]);
|
|
237462
237803
|
var isReservedWord = (name) => RESERVED_WORDS.has(name);
|
|
237463
237804
|
function propIdentifier(name) {
|
|
237805
|
+
if (REACT_WITHHELD_PROPS.has(name)) return `${name}Prop`;
|
|
237464
237806
|
if (TS_IDENTIFIER.test(name) && !RESERVED_WORDS.has(name)) return name;
|
|
237465
237807
|
const words3 = name.split(/[^A-Za-z0-9_$]+/).filter((w) => w.length > 0);
|
|
237466
237808
|
const joined = words3.map((w, i) => i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
@@ -239187,6 +239529,10 @@ var PROPERTY_ORDER = [
|
|
|
239187
239529
|
"letter-spacing",
|
|
239188
239530
|
"text-transform",
|
|
239189
239531
|
"text-align",
|
|
239532
|
+
// P18-S110-NOWRAP. Text's three white-space arms, together, after the text block they shape.
|
|
239533
|
+
"white-space",
|
|
239534
|
+
"overflow-wrap",
|
|
239535
|
+
"text-overflow",
|
|
239190
239536
|
"color",
|
|
239191
239537
|
"opacity",
|
|
239192
239538
|
"user-select"
|
|
@@ -239416,6 +239762,20 @@ function computeNodeStyle(node, role, catalog2, styles) {
|
|
|
239416
239762
|
}
|
|
239417
239763
|
consumed.add("backdropBlur");
|
|
239418
239764
|
if (role !== "stack" && literal2("clip") === true) decls.push({ prop: "overflow", value: "hidden" });
|
|
239765
|
+
if (node.type === "Text") {
|
|
239766
|
+
const textOverflow = String(literal2("textOverflow") ?? catalogDefault("textOverflow") ?? "wrap");
|
|
239767
|
+
if (textOverflow === "ellipsis" || textOverflow === "clip") {
|
|
239768
|
+
decls.push({ prop: "white-space", value: "nowrap" });
|
|
239769
|
+
decls.push({ prop: "text-overflow", value: textOverflow });
|
|
239770
|
+
if (!decls.some((d) => d.prop === "overflow")) decls.push({ prop: "overflow", value: "hidden" });
|
|
239771
|
+
} else if (sizeMode === "contentSize" || sizeMode === "contentWidth") {
|
|
239772
|
+
decls.push({ prop: "white-space", value: "pre" });
|
|
239773
|
+
} else {
|
|
239774
|
+
decls.push({ prop: "white-space", value: "pre-wrap" });
|
|
239775
|
+
decls.push({ prop: "overflow-wrap", value: "anywhere" });
|
|
239776
|
+
}
|
|
239777
|
+
consumed.add("textOverflow");
|
|
239778
|
+
}
|
|
239419
239779
|
boxShorthand("padding", params, decls);
|
|
239420
239780
|
boxShorthand("margin", params, decls);
|
|
239421
239781
|
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
@@ -239569,6 +239929,25 @@ function computeRoleCss(node, role, catalog2, styles) {
|
|
|
239569
239929
|
};
|
|
239570
239930
|
}
|
|
239571
239931
|
}
|
|
239932
|
+
if (role === "select") {
|
|
239933
|
+
const placeholder = literal2("placeholder");
|
|
239934
|
+
if (typeof placeholder === "string" && placeholder.length > 0) {
|
|
239935
|
+
const raw = params.get("placeholderOpacity");
|
|
239936
|
+
if (raw !== void 0 && raw.kind !== "literal") {
|
|
239937
|
+
result.notes.push(`node ${node.id}'s Placeholder opacity is not a literal; the placeholder is drawn at full opacity`);
|
|
239938
|
+
} else {
|
|
239939
|
+
const authored = Number((raw?.kind === "literal" ? raw.value : void 0) ?? catalogDefault("placeholderOpacity") ?? 0.5);
|
|
239940
|
+
const opacity = Number.isFinite(authored) ? Math.min(1, Math.max(0, authored)) : 1;
|
|
239941
|
+
if (opacity < 1) {
|
|
239942
|
+
const color = cssParam("color") ?? "FieldText";
|
|
239943
|
+
const percent2 = `${Number((opacity * 100).toFixed(4))}%`;
|
|
239944
|
+
const checked = ":has(> option[hidden]:checked)";
|
|
239945
|
+
result.blocks.push({ suffix: checked, decls: [{ prop: "color", value: `color-mix(in srgb, ${color} ${percent2}, transparent)` }] });
|
|
239946
|
+
result.blocks.push({ suffix: `${checked} > option`, decls: [{ prop: "color", value: color }] });
|
|
239947
|
+
}
|
|
239948
|
+
}
|
|
239949
|
+
}
|
|
239950
|
+
}
|
|
239572
239951
|
if (role === "columns") {
|
|
239573
239952
|
const minWidth = cssParam("minWidth");
|
|
239574
239953
|
const pairs = [
|
|
@@ -241540,6 +241919,195 @@ ${source}`);
|
|
|
241540
241919
|
return scriptFileMarkerDefer(source);
|
|
241541
241920
|
}
|
|
241542
241921
|
|
|
241922
|
+
// ../nodegx-export/src/analyze/querySettings.ts
|
|
241923
|
+
var WIRED_VALUE = "@nodegx/wired-value";
|
|
241924
|
+
var MAX_WIRED_PARAMS = 3;
|
|
241925
|
+
var paramOf = (node, name) => node.parameters.find((p) => p.name === name)?.value;
|
|
241926
|
+
var valueOf = (value) => value === void 0 ? void 0 : value.kind === "literal" || value.kind === "json" ? value.value : void 0;
|
|
241927
|
+
function hasRelatedTo(filter) {
|
|
241928
|
+
if (filter === null || typeof filter !== "object") return false;
|
|
241929
|
+
const record2 = filter;
|
|
241930
|
+
if (Array.isArray(record2.and)) return record2.and.some(hasRelatedTo);
|
|
241931
|
+
if (Array.isArray(record2.or)) return record2.or.some(hasRelatedTo);
|
|
241932
|
+
return "relatedTo" in record2;
|
|
241933
|
+
}
|
|
241934
|
+
function relatedToNeedsALoadedRecord(filter) {
|
|
241935
|
+
if (filter === null || typeof filter !== "object") return false;
|
|
241936
|
+
const record2 = filter;
|
|
241937
|
+
if (Array.isArray(record2.and)) return record2.and.some(relatedToNeedsALoadedRecord);
|
|
241938
|
+
if (Array.isArray(record2.or)) return record2.or.some(relatedToNeedsALoadedRecord);
|
|
241939
|
+
const relatedTo = record2.relatedTo;
|
|
241940
|
+
return relatedTo !== void 0 && (relatedTo.className === void 0 || relatedTo.className === "");
|
|
241941
|
+
}
|
|
241942
|
+
function hasPointsTo(filter) {
|
|
241943
|
+
if (filter === null || typeof filter !== "object") return false;
|
|
241944
|
+
const record2 = filter;
|
|
241945
|
+
if (Array.isArray(record2.and)) return record2.and.some(hasPointsTo);
|
|
241946
|
+
if (Array.isArray(record2.or)) return record2.or.some(hasPointsTo);
|
|
241947
|
+
return Object.values(record2).some((condition) => condition !== null && typeof condition === "object" && "pointsTo" in condition);
|
|
241948
|
+
}
|
|
241949
|
+
function schemaOf(collectionName4, collections) {
|
|
241950
|
+
const collection = collections.find((c) => c.name === collectionName4);
|
|
241951
|
+
if (collection === void 0) return void 0;
|
|
241952
|
+
const properties = {};
|
|
241953
|
+
for (const column of collection.columns) properties[column.name] = { type: column.type };
|
|
241954
|
+
return { collection: collectionName4, properties };
|
|
241955
|
+
}
|
|
241956
|
+
function querySettingsOf(node, component, collections) {
|
|
241957
|
+
const wired = (port5) => component.connections.some((c) => c.toId === node.id && c.toProperty === port5);
|
|
241958
|
+
const collectionName4 = String(valueOf(paramOf(node, "collectionName")) ?? "");
|
|
241959
|
+
for (const port5 of ["storageFilterType", "visualFilter", "visualSort", "storageEnableLimit", "storageLimit", "storageSkip"]) {
|
|
241960
|
+
if (wired(port5)) return { refuse: `its ${port5} input is wired \u2014 which records it asks for must be readable off the graph in this slice` };
|
|
241961
|
+
}
|
|
241962
|
+
if (valueOf(paramOf(node, "storageFilterType")) === "json") {
|
|
241963
|
+
return { refuse: "its Filter is a Javascript filter, a script the runtime runs on every fetch \u2014 the export translates the Visual filter only" };
|
|
241964
|
+
}
|
|
241965
|
+
const search = valueOf(paramOf(node, "search"));
|
|
241966
|
+
if (wired("search") || search !== void 0 && search !== "" && search !== null) {
|
|
241967
|
+
return { refuse: "it searches (the Search input), which is the backend's full-text path \u2014 not exported in this slice" };
|
|
241968
|
+
}
|
|
241969
|
+
const params = {};
|
|
241970
|
+
const visualFilter = valueOf(paramOf(node, "visualFilter"));
|
|
241971
|
+
if (visualFilter !== void 0) {
|
|
241972
|
+
const typedIn = {};
|
|
241973
|
+
for (const p of node.parameters) if (p.name.startsWith("qp-")) typedIn[p.name.slice("qp-".length)] = valueOf(p.value);
|
|
241974
|
+
const schema = schemaOf(collectionName4, collections);
|
|
241975
|
+
const translate = (queryParameters, asked2) => {
|
|
241976
|
+
const read2 = (name) => {
|
|
241977
|
+
asked2.add(name);
|
|
241978
|
+
return queryParameters[name];
|
|
241979
|
+
};
|
|
241980
|
+
let neutral;
|
|
241981
|
+
if (isVisualQueryFormat(visualFilter)) {
|
|
241982
|
+
const parameters = new Proxy(queryParameters, { get: (_target, key2) => typeof key2 === "string" ? read2(key2) : void 0 });
|
|
241983
|
+
neutral = visualQueryToNeutral(visualFilter, parameters);
|
|
241984
|
+
} else {
|
|
241985
|
+
const bare2 = (portName) => portName.startsWith("qp-") ? portName.slice("qp-".length) : portName;
|
|
241986
|
+
neutral = savedFilterToNeutral(visualFilter, (portName) => read2(bare2(portName)), {
|
|
241987
|
+
dropUnresolvedConnected: true
|
|
241988
|
+
});
|
|
241989
|
+
}
|
|
241990
|
+
if (neutral === null || neutral === void 0) return {};
|
|
241991
|
+
if (hasRelatedTo(neutral) && relatedToNeedsALoadedRecord(neutral)) {
|
|
241992
|
+
return { refuse: "its Filter asks a relation question whose class the runtime reads off a loaded record \u2014 not exported in this slice" };
|
|
241993
|
+
}
|
|
241994
|
+
if (hasPointsTo(neutral)) {
|
|
241995
|
+
return { refuse: `its Filter has a "points to" condition, whose target class the export's schema snapshot does not carry` };
|
|
241996
|
+
}
|
|
241997
|
+
let where;
|
|
241998
|
+
try {
|
|
241999
|
+
where = toParseWhere(neutral, { backend: "nodegx", schema });
|
|
242000
|
+
} catch (error2) {
|
|
242001
|
+
if (error2 instanceof FilterTranslationError) {
|
|
242002
|
+
return { refuse: `the runtime cannot translate its Filter either ("${error2.message}"), so it answers Failure and never queries` };
|
|
242003
|
+
}
|
|
242004
|
+
throw error2;
|
|
242005
|
+
}
|
|
242006
|
+
return Object.keys(where).length > 0 ? { where } : {};
|
|
242007
|
+
};
|
|
242008
|
+
const asked = /* @__PURE__ */ new Set();
|
|
242009
|
+
const typedInReading = translate(typedIn, asked);
|
|
242010
|
+
const wiredParams = [...asked].filter((name) => wired(`qp-${name}`));
|
|
242011
|
+
if (wiredParams.length === 0) {
|
|
242012
|
+
if ("refuse" in typedInReading) return typedInReading;
|
|
242013
|
+
if (typedInReading.where !== void 0) params.where = typedInReading.where;
|
|
242014
|
+
} else {
|
|
242015
|
+
const reading = wiredWhereOf(node, component, wiredParams, typedIn, schema, translate);
|
|
242016
|
+
if ("refuse" in reading) return reading;
|
|
242017
|
+
params.wired = reading;
|
|
242018
|
+
}
|
|
242019
|
+
}
|
|
242020
|
+
const visualSort = valueOf(paramOf(node, "visualSort"));
|
|
242021
|
+
if (Array.isArray(visualSort)) {
|
|
242022
|
+
params.sort = visualSort.map((s) => (s?.order === "descending" ? "-" : "") + String(s?.property));
|
|
242023
|
+
}
|
|
242024
|
+
if (valueOf(paramOf(node, "storageEnableLimit"))) {
|
|
242025
|
+
const limit = Number(valueOf(paramOf(node, "storageLimit")) || 10);
|
|
242026
|
+
const skip = Number(valueOf(paramOf(node, "storageSkip")) || 0);
|
|
242027
|
+
params.limit = limit;
|
|
242028
|
+
params.skip = skip;
|
|
242029
|
+
}
|
|
242030
|
+
return { params };
|
|
242031
|
+
}
|
|
242032
|
+
var markerOf2 = (name) => `\0nodegx-qp:${name}\0`;
|
|
242033
|
+
var PLAIN_COMPARISONS = /* @__PURE__ */ new Set(["$eq", "$ne", "$lt", "$gt", "$lte", "$gte"]);
|
|
242034
|
+
function wiredWhereOf(node, component, params, typedIn, schema, translate) {
|
|
242035
|
+
const quoted = params.map((n) => `"${n}"`).join(", ");
|
|
242036
|
+
const wiresInto = (port5) => component.connections.filter((c) => c.toId === node.id && c.toProperty === port5);
|
|
242037
|
+
const fetchWired = wiresInto("storageFetch").length > 0;
|
|
242038
|
+
if (fetchWired && new Set(params.map((name) => valueOf(paramOf(node, `runOnChange-qp-${name}`)) === false)).size > 1) {
|
|
242039
|
+
return { refuse: `its Fetch is wired and the Run On Value Change boxes of ${quoted} disagree \u2014 a value that asks beside one that waits is not exported in this slice` };
|
|
242040
|
+
}
|
|
242041
|
+
if (params.length > MAX_WIRED_PARAMS) {
|
|
242042
|
+
return { refuse: `its Filter reads ${params.length} values from wired inputs; the export builds a \`where\` from at most ${MAX_WIRED_PARAMS}` };
|
|
242043
|
+
}
|
|
242044
|
+
for (const name of params) {
|
|
242045
|
+
if (wiresInto(`qp-${name}`).length > 1) {
|
|
242046
|
+
return { refuse: `two wires feed its "${name}" input \u2014 last-writer-wins is not statically ordered` };
|
|
242047
|
+
}
|
|
242048
|
+
if (paramOf(node, `qp-${name}`) !== void 0) {
|
|
242049
|
+
return { refuse: `its "${name}" input is wired and holds a typed-in value too \u2014 which one the first fetch reads is not exported in this slice` };
|
|
242050
|
+
}
|
|
242051
|
+
if (!fetchWired && valueOf(paramOf(node, `runOnChange-qp-${name}`)) === false) {
|
|
242052
|
+
return {
|
|
242053
|
+
refuse: `its Run On Value Change box for "${name}" is unticked, so a new value waits for a fetch something else starts \u2014 not exported in this slice`
|
|
242054
|
+
};
|
|
242055
|
+
}
|
|
242056
|
+
}
|
|
242057
|
+
const cases = [];
|
|
242058
|
+
for (let mask = 0; mask < 1 << params.length; mask++) {
|
|
242059
|
+
const values = { ...typedIn };
|
|
242060
|
+
params.forEach((name, i) => {
|
|
242061
|
+
values[name] = mask & 1 << i ? markerOf2(name) : void 0;
|
|
242062
|
+
});
|
|
242063
|
+
const reading = translate(values, /* @__PURE__ */ new Set());
|
|
242064
|
+
if ("refuse" in reading) return reading;
|
|
242065
|
+
if (reading.where === void 0) {
|
|
242066
|
+
cases.push(void 0);
|
|
242067
|
+
continue;
|
|
242068
|
+
}
|
|
242069
|
+
const placed2 = placeMarkers(reading.where, params, schema);
|
|
242070
|
+
if ("refuse" in placed2) return placed2;
|
|
242071
|
+
cases.push(placed2.where);
|
|
242072
|
+
}
|
|
242073
|
+
return { params, cases };
|
|
242074
|
+
}
|
|
242075
|
+
function placeMarkers(where, params, schema) {
|
|
242076
|
+
let refusal2;
|
|
242077
|
+
const nameIn = (text2) => params.find((name) => text2.includes(markerOf2(name)));
|
|
242078
|
+
const walk2 = (value, key2, field2) => {
|
|
242079
|
+
if (typeof value === "string") {
|
|
242080
|
+
const name = nameIn(value);
|
|
242081
|
+
if (name === void 0) return value;
|
|
242082
|
+
if (value !== markerOf2(name) || key2 === void 0 || !PLAIN_COMPARISONS.has(key2) || field2 === void 0) {
|
|
242083
|
+
refusal2 ??= `its Filter puts the value of "${name}" somewhere other than a plain comparison (${key2 ?? "the top"}${field2 === void 0 || field2 === key2 ? "" : ` on "${field2}"`}), which the runtime reshapes per value \u2014 not exported in this slice`;
|
|
242084
|
+
return value;
|
|
242085
|
+
}
|
|
242086
|
+
const mark = { [WIRED_VALUE]: name, date: schema?.properties?.[field2]?.type === "Date" };
|
|
242087
|
+
return mark;
|
|
242088
|
+
}
|
|
242089
|
+
if (Array.isArray(value)) return value.map((item) => walk2(item, key2, field2));
|
|
242090
|
+
if (value !== null && typeof value === "object") {
|
|
242091
|
+
const out = {};
|
|
242092
|
+
for (const [k, v] of Object.entries(value)) out[k] = walk2(v, k, k.startsWith("$") ? field2 : k);
|
|
242093
|
+
return out;
|
|
242094
|
+
}
|
|
242095
|
+
return value;
|
|
242096
|
+
};
|
|
242097
|
+
const placed2 = walk2(where, void 0, void 0);
|
|
242098
|
+
return refusal2 === void 0 ? { where: placed2 } : { refuse: refusal2 };
|
|
242099
|
+
}
|
|
242100
|
+
var isWiredValueMark = (value) => value !== null && typeof value === "object" && WIRED_VALUE in value;
|
|
242101
|
+
function wiredWhereCode(where, valueCode) {
|
|
242102
|
+
const codes = [];
|
|
242103
|
+
const json3 = JSON.stringify(where, (_key, value) => {
|
|
242104
|
+
if (!isWiredValueMark(value)) return value;
|
|
242105
|
+
codes.push(valueCode(value[WIRED_VALUE], value.date));
|
|
242106
|
+
return `\0${codes.length - 1}\0`;
|
|
242107
|
+
});
|
|
242108
|
+
return json3.replace(/"\\u0000(\d+)\\u0000"/g, (_m, index) => codes[Number(index)]);
|
|
242109
|
+
}
|
|
242110
|
+
|
|
241543
242111
|
// ../nodegx-export/src/analyze/appState.ts
|
|
241544
242112
|
var GLOBAL_STORE = "net.noodl.GlobalStore";
|
|
241545
242113
|
var GLOBAL_STORE_SET = "net.noodl.GlobalStore.Set";
|
|
@@ -241802,7 +242370,7 @@ function collectAppState(ir) {
|
|
|
241802
242370
|
if (literalBool(node, "persist") === true || wiredPortsOf(component).has(`${node.id}:persist`)) {
|
|
241803
242371
|
plan.deferred = plan.deferred ?? "a declarer authors persist \u2014 browser-storage rehydration is not translatable";
|
|
241804
242372
|
} else if (node.parameters.some((p) => p.name === "storageKey")) {
|
|
241805
|
-
plan.notes.push(
|
|
242373
|
+
plan.notes.push({ componentPath: component.path, nodeId: node.id, text: "sets storageKey without persist \u2014 inert, ignored" });
|
|
241806
242374
|
}
|
|
241807
242375
|
const initial = initialStateOf(node, wiredPortsOf(component));
|
|
241808
242376
|
if (initial.kind === "object") {
|
|
@@ -241812,13 +242380,15 @@ function collectAppState(ir) {
|
|
|
241812
242380
|
entry.required = true;
|
|
241813
242381
|
entry.initial = value;
|
|
241814
242382
|
} else if (JSON.stringify(entry.initial) !== JSON.stringify(value)) {
|
|
241815
|
-
plan.notes.push(
|
|
241816
|
-
|
|
241817
|
-
|
|
242383
|
+
plan.notes.push({
|
|
242384
|
+
componentPath: component.path,
|
|
242385
|
+
nodeId: node.id,
|
|
242386
|
+
text: `gives key "${key2}" a different initial value from an earlier declarer \u2014 first-seen wins, so this one is ignored`
|
|
242387
|
+
});
|
|
241818
242388
|
}
|
|
241819
242389
|
}
|
|
241820
242390
|
} else if (initial.kind === "bad") {
|
|
241821
|
-
plan.notes.push(
|
|
242391
|
+
plan.notes.push({ componentPath: component.path, nodeId: node.id, text: "has a non-literal or non-object initialState \u2014 contributes nothing" });
|
|
241822
242392
|
}
|
|
241823
242393
|
}
|
|
241824
242394
|
if (node.type === GLOBAL_STORE_SET) {
|
|
@@ -242373,6 +242943,7 @@ var STACK_COMPLETED_REASON = "its Completed output is consumed \u2014 it fires a
|
|
|
242373
242943
|
var CLOUD_FUNCTION_TYPE2 = "CloudFunction2";
|
|
242374
242944
|
var CLOUD_FUNCTION_OUTPUTS = ["done", "failure", "completed", "error"];
|
|
242375
242945
|
var RECORD_TYPE = "DbModel2";
|
|
242946
|
+
var VARIABLE_FROZEN = "its Variable changes box is unticked under Run On Value Change \u2014 it keeps the value it read until a Fetch, and the exported variable is read live";
|
|
242376
242947
|
var RECORD_OUTPUTS = ["done", "failure", "completed", "error", "id"];
|
|
242377
242948
|
var FILE_PICKER_TYPE = "Open File Picker";
|
|
242378
242949
|
var UPLOAD_FILE_TYPE = "Upload File";
|
|
@@ -243169,6 +243740,11 @@ var USER_VERBS = {
|
|
|
243169
243740
|
clearsErrorOnDone: true
|
|
243170
243741
|
}
|
|
243171
243742
|
};
|
|
243743
|
+
function isAwaitedAction(value) {
|
|
243744
|
+
if (value === null || typeof value !== "object") return false;
|
|
243745
|
+
const kind = value.kind;
|
|
243746
|
+
return kind === "api-call" || kind === "http-call" || kind === "cloud-call" || kind === "record-fetch" || kind === "file-pick" || kind === "file-upload" || kind === "file-sign" || kind === "crypto-call" && value.async === true;
|
|
243747
|
+
}
|
|
243172
243748
|
var SESSION_READS = {
|
|
243173
243749
|
authenticated: "authenticated",
|
|
243174
243750
|
id: { field: "id", maybeUndefined: true },
|
|
@@ -243227,32 +243803,41 @@ function planProject(ir, catalog2) {
|
|
|
243227
243803
|
};
|
|
243228
243804
|
for (const component of ir.components) visit(`/${component.path}`, []);
|
|
243229
243805
|
const plannedByLegacy = /* @__PURE__ */ new Map();
|
|
243230
|
-
|
|
243806
|
+
const exclusionsOf = /* @__PURE__ */ new Map();
|
|
243807
|
+
const namesTakenBy = /* @__PURE__ */ new Map();
|
|
243808
|
+
const nameSets = [usedPageNames, usedComponentNames, usedHttpNames];
|
|
243809
|
+
const planOne = (legacy) => {
|
|
243810
|
+
for (const taken of namesTakenBy.get(legacy) ?? []) for (const name of taken.names) taken.set.delete(name);
|
|
243811
|
+
const before = nameSets.map((set2) => new Set(set2));
|
|
243231
243812
|
const component = ir.components.find((c) => `/${c.path}` === legacy);
|
|
243232
|
-
|
|
243813
|
+
const planned = planComponent(
|
|
243814
|
+
component,
|
|
243815
|
+
ir,
|
|
243816
|
+
catalog2,
|
|
243817
|
+
registry2,
|
|
243818
|
+
pageByLegacy,
|
|
243819
|
+
pageFileByPath,
|
|
243820
|
+
usedPageNames,
|
|
243821
|
+
usedComponentNames,
|
|
243822
|
+
kits,
|
|
243823
|
+
usedHttpNames,
|
|
243824
|
+
plannedByLegacy,
|
|
243825
|
+
templateCycle,
|
|
243826
|
+
exclusionsOf.get(legacy)
|
|
243827
|
+
);
|
|
243828
|
+
namesTakenBy.set(
|
|
243233
243829
|
legacy,
|
|
243234
|
-
|
|
243235
|
-
component,
|
|
243236
|
-
ir,
|
|
243237
|
-
catalog2,
|
|
243238
|
-
registry2,
|
|
243239
|
-
pageByLegacy,
|
|
243240
|
-
pageFileByPath,
|
|
243241
|
-
usedPageNames,
|
|
243242
|
-
usedComponentNames,
|
|
243243
|
-
kits,
|
|
243244
|
-
usedHttpNames,
|
|
243245
|
-
plannedByLegacy,
|
|
243246
|
-
templateCycle
|
|
243247
|
-
)
|
|
243830
|
+
nameSets.map((set2, i) => ({ set: set2, names: [...set2].filter((name) => !before[i].has(name)) }))
|
|
243248
243831
|
);
|
|
243249
|
-
|
|
243250
|
-
|
|
243251
|
-
|
|
243252
|
-
for (const
|
|
243832
|
+
plannedByLegacy.set(legacy, planned);
|
|
243833
|
+
return planned;
|
|
243834
|
+
};
|
|
243835
|
+
for (const legacy of order) planOne(legacy);
|
|
243836
|
+
const byLegacyNow = (legacy) => plannedByLegacy.get(legacy);
|
|
243837
|
+
const liftInto = (plan) => {
|
|
243253
243838
|
const liftedVarByKey = /* @__PURE__ */ new Map();
|
|
243254
243839
|
for (const pending of plan.pendingLifted) {
|
|
243255
|
-
const child =
|
|
243840
|
+
const child = byLegacyNow(pending.targetLegacy);
|
|
243256
243841
|
const lifted6 = child?.liftedOutputProps.find((l) => l.port === pending.port);
|
|
243257
243842
|
if (!lifted6) {
|
|
243258
243843
|
plan.notes.push(
|
|
@@ -243289,7 +243874,56 @@ function planProject(ir, catalog2) {
|
|
|
243289
243874
|
};
|
|
243290
243875
|
}
|
|
243291
243876
|
plan.pendingLifted = [];
|
|
243877
|
+
};
|
|
243878
|
+
for (const legacy of order) liftInto(plannedByLegacy.get(legacy));
|
|
243879
|
+
for (let round = 0; round < 8; round++) {
|
|
243880
|
+
const again = [];
|
|
243881
|
+
for (const legacy of order) {
|
|
243882
|
+
const planned = plannedByLegacy.get(legacy);
|
|
243883
|
+
const component = ir.components.find((c) => `/${c.path}` === legacy);
|
|
243884
|
+
const found2 = unreachedTriggersOf(planned, component);
|
|
243885
|
+
const known = exclusionsOf.get(legacy) ?? { owners: /* @__PURE__ */ new Map(), mounts: /* @__PURE__ */ new Map(), rows: /* @__PURE__ */ new Map() };
|
|
243886
|
+
let fresh = false;
|
|
243887
|
+
for (const key2 of ["owners", "mounts", "rows"]) {
|
|
243888
|
+
for (const [id, reason2] of found2[key2]) {
|
|
243889
|
+
if (known[key2].has(id)) continue;
|
|
243890
|
+
known[key2].set(id, reason2);
|
|
243891
|
+
fresh = true;
|
|
243892
|
+
}
|
|
243893
|
+
}
|
|
243894
|
+
if (!fresh) continue;
|
|
243895
|
+
exclusionsOf.set(legacy, known);
|
|
243896
|
+
again.push(legacy);
|
|
243897
|
+
}
|
|
243898
|
+
if (again.length === 0) break;
|
|
243899
|
+
for (const legacy of [...again]) {
|
|
243900
|
+
for (const [host, templates] of templatesOf) if (templates.includes(legacy) && !again.includes(host)) again.push(host);
|
|
243901
|
+
}
|
|
243902
|
+
const liftedBefore = new Map(again.map((legacy) => [legacy, JSON.stringify(plannedByLegacy.get(legacy).liftedOutputProps)]));
|
|
243903
|
+
for (const legacy of order) if (again.includes(legacy)) planOne(legacy);
|
|
243904
|
+
for (const [legacy, before] of liftedBefore) {
|
|
243905
|
+
if (JSON.stringify(plannedByLegacy.get(legacy).liftedOutputProps) === before) continue;
|
|
243906
|
+
for (const component of ir.components) {
|
|
243907
|
+
const parent = `/${component.path}`;
|
|
243908
|
+
if (again.includes(parent) || !component.nodes.some((n) => n.type === legacy)) continue;
|
|
243909
|
+
again.push(parent);
|
|
243910
|
+
planOne(parent);
|
|
243911
|
+
}
|
|
243912
|
+
}
|
|
243913
|
+
for (const legacy of again) liftInto(plannedByLegacy.get(legacy));
|
|
243914
|
+
}
|
|
243915
|
+
for (const [legacy, known] of exclusionsOf) {
|
|
243916
|
+
const planned = plannedByLegacy.get(legacy);
|
|
243917
|
+
for (const [fnId, reason2] of known.rows) {
|
|
243918
|
+
const row = planned.stateVars.find((v) => v.origin === "jsfun" && v.originNodeId === fnId);
|
|
243919
|
+
if (row === void 0) continue;
|
|
243920
|
+
const text2 = JSON.stringify({ ...planned, stateVars: void 0, dispositions: void 0, notes: void 0 });
|
|
243921
|
+
if (!text2.includes(`"viaState":"${row.name}"`)) continue;
|
|
243922
|
+
planned.notes.push(`node ${fnId}: a handler or a node that is not drawn still reads its outputs, and reads nothing \u2014 ${reason2.replace(/^it reads /, "")}`);
|
|
243923
|
+
}
|
|
243292
243924
|
}
|
|
243925
|
+
const plans = ir.components.map((component) => plannedByLegacy.get(`/${component.path}`));
|
|
243926
|
+
const byLegacyPath = new Map(plans.map((p) => [p.legacyPath, p]));
|
|
243293
243927
|
const stubCollections = [];
|
|
243294
243928
|
for (const plan of plans) {
|
|
243295
243929
|
for (const query2 of plan.queries) {
|
|
@@ -243309,7 +243943,108 @@ function planProject(ir, catalog2) {
|
|
|
243309
243943
|
kitDuplicates
|
|
243310
243944
|
};
|
|
243311
243945
|
}
|
|
243312
|
-
function
|
|
243946
|
+
function undrawnReasonOf(plan, component, id) {
|
|
243947
|
+
const nodeById = new Map(component.nodes.map((n) => [n.id, n]));
|
|
243948
|
+
const labelOf = (nodeId) => {
|
|
243949
|
+
const node = nodeById.get(nodeId);
|
|
243950
|
+
return node?.authoredLabel ? `"${node.authoredLabel}" (${nodeId})` : `${node?.type ?? "a node"} ${nodeId}`;
|
|
243951
|
+
};
|
|
243952
|
+
for (let at = id; at !== void 0; at = nodeById.get(at)?.parent) {
|
|
243953
|
+
const whose = at === id ? "it is" : `an element above it, ${labelOf(at)}, is`;
|
|
243954
|
+
const bound = plan.bindings[at]?.["mounted"];
|
|
243955
|
+
if (bound !== void 0) {
|
|
243956
|
+
if (bound.kind === "computed" && (bound.expr.kind === "undefined" || bound.expr.kind === "literal" && !bound.expr.value)) {
|
|
243957
|
+
return `${whose} mounted from a statically false source`;
|
|
243958
|
+
}
|
|
243959
|
+
} else if (nodeById.get(at)?.parameters.find((p) => p.name === "mounted")?.value.kind === "literal" && nodeById.get(at).parameters.find((p) => p.name === "mounted").value.value === false) {
|
|
243960
|
+
const wire = component.connections.find((c) => c.toId === at && c.toProperty === "mounted");
|
|
243961
|
+
if (wire === void 0) return `${whose} authored Mounted false, and nothing mounts it`;
|
|
243962
|
+
const prefix = `wire ${wire.key} dropped: `;
|
|
243963
|
+
const dropped = plan.droppedWires.find((w) => w.key === wire.key)?.reason ?? plan.notes.find((n) => n.startsWith(prefix))?.slice(prefix.length);
|
|
243964
|
+
const from = nodeById.get(wire.fromId);
|
|
243965
|
+
const fromRefused = plan.dispositions[wire.fromId];
|
|
243966
|
+
const because = dropped !== void 0 ? ` (${dropped})` : fromRefused?.kind === "deferred" ? ` (the node it comes from is not exported: ${fromRefused.reason})` : "";
|
|
243967
|
+
return `${whose} authored Mounted false, and the wire into its Mounted from ${from?.authoredLabel ? `"${from.authoredLabel}"` : from?.type ?? wire.fromId}.${wire.fromProperty} is not exported${because}`;
|
|
243968
|
+
}
|
|
243969
|
+
if (at === plan.rootId) break;
|
|
243970
|
+
}
|
|
243971
|
+
return null;
|
|
243972
|
+
}
|
|
243973
|
+
function unreachedTriggersOf(plan, component) {
|
|
243974
|
+
const found2 = { owners: /* @__PURE__ */ new Map(), mounts: /* @__PURE__ */ new Map(), rows: /* @__PURE__ */ new Map() };
|
|
243975
|
+
if (plan.file === null) return found2;
|
|
243976
|
+
const nodeById = new Map(component.nodes.map((n) => [n.id, n]));
|
|
243977
|
+
const labelOf = (nodeId) => {
|
|
243978
|
+
const node = nodeById.get(nodeId);
|
|
243979
|
+
return node?.authoredLabel ? `"${node.authoredLabel}" (${nodeId})` : `${node?.type ?? "a node"} ${nodeId}`;
|
|
243980
|
+
};
|
|
243981
|
+
const whyUndrawn = /* @__PURE__ */ new Map();
|
|
243982
|
+
const owners = /* @__PURE__ */ new Set([...Object.keys(plan.handlers), ...Object.keys(plan.changeHandlers), ...plan.drags.map((d) => d.nodeId)]);
|
|
243983
|
+
for (const owner of owners) {
|
|
243984
|
+
if (plan.roleOf[owner] === void 0) continue;
|
|
243985
|
+
const why = undrawnReasonOf(plan, component, owner);
|
|
243986
|
+
if (why === null) continue;
|
|
243987
|
+
whyUndrawn.set(owner, why);
|
|
243988
|
+
found2.owners.set(owner, `its trigger, ${labelOf(owner)}, is not drawn: ${why}`);
|
|
243989
|
+
}
|
|
243990
|
+
const whyUnmounted = /* @__PURE__ */ new Map();
|
|
243991
|
+
for (const [owner, byPort] of Object.entries(plan.handlers)) {
|
|
243992
|
+
if (byPort["didMount"] === void 0 || whyUndrawn.has(owner)) continue;
|
|
243993
|
+
for (let at = owner; at !== void 0; at = nodeById.get(at)?.parent) {
|
|
243994
|
+
const bound = plan.bindings[at]?.["mounted"];
|
|
243995
|
+
if (bound !== void 0 && !(bound.kind === "computed" && bound.expr.kind === "literal")) {
|
|
243996
|
+
const whose = at === owner ? "it is" : `an element above it, ${labelOf(at)}, is`;
|
|
243997
|
+
const why = `${whose} mounted by a value, so its Did Mount fires when that value says, and a mount effect runs when the component mounts`;
|
|
243998
|
+
whyUnmounted.set(owner, why);
|
|
243999
|
+
found2.mounts.set(owner, `its trigger, the Did Mount of ${labelOf(owner)}, is not run: ${why}`);
|
|
244000
|
+
break;
|
|
244001
|
+
}
|
|
244002
|
+
if (at === plan.rootId) break;
|
|
244003
|
+
}
|
|
244004
|
+
}
|
|
244005
|
+
const writers = /* @__PURE__ */ new Map();
|
|
244006
|
+
const walk2 = (value, owner) => {
|
|
244007
|
+
if (Array.isArray(value)) {
|
|
244008
|
+
for (const item of value) walk2(item, owner);
|
|
244009
|
+
return;
|
|
244010
|
+
}
|
|
244011
|
+
if (value === null || typeof value !== "object") return;
|
|
244012
|
+
const record2 = value;
|
|
244013
|
+
if (record2.kind === "jsfun-run" && typeof record2.materialize === "string") {
|
|
244014
|
+
const list5 = writers.get(record2.materialize) ?? [];
|
|
244015
|
+
list5.push(owner ?? "");
|
|
244016
|
+
writers.set(record2.materialize, list5);
|
|
244017
|
+
}
|
|
244018
|
+
for (const child of Object.values(record2)) walk2(child, owner);
|
|
244019
|
+
};
|
|
244020
|
+
for (const [key2, value] of Object.entries(plan)) {
|
|
244021
|
+
if (key2 === "handlers") {
|
|
244022
|
+
for (const [owner, byPort] of Object.entries(plan.handlers)) {
|
|
244023
|
+
for (const [port5, actions] of Object.entries(byPort)) {
|
|
244024
|
+
walk2(actions, whyUndrawn.has(owner) || port5 === "didMount" && whyUnmounted.has(owner) ? owner : void 0);
|
|
244025
|
+
}
|
|
244026
|
+
}
|
|
244027
|
+
} else if (key2 === "changeHandlers") {
|
|
244028
|
+
for (const [owner, actions] of Object.entries(plan.changeHandlers)) walk2(actions, whyUndrawn.has(owner) ? owner : void 0);
|
|
244029
|
+
} else if (key2 !== "stateVars" && key2 !== "dispositions" && key2 !== "notes") {
|
|
244030
|
+
walk2(value, void 0);
|
|
244031
|
+
}
|
|
244032
|
+
}
|
|
244033
|
+
const planText = JSON.stringify({ ...plan, stateVars: void 0, dispositions: void 0, notes: void 0 });
|
|
244034
|
+
for (const row of plan.stateVars) {
|
|
244035
|
+
if (row.origin !== "jsfun") continue;
|
|
244036
|
+
const fnId = row.originNodeId;
|
|
244037
|
+
const written2 = writers.get(row.name) ?? [];
|
|
244038
|
+
if (written2.some((owner2) => owner2 === "")) continue;
|
|
244039
|
+
if (!planText.includes(`"viaState":"${row.name}"`)) continue;
|
|
244040
|
+
const disposition = plan.dispositions[fnId];
|
|
244041
|
+
const owner = written2[0];
|
|
244042
|
+
const why = owner === void 0 ? disposition?.kind === "deferred" ? disposition.reason : "nothing the export runs sends its Run" : whyUndrawn.has(owner) ? `its Run is sent only from ${labelOf(owner)}, which is not drawn: ${whyUndrawn.get(owner)}` : `its Run is sent only from the Did Mount of ${labelOf(owner)}, which is not run: ${whyUnmounted.get(owner)}`;
|
|
244043
|
+
found2.rows.set(fnId, `it reads ${labelOf(fnId)}, which the export never runs, so its outputs are never written: ${why}`);
|
|
244044
|
+
}
|
|
244045
|
+
return found2;
|
|
244046
|
+
}
|
|
244047
|
+
function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFileByPath, usedPageNames, usedComponentNames, kits, usedHttpNames, plannedByLegacy, templateCycle, excluded = { owners: /* @__PURE__ */ new Map(), mounts: /* @__PURE__ */ new Map(), rows: /* @__PURE__ */ new Map() }) {
|
|
243313
244048
|
const nodeById = new Map(component.nodes.map((n) => [n.id, n]));
|
|
243314
244049
|
const dispositions = {};
|
|
243315
244050
|
const notes = [];
|
|
@@ -243367,7 +244102,9 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
243367
244102
|
recordEffects: [],
|
|
243368
244103
|
variableSeeds: [],
|
|
243369
244104
|
objectSeeds: [],
|
|
244105
|
+
fieldMounts: [],
|
|
243370
244106
|
valueChangedEffects: [],
|
|
244107
|
+
tickedRuns: [],
|
|
243371
244108
|
styleSheets: [],
|
|
243372
244109
|
statesMachines: [],
|
|
243373
244110
|
animations: [],
|
|
@@ -243491,6 +244228,10 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
243491
244228
|
const renderedStackIds = [];
|
|
243492
244229
|
const roleOf = (node) => {
|
|
243493
244230
|
const role = renderRole(node, catalog2, kits);
|
|
244231
|
+
if (role === "repeater" && (literalParam(node, "templateType") === "dynamic" || wiredIn.has(`${node.id}:templateType`))) {
|
|
244232
|
+
deferReasons.set(node.id, "its Template Type is dynamic \u2014 each row's component is chosen by the Template Script at run time, and this slice renders one static template");
|
|
244233
|
+
return "unsupported";
|
|
244234
|
+
}
|
|
243494
244235
|
if (role === null || role === "unsupported" || role === "instance" || role === "repeater" || role === "slot") return role;
|
|
243495
244236
|
if (role === "custom") return role;
|
|
243496
244237
|
if (role === "drag") {
|
|
@@ -243801,6 +244542,12 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
243801
244542
|
return plan2 !== void 0 && plan2.origin === "object" ? plan2 : void 0;
|
|
243802
244543
|
};
|
|
243803
244544
|
const objectNodeGate = (node, store2) => {
|
|
244545
|
+
if (literalParam(node, "runOnChange-modelId") === false) {
|
|
244546
|
+
return "Id is unticked under Run On Value Change \u2014 its properties read nothing until a Fetch, and the exported store is read at once";
|
|
244547
|
+
}
|
|
244548
|
+
if (literalParam(node, "runOnChange-object") === false) {
|
|
244549
|
+
return "Object properties is unticked under Run On Value Change \u2014 its outputs freeze between Fetch pulses, and the exported store is read live";
|
|
244550
|
+
}
|
|
243804
244551
|
const propInput2 = component.connections.find((c) => c.toId === node.id && c.toProperty.startsWith("prop-"));
|
|
243805
244552
|
if (propInput2 !== void 0) {
|
|
243806
244553
|
return `its "${propInput2.toProperty.slice("prop-".length)}" property input is wired \u2014 a write through the Object node itself is not translated in this slice; a Set Object Properties naming "${store2.name}" is`;
|
|
@@ -244336,6 +245083,8 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244336
245083
|
const logicNodeIds = [];
|
|
244337
245084
|
const subscriberIds = [];
|
|
244338
245085
|
let anyDelivery = false;
|
|
245086
|
+
let anyLiteral = false;
|
|
245087
|
+
const wireFed = [];
|
|
244339
245088
|
jsResolving.add(node.id);
|
|
244340
245089
|
try {
|
|
244341
245090
|
for (const name of inputNames) {
|
|
@@ -244345,6 +245094,7 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244345
245094
|
return { defer: `two wires feed input "${name}" \u2014 last-writer-wins is not statically ordered` };
|
|
244346
245095
|
}
|
|
244347
245096
|
const runChangeParam = kind === "function" ? `runOnChange-in-${name}` : `runOnChange-${name}`;
|
|
245097
|
+
if (literalParam(node, port5) !== void 0) anyLiteral = true;
|
|
244348
245098
|
if (wires.length === 1) {
|
|
244349
245099
|
const wire = wires[0];
|
|
244350
245100
|
if (mode === "reactive" && literalParam(node, runChangeParam) === false) {
|
|
@@ -244364,6 +245114,7 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244364
245114
|
return { defer: `input "${name}" is fed a logic truth value \u2014 only truthiness sinks take one in this slice` };
|
|
244365
245115
|
}
|
|
244366
245116
|
anyDelivery = true;
|
|
245117
|
+
wireFed.push(name);
|
|
244367
245118
|
const tsType = exprTsType(expr);
|
|
244368
245119
|
inputs.push({
|
|
244369
245120
|
name,
|
|
@@ -244386,8 +245137,12 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244386
245137
|
} finally {
|
|
244387
245138
|
jsResolving.delete(node.id);
|
|
244388
245139
|
}
|
|
244389
|
-
|
|
244390
|
-
|
|
245140
|
+
const evaluateAtLoad = literalParam(node, "evaluateAtLoad") !== false;
|
|
245141
|
+
const loadGate = kind === "expression" && mode === "reactive" && exprIds.ports.length > 0 && !anyLiteral ? { unset: wireFed, atLoad: evaluateAtLoad ? "evaluate" : "wait" } : void 0;
|
|
245142
|
+
if (loadGate?.atLoad === "wait" && !anyDelivery) {
|
|
245143
|
+
return {
|
|
245144
|
+
defer: "Evaluate At Load is unticked and none of its inputs is ever delivered \u2014 the expression never evaluates (its outputs abstain null)"
|
|
245145
|
+
};
|
|
244391
245146
|
}
|
|
244392
245147
|
const outputs2 = [];
|
|
244393
245148
|
const signals = [];
|
|
@@ -244437,6 +245192,7 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244437
245192
|
signals,
|
|
244438
245193
|
mathAliases: exprIds.mathAliases,
|
|
244439
245194
|
mode,
|
|
245195
|
+
...loadGate !== void 0 ? { loadGate } : {},
|
|
244440
245196
|
...scriptUses3.length > 0 ? { scripts: scriptUses3 } : {},
|
|
244441
245197
|
...kind === "visual" ? {
|
|
244442
245198
|
variables: [
|
|
@@ -244472,8 +245228,13 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244472
245228
|
return null;
|
|
244473
245229
|
}
|
|
244474
245230
|
if (jsResolving.size > 0) {
|
|
244475
|
-
|
|
244476
|
-
|
|
245231
|
+
const runKey = `${fromNode.id}:run`;
|
|
245232
|
+
const materialized = kind === "function" && wiredPorts.has(runKey) && !jsResolving.has(fromNode.id) && jsMaterializedVars.has(fromNode.id);
|
|
245233
|
+
if (!materialized) {
|
|
245234
|
+
const compiled2 = kind === "function" ? compiledSinks.get(runKey) : void 0;
|
|
245235
|
+
ctx.defer = compiled2 !== void 0 && "defer" in compiled2 ? `the Function that feeds it is not exported: ${compiled2.defer}` : "it is fed by another Function/Expression node \u2014 JS-node chains are not translated in this slice";
|
|
245236
|
+
return null;
|
|
245237
|
+
}
|
|
244477
245238
|
}
|
|
244478
245239
|
const record2 = jsFunDefOf(fromNode);
|
|
244479
245240
|
if ("defer" in record2) {
|
|
@@ -244502,8 +245263,9 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244502
245263
|
switch (output) {
|
|
244503
245264
|
case "result":
|
|
244504
245265
|
return base;
|
|
244505
|
-
// The abstain-null pre-first-evaluation state is
|
|
244506
|
-
//
|
|
245266
|
+
// The abstain-null pre-first-evaluation state: Result is `null` there (a `loadGate`,
|
|
245267
|
+
// P18-S110-EVALATLOAD), but the four folds below still compute from it where the runtime's
|
|
245268
|
+
// getters abstain (§4, noted not modeled) — `isTrue` reads false, not "nothing yet".
|
|
244507
245269
|
case "isTrue":
|
|
244508
245270
|
return truthyExpr(base);
|
|
244509
245271
|
case "isFalse":
|
|
@@ -244546,6 +245308,19 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244546
245308
|
return stateVar;
|
|
244547
245309
|
};
|
|
244548
245310
|
const verbErrorVars = /* @__PURE__ */ new Map();
|
|
245311
|
+
const verbChainScope = /* @__PURE__ */ new Map();
|
|
245312
|
+
const chainSenders = [];
|
|
245313
|
+
const valueWiresInto = (node) => component.connections.filter((c) => c.toId === node.id && c.toProperty === "value" && c.kind !== "signal");
|
|
245314
|
+
const lastValueWireOf = (node) => {
|
|
245315
|
+
const wires = valueWiresInto(node);
|
|
245316
|
+
if (wires.length < 2) return wires[0] ?? component.connections.find((c) => c.toId === node.id && c.toProperty === "value");
|
|
245317
|
+
for (const sender of [...chainSenders].reverse()) {
|
|
245318
|
+
const own2 = wires.filter((c) => c.fromId === sender);
|
|
245319
|
+
if (own2.length === 1) return own2[0];
|
|
245320
|
+
if (own2.length > 1) return void 0;
|
|
245321
|
+
}
|
|
245322
|
+
return void 0;
|
|
245323
|
+
};
|
|
244549
245324
|
const attachedRecordVerbs = /* @__PURE__ */ new Set();
|
|
244550
245325
|
const sessionReadGate = (node) => {
|
|
244551
245326
|
if (wiredPorts.has(`${node.id}:fetch`)) {
|
|
@@ -244763,7 +245538,8 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244763
245538
|
null,
|
|
244764
245539
|
node.id,
|
|
244765
245540
|
"record-row-error",
|
|
244766
|
-
|
|
245541
|
+
// P18-S113-IDBINDREAD. With Fetch unwired the node is the Id bind, which reads nothing and so reports nothing.
|
|
245542
|
+
wiredPorts.has(`${node.id}:fetch`) ? `The Error output of ${node.authoredLabel ? `"${node.authoredLabel}"` : "the Record"} \u2014 why the last read failed, and never cleared by a later success (dbmodelnode2.ts).` : `The Error output of ${node.authoredLabel ? `"${node.authoredLabel}"` : "the Record"} \u2014 its Fetch is unwired and only a Fetch reports one, so it stays empty (dbmodelnode2.ts).`
|
|
244767
245543
|
);
|
|
244768
245544
|
recordErrorVars.set(node.id, stateVar);
|
|
244769
245545
|
}
|
|
@@ -244781,16 +245557,27 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
244781
245557
|
null,
|
|
244782
245558
|
node.id,
|
|
244783
245559
|
"record-row",
|
|
244784
|
-
`The record the last ${names2.fnName} read \u2014 undefined until the first read, as the node's property outputs read before one has been made.`
|
|
245560
|
+
wiredPorts.has(`${node.id}:fetch`) ? `The record the last ${names2.fnName} read \u2014 undefined until the first read, as the node's property outputs read before one has been made.` : `The app's copy of the record ${node.authoredLabel ? `"${node.authoredLabel}"` : "the Record"}'s Id names \u2014 undefined until the app holds it, as the node's property outputs read before anything has filled the record (dbmodelnode2.ts setModelID).`
|
|
244785
245561
|
);
|
|
244786
245562
|
recordAnswerVars.set(node.id, stateVar);
|
|
244787
245563
|
}
|
|
244788
245564
|
return stateVar;
|
|
244789
245565
|
};
|
|
244790
245566
|
const attachedRecordNodes = /* @__PURE__ */ new Set();
|
|
245567
|
+
const recordBindRefusal = (node) => {
|
|
245568
|
+
if (literalParam(node, "runOnChange-modelId") === false) {
|
|
245569
|
+
return "its Id is unticked under Run On Value Change and its Fetch is unwired \u2014 the runtime never reads the row";
|
|
245570
|
+
}
|
|
245571
|
+
const pulse = component.connections.find((c) => c.fromId === node.id && (c.fromProperty === "done" || c.fromProperty === "failure"));
|
|
245572
|
+
if (pulse !== void 0) {
|
|
245573
|
+
return `its ${pulse.fromProperty === "done" ? "Done" : "Failure"} output is consumed and its Fetch is unwired \u2014 both fire only after a Fetch, and an Id only binds the record it names (dbmodelnode2.ts setModelID)`;
|
|
245574
|
+
}
|
|
245575
|
+
return void 0;
|
|
245576
|
+
};
|
|
244791
245577
|
const recordWillFire = (node) => {
|
|
244792
245578
|
if (wiredPorts.has(`${node.id}:fetch`)) return false;
|
|
244793
245579
|
if (!plan.file) return false;
|
|
245580
|
+
if (recordBindRefusal(node) !== void 0) return false;
|
|
244794
245581
|
const compiled2 = compiledOf(node, "fetch");
|
|
244795
245582
|
return !("defer" in compiled2) && actionsValidIn([compiled2.action], { kind: "render" });
|
|
244796
245583
|
};
|
|
@@ -245077,9 +245864,53 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
245077
245864
|
const usedQueryStateNames = /* @__PURE__ */ new Set();
|
|
245078
245865
|
const queryReaders = /* @__PURE__ */ new Set();
|
|
245079
245866
|
const staticRowsReadIds = /* @__PURE__ */ new Set();
|
|
245867
|
+
const querySettingsCache = /* @__PURE__ */ new Map();
|
|
245868
|
+
const queryWirings = /* @__PURE__ */ new Map();
|
|
245869
|
+
const querySettingsFor = (node) => {
|
|
245870
|
+
let settings = querySettingsCache.get(node.id);
|
|
245871
|
+
if (settings === void 0) {
|
|
245872
|
+
querySettingsCache.set(node.id, { refuse: "its Filter reads a value that depends on the query itself" });
|
|
245873
|
+
settings = querySettingsOf(node, component, ir.project.collections);
|
|
245874
|
+
if ("params" in settings && settings.params.wired !== void 0) settings = wireQueryValues(node, settings.params.wired.params) ?? settings;
|
|
245875
|
+
querySettingsCache.set(node.id, settings);
|
|
245876
|
+
}
|
|
245877
|
+
return settings;
|
|
245878
|
+
};
|
|
245879
|
+
const wireQueryValues = (node, names2) => {
|
|
245880
|
+
const wiring = { values: [], consumes: [], collapses: [], subscribes: [] };
|
|
245881
|
+
for (const name of names2) {
|
|
245882
|
+
const wire = component.connections.find((c) => c.toId === node.id && c.toProperty === `qp-${name}`);
|
|
245883
|
+
if (wire === void 0) return { refuse: `its "${name}" input has no wire` };
|
|
245884
|
+
const ctx = newCtx();
|
|
245885
|
+
const expr = resolveExpr(nodeById.get(wire.fromId), wire.fromProperty, ctx);
|
|
245886
|
+
if (expr === null) return { refuse: `its Filter reads "${name}" from a wire the export cannot read: ${ctx.defer ?? "no statically known source"}` };
|
|
245887
|
+
if (!exprValidIn(expr, { kind: "render" })) return { refuse: `its Filter reads "${name}" from a value that only exists inside a handler` };
|
|
245888
|
+
wiring.values.push({ name, expr });
|
|
245889
|
+
wiring.consumes.push(wire.key, ...ctx.consumes);
|
|
245890
|
+
wiring.collapses.push(...ctx.logicNodeIds);
|
|
245891
|
+
wiring.subscribes.push(...ctx.subscriberIds);
|
|
245892
|
+
}
|
|
245893
|
+
queryWirings.set(node.id, wiring);
|
|
245894
|
+
return void 0;
|
|
245895
|
+
};
|
|
245896
|
+
const queryLands = (node) => Object.values(plan.repeaters).some((r) => r.itemsQueryId === node.id) || queryReaders.has(node.id) && component.connections.some((c) => c.fromId === node.id && c.fromProperty === "items" && consumed.has(c.key));
|
|
245897
|
+
const pageMountFetch = (wire) => wire.toProperty === "storageFetch" && wire.fromProperty === "didMount" && nodeById.get(wire.fromId)?.type === "Page" && nodeById.get(wire.toId)?.type === "DbCollection2";
|
|
245898
|
+
const fetchWiredOf = (node) => {
|
|
245899
|
+
const fetches2 = component.connections.filter((c) => c.toId === node.id && c.toProperty === "storageFetch");
|
|
245900
|
+
if (fetches2.length === 0) return void 0;
|
|
245901
|
+
const ticked3 = (input) => literalParam(node, `runOnChange-${input}`) !== false;
|
|
245902
|
+
const NOT_QUERY_SETTINGS = /* @__PURE__ */ new Set(["collectionName", "search", "realtime"]);
|
|
245903
|
+
const setting4 = node.parameters.some((p) => !NOT_QUERY_SETTINGS.has(p.name) && !p.name.startsWith("qp-") && !p.name.startsWith("runOnChange-"));
|
|
245904
|
+
const wired = queryWirings.get(node.id)?.values ?? [];
|
|
245905
|
+
return {
|
|
245906
|
+
mount: ticked3("collectionName") || setting4 && ticked3("querySettings") || fetches2.some(pageMountFetch),
|
|
245907
|
+
onValues: wired.length > 0 && wired.every((v) => ticked3(`qp-${v.name}`))
|
|
245908
|
+
};
|
|
245909
|
+
};
|
|
245080
245910
|
const queryPlanOf = (node) => {
|
|
245081
245911
|
const existing = plan.queries.find((q) => q.nodeId === node.id);
|
|
245082
245912
|
if (existing !== void 0) return existing;
|
|
245913
|
+
const settings = querySettingsFor(node);
|
|
245083
245914
|
const collectionName4 = String(literalParam(node, "collectionName") ?? "Record");
|
|
245084
245915
|
const { typeName: typeName3, plural: plural3, moduleBase, fetchName } = collectionModuleNames(collectionName4);
|
|
245085
245916
|
const stateName = dedupe4(plural3, usedQueryStateNames);
|
|
@@ -245091,7 +245922,10 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
|
|
|
245091
245922
|
itemName: typeName3.charAt(0).toLowerCase() + typeName3.slice(1),
|
|
245092
245923
|
fetchName,
|
|
245093
245924
|
typeName: typeName3,
|
|
245094
|
-
moduleBase
|
|
245925
|
+
moduleBase,
|
|
245926
|
+
..."params" in settings && Object.keys(settings.params).length > 0 ? { params: settings.params } : {},
|
|
245927
|
+
...queryWirings.has(node.id) ? { wiredValues: queryWirings.get(node.id).values, wiredClient: ir.project.cloudservices !== void 0 } : {},
|
|
245928
|
+
...fetchWiredOf(node) !== void 0 ? { fetchWired: fetchWiredOf(node) } : {}
|
|
245095
245929
|
};
|
|
245096
245930
|
plan.queries.push(query2);
|
|
245097
245931
|
return query2;
|
|
@@ -245774,6 +246608,12 @@ ${body}
|
|
|
245774
246608
|
ctx.defer = `its ${wiredSetting.slice(node.id.length + 1)} setting is wired \u2014 the filter is not statically known`;
|
|
245775
246609
|
return null;
|
|
245776
246610
|
}
|
|
246611
|
+
for (const box of ["items", "array"]) {
|
|
246612
|
+
if (literalParam(node, `runOnChange-${box}`) === false) {
|
|
246613
|
+
ctx.defer = `its Run On Value Change box for "${box === "items" ? "Items" : "Array changes"}" is unticked \u2014 it would re-filter only on its Filter signal, which this slice does not translate`;
|
|
246614
|
+
return null;
|
|
246615
|
+
}
|
|
246616
|
+
}
|
|
245777
246617
|
if (literalParam(node, "enabled") === false) {
|
|
245778
246618
|
ctx.logicNodeIds.push(node.id);
|
|
245779
246619
|
return source;
|
|
@@ -246018,6 +246858,11 @@ ${body}
|
|
|
246018
246858
|
ctx.defer = "the Query Records it reads names no Class statically";
|
|
246019
246859
|
return null;
|
|
246020
246860
|
}
|
|
246861
|
+
const settings = querySettingsFor(fromNode);
|
|
246862
|
+
if ("refuse" in settings) {
|
|
246863
|
+
ctx.defer = `the Query Records it reads is not exported: ${settings.refuse}`;
|
|
246864
|
+
return null;
|
|
246865
|
+
}
|
|
246021
246866
|
const query2 = queryPlanOf(fromNode);
|
|
246022
246867
|
queryReaders.add(fromNode.id);
|
|
246023
246868
|
return { kind: "query-get", nodeId: fromNode.id, stateName: query2.stateName, typeName: query2.typeName, collectionName: query2.collectionName };
|
|
@@ -246243,6 +247088,10 @@ ${body}
|
|
|
246243
247088
|
}
|
|
246244
247089
|
if (fromNode.type === "Variable2" && fromProperty === "value") {
|
|
246245
247090
|
const name = variableNameOf(fromNode);
|
|
247091
|
+
if (name !== void 0 && literalParam(fromNode, "runOnChange-variable") === false) {
|
|
247092
|
+
ctx.defer = VARIABLE_FROZEN;
|
|
247093
|
+
return null;
|
|
247094
|
+
}
|
|
246246
247095
|
return name !== void 0 ? { kind: "store-get", variableName: name } : null;
|
|
246247
247096
|
}
|
|
246248
247097
|
if (fromNode.type === GLOBAL_STORE_SUBSCRIBE && fromProperty === "value") {
|
|
@@ -246296,6 +247145,9 @@ ${body}
|
|
|
246296
247145
|
}
|
|
246297
247146
|
}
|
|
246298
247147
|
if ((RECORD_VERBS[fromNode.type] !== void 0 || RELATION_VERBS[fromNode.type] !== void 0 || USER_VERBS[fromNode.type] !== void 0) && fromProperty === "error") {
|
|
247148
|
+
if (verbChainScope.get(fromNode.id) === "failure") {
|
|
247149
|
+
return { kind: "outcome-error", nodeId: fromNode.id, local: `${verbErrorStateOf(fromNode).name}Message` };
|
|
247150
|
+
}
|
|
246299
247151
|
if (!attachedRecordVerbs.has(fromNode.id)) {
|
|
246300
247152
|
const trigger = USER_VERBS[fromNode.type]?.trigger ?? "store";
|
|
246301
247153
|
const compiled2 = compiledOf(fromNode, trigger);
|
|
@@ -246992,6 +247844,72 @@ ${body}
|
|
|
246992
247844
|
ctx.logicNodeIds.push(node.id);
|
|
246993
247845
|
return output === "result" ? truthyExpr(cond) : notExpr(cond);
|
|
246994
247846
|
};
|
|
247847
|
+
const SNAPSHOT_SINK_PORTS = /* @__PURE__ */ new Set(["mounted", "visible"]);
|
|
247848
|
+
const snapshotWiresOf = (node) => component.connections.filter((c) => c.fromId === node.id && (c.fromProperty === "result" || c.fromProperty === "isfalse"));
|
|
247849
|
+
let snapshotVerdictMemo;
|
|
247850
|
+
const snapshotVerdicts = () => {
|
|
247851
|
+
if (snapshotVerdictMemo !== void 0) return snapshotVerdictMemo;
|
|
247852
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
247853
|
+
for (const node of component.nodes) {
|
|
247854
|
+
if (node.type !== "Condition" || literalParam(node, "runOnChange-condition") !== false) continue;
|
|
247855
|
+
if (!wiredPorts.has(`${node.id}:eval`)) continue;
|
|
247856
|
+
const wires = snapshotWiresOf(node);
|
|
247857
|
+
if (wires.length === 0) continue;
|
|
247858
|
+
let reason2 = null;
|
|
247859
|
+
for (const w of wires) {
|
|
247860
|
+
const sink = nodeById.get(w.toId);
|
|
247861
|
+
if (!SNAPSHOT_SINK_PORTS.has(w.toProperty)) {
|
|
247862
|
+
reason2 = `its ${w.fromProperty} feeds ${sink?.type ?? "a missing node"}.${w.toProperty} \u2014 a snapshot of the last Evaluate lands only in a Mounted or a Visible in this slice`;
|
|
247863
|
+
} else if (!sink || !rendered2.has(sink.id)) {
|
|
247864
|
+
reason2 = `its ${w.fromProperty} feeds an element that is not drawn \u2014 a snapshot lands only in a drawn element's Mounted or Visible in this slice`;
|
|
247865
|
+
} else if (w.toProperty === "mounted" && sink.id === plan.rootId) {
|
|
247866
|
+
reason2 = "a mounted wire into the component root is a router concern \u2014 not translated in this slice";
|
|
247867
|
+
}
|
|
247868
|
+
if (reason2 !== null) break;
|
|
247869
|
+
}
|
|
247870
|
+
verdicts.set(node.id, reason2);
|
|
247871
|
+
}
|
|
247872
|
+
for (let changed = true; changed; ) {
|
|
247873
|
+
changed = false;
|
|
247874
|
+
for (const [id, verdict] of verdicts) {
|
|
247875
|
+
if (verdict !== null) continue;
|
|
247876
|
+
for (const w of snapshotWiresOf(nodeById.get(id))) {
|
|
247877
|
+
const feeders = component.connections.filter((c) => c.toId === w.toId && c.toProperty === w.toProperty);
|
|
247878
|
+
const isSnapshot = (c) => (c.fromProperty === "result" || c.fromProperty === "isfalse") && verdicts.has(c.fromId);
|
|
247879
|
+
const outsider = feeders.find((c) => !isSnapshot(c));
|
|
247880
|
+
const peer = feeders.find((c) => isSnapshot(c) && verdicts.get(c.fromId) !== null);
|
|
247881
|
+
if (outsider === void 0 && peer === void 0) continue;
|
|
247882
|
+
verdicts.set(
|
|
247883
|
+
id,
|
|
247884
|
+
outsider !== void 0 ? `${w.toId}.${w.toProperty} is also fed by ${nodeById.get(outsider.fromId)?.type ?? "a missing node"}.${outsider.fromProperty}, which is not an Evaluate snapshot this slice carries \u2014 the runtime shows whichever wrote last` : `${w.toId}.${w.toProperty} is also written by Condition ${peer.fromId}, which does not translate here \u2014 the runtime shows whichever wrote last`
|
|
247885
|
+
);
|
|
247886
|
+
changed = true;
|
|
247887
|
+
break;
|
|
247888
|
+
}
|
|
247889
|
+
}
|
|
247890
|
+
}
|
|
247891
|
+
snapshotVerdictMemo = verdicts;
|
|
247892
|
+
return verdicts;
|
|
247893
|
+
};
|
|
247894
|
+
const snapshotCells = /* @__PURE__ */ new Map();
|
|
247895
|
+
const snapshotCellOf = (toId, toProperty) => {
|
|
247896
|
+
const key2 = `${toId}\0${toProperty}`;
|
|
247897
|
+
let cell = snapshotCells.get(key2);
|
|
247898
|
+
if (cell === void 0) {
|
|
247899
|
+
const sink = nodeById.get(toId);
|
|
247900
|
+
cell = allocStateVar(
|
|
247901
|
+
sink?.authoredLabel === void 0 ? void 0 : `${sink.authoredLabel} ${toProperty}`,
|
|
247902
|
+
toProperty === "mounted" ? "shown" : "visible",
|
|
247903
|
+
"boolean",
|
|
247904
|
+
false,
|
|
247905
|
+
toId,
|
|
247906
|
+
"condition",
|
|
247907
|
+
`${toProperty === "mounted" ? "Mounted" : "Visible"} of ${sink?.authoredLabel ? `"${sink.authoredLabel}"` : toId}, written by the Evaluate of the Condition${component.connections.filter((c) => c.toId === toId && c.toProperty === toProperty).length > 1 ? "s" : ""} wired into it \u2014 false until the first one (EXP-011 \xA787).`
|
|
247908
|
+
);
|
|
247909
|
+
snapshotCells.set(key2, cell);
|
|
247910
|
+
}
|
|
247911
|
+
return cell;
|
|
247912
|
+
};
|
|
246995
247913
|
const formatExprOf = (node, ctx) => {
|
|
246996
247914
|
if (wiredPorts.has(`${node.id}:format`)) {
|
|
246997
247915
|
ctx.defer = "the format string is wired, not literal";
|
|
@@ -247234,6 +248152,8 @@ ${body}
|
|
|
247234
248152
|
[CLOUD_FUNCTION_TYPE2]: "call",
|
|
247235
248153
|
// EXP-011 §43. `Record`'s only action port; with it unwired the node is an effect (§43.2).
|
|
247236
248154
|
[RECORD_TYPE]: "fetch",
|
|
248155
|
+
// P18-S116-QUERYFETCH. `Query Records`' only action port (display name "Do"); with it unwired the node is an effect.
|
|
248156
|
+
DbCollection2: "storageFetch",
|
|
247237
248157
|
// EXP-011 §45. The three files nodes' only action ports (`Cloud File` has none — it is a read).
|
|
247238
248158
|
[FILE_PICKER_TYPE]: "open",
|
|
247239
248159
|
[UPLOAD_FILE_TYPE]: "upload",
|
|
@@ -247309,6 +248229,14 @@ ${body}
|
|
|
247309
248229
|
return taken.has("onClose") ? 'a declared port already claims the reserved prop "onClose" \u2014 rename the port (POPUPS-TARGET \xA74)' : void 0;
|
|
247310
248230
|
})();
|
|
247311
248231
|
const doneChainOf = (node, port5 = "done") => {
|
|
248232
|
+
chainSenders.push(node.id);
|
|
248233
|
+
try {
|
|
248234
|
+
return doneChainOfSender(node, port5);
|
|
248235
|
+
} finally {
|
|
248236
|
+
chainSenders.pop();
|
|
248237
|
+
}
|
|
248238
|
+
};
|
|
248239
|
+
const doneChainOfSender = (node, port5) => {
|
|
247312
248240
|
const then2 = [];
|
|
247313
248241
|
const consumes = [];
|
|
247314
248242
|
const collapses = [];
|
|
@@ -247340,6 +248268,18 @@ ${body}
|
|
|
247340
248268
|
}
|
|
247341
248269
|
return { then: then2, consumes, collapses, subscribes };
|
|
247342
248270
|
};
|
|
248271
|
+
const verbFailChainOf = (node) => {
|
|
248272
|
+
const outer = verbChainScope.get(node.id);
|
|
248273
|
+
verbChainScope.set(node.id, "failure");
|
|
248274
|
+
let chain;
|
|
248275
|
+
try {
|
|
248276
|
+
chain = doneChainOf(node, "failure");
|
|
248277
|
+
} finally {
|
|
248278
|
+
if (outer === void 0) verbChainScope.delete(node.id);
|
|
248279
|
+
else verbChainScope.set(node.id, outer);
|
|
248280
|
+
}
|
|
248281
|
+
return chain;
|
|
248282
|
+
};
|
|
247343
248283
|
const compileShowPopup = (node) => {
|
|
247344
248284
|
if (wiredPorts.has(`${node.id}:target`)) {
|
|
247345
248285
|
return { defer: "target is wired \u2014 which component opens is not statically knowable" };
|
|
@@ -247455,9 +248395,65 @@ ${body}
|
|
|
247455
248395
|
if (from?.type === "NewModel" && !chainByNewModel.has(from.id)) chainByNewModel.set(from.id, result);
|
|
247456
248396
|
}
|
|
247457
248397
|
}
|
|
248398
|
+
const pulseRefusals = /* @__PURE__ */ new Map();
|
|
248399
|
+
const pulseChainsOf = (node, def) => {
|
|
248400
|
+
const none = { then: [], consumes: [], collapses: [], subscribes: [] };
|
|
248401
|
+
if (def.kind !== "function") return none;
|
|
248402
|
+
const sentWires = component.connections.filter((c) => c.fromId === node.id && def.signals.includes(c.fromProperty.slice("out-".length)));
|
|
248403
|
+
if (sentWires.length === 0) return none;
|
|
248404
|
+
const signalsByPort = /* @__PURE__ */ new Map();
|
|
248405
|
+
for (const c of sentWires) {
|
|
248406
|
+
const port5 = `${c.toId}:${c.toProperty}`;
|
|
248407
|
+
const signals = signalsByPort.get(port5) ?? /* @__PURE__ */ new Set();
|
|
248408
|
+
signals.add(c.fromProperty.slice("out-".length));
|
|
248409
|
+
signalsByPort.set(port5, signals);
|
|
248410
|
+
}
|
|
248411
|
+
for (const [port5, signals] of signalsByPort) {
|
|
248412
|
+
if (signals.size < 2) continue;
|
|
248413
|
+
const target = nodeById.get(port5.slice(0, port5.indexOf(":")));
|
|
248414
|
+
return {
|
|
248415
|
+
defer: `two of its signals (${[...signals].map((s) => `"${s}"`).join(", ")}) drive ${target?.type ?? "a missing node"}.${port5.slice(port5.indexOf(":") + 1)} \u2014 which value each pulse reads there is not translated`
|
|
248416
|
+
};
|
|
248417
|
+
}
|
|
248418
|
+
const arms = [];
|
|
248419
|
+
const result = { then: [], consumes: [], collapses: [], subscribes: [] };
|
|
248420
|
+
for (const signal2 of def.signals) {
|
|
248421
|
+
if (!sentWires.some((c) => c.fromProperty === `out-${signal2}`)) continue;
|
|
248422
|
+
const arm = doneChainOf(node, `out-${signal2}`);
|
|
248423
|
+
if ("defer" in arm) {
|
|
248424
|
+
pulseRefusals.set(`${node.id}:out-${signal2}`, arm.defer.replace(`its out-${signal2} output`, `its "${signal2}" signal`));
|
|
248425
|
+
continue;
|
|
248426
|
+
}
|
|
248427
|
+
arms.push({ kind: "jsfun-pulse", nodeId: node.id, signal: signal2, then: arm.then });
|
|
248428
|
+
result.consumes.push(...arm.consumes);
|
|
248429
|
+
result.collapses.push(...arm.collapses);
|
|
248430
|
+
result.subscribes.push(...arm.subscribes);
|
|
248431
|
+
}
|
|
248432
|
+
if (arms.length > 0) result.then.push({ kind: "jsfun-pulses", nodeId: node.id, then: arms });
|
|
248433
|
+
return result;
|
|
248434
|
+
};
|
|
248435
|
+
const tickedWatchOf = (node, def) => {
|
|
248436
|
+
if (def.kind === "visual") return { watch: [] };
|
|
248437
|
+
const watch = [];
|
|
248438
|
+
for (const input of def.inputs) {
|
|
248439
|
+
if (input.expr === void 0) continue;
|
|
248440
|
+
if (literalParam(node, def.kind === "function" ? `runOnChange-in-${input.name}` : `runOnChange-${input.name}`) === false) continue;
|
|
248441
|
+
if (!exprValidIn(input.expr, { kind: "render" })) {
|
|
248442
|
+
return {
|
|
248443
|
+
defer: `its "${input.name}" input is ticked under Run On Value Change, so a new value runs it again, and that value exists only inside a handler \u2014 the export cannot watch it`
|
|
248444
|
+
};
|
|
248445
|
+
}
|
|
248446
|
+
watch.push(input.expr);
|
|
248447
|
+
}
|
|
248448
|
+
return { watch };
|
|
248449
|
+
};
|
|
248450
|
+
const isRowReader = (reader, port5) => reader.type === JS_FUNCTION && port5.startsWith("in-") || reader.type === "Set Variable" && port5 === "value" || reader.type === "DbModel2" && port5 === "modelId" || reader.type === "SetDbModelProperties" && (port5 === "modelId" || port5.startsWith("prop-")) || reader.type === "NewDbModelProperties" && port5.startsWith("prop-") || reader.type === "DeleteDbModelProperties" && port5 === "modelId" || reader.type === CLOUD_FUNCTION_TYPE2 && port5.startsWith("in-") || reader.type === "DbCollection2" && port5.startsWith("qp-");
|
|
248451
|
+
const tickedRunPieces = /* @__PURE__ */ new Map();
|
|
247458
248452
|
const compileJsRun = (node) => {
|
|
247459
248453
|
const record2 = jsFunDefOf(node);
|
|
247460
248454
|
if ("defer" in record2) return { defer: record2.defer };
|
|
248455
|
+
const ticked3 = tickedWatchOf(node, record2.def);
|
|
248456
|
+
if ("defer" in ticked3) return { defer: ticked3.defer };
|
|
247461
248457
|
for (const c of component.connections) {
|
|
247462
248458
|
if (c.fromId !== node.id || c.fromProperty === "done" || isJsValueOutput(node, c.fromProperty)) continue;
|
|
247463
248459
|
if (node.type === JS_FUNCTION && !c.fromProperty.startsWith("out-")) {
|
|
@@ -247472,19 +248468,68 @@ ${body}
|
|
|
247472
248468
|
}
|
|
247473
248469
|
return { defer: `its ${c.fromProperty} output is consumed \u2014 only done continues a Run chain in this slice` };
|
|
247474
248470
|
}
|
|
247475
|
-
const
|
|
247476
|
-
if ("defer" in
|
|
247477
|
-
const
|
|
248471
|
+
const done = doneChainOf(node);
|
|
248472
|
+
if ("defer" in done) return done;
|
|
248473
|
+
const pulsed = pulseChainsOf(node, record2.def);
|
|
248474
|
+
if ("defer" in pulsed) pulseRefusals.set(node.id, pulsed.defer);
|
|
248475
|
+
const pulses = "defer" in pulsed ? { then: [], consumes: [], collapses: [], subscribes: [] } : pulsed;
|
|
248476
|
+
if (ticked3.watch.length > 0 && !actionsValidIn([{ kind: "jsfun-run", nodeId: node.id, then: pulses.then }], { kind: "render" })) {
|
|
248477
|
+
return {
|
|
248478
|
+
defer: "a ticked input runs it again from no handler, and its signals' chains read values that exist only inside a handler"
|
|
248479
|
+
};
|
|
248480
|
+
}
|
|
248481
|
+
const chain = {
|
|
248482
|
+
then: [...pulses.then, ...done.then],
|
|
248483
|
+
consumes: [...pulses.consumes, ...done.consumes],
|
|
248484
|
+
collapses: [...pulses.collapses, ...done.collapses],
|
|
248485
|
+
subscribes: [...pulses.subscribes, ...done.subscribes]
|
|
248486
|
+
};
|
|
248487
|
+
const strays = component.connections.filter(
|
|
247478
248488
|
(c) => c.fromId === node.id && isJsValueOutput(node, c.fromProperty) && !chain.consumes.includes(c.key)
|
|
247479
248489
|
);
|
|
247480
|
-
|
|
247481
|
-
|
|
247482
|
-
|
|
247483
|
-
|
|
248490
|
+
const signalTargetOf = (toId) => record2.def.kind !== "function" ? void 0 : component.connections.find((c) => c.fromId === node.id && c.toId === toId && record2.def.signals.includes(c.fromProperty.slice("out-".length)));
|
|
248491
|
+
const missingSink = strays.find((c) => nodeById.get(c.toId) === void 0);
|
|
248492
|
+
if (missingSink) {
|
|
248493
|
+
return { defer: `its ${missingSink.fromProperty} output is consumed outside the Run chain by a missing node` };
|
|
248494
|
+
}
|
|
248495
|
+
const ownSignalStray = (c) => {
|
|
248496
|
+
if (rendered2.has(c.toId)) return false;
|
|
248497
|
+
const signalWire = signalTargetOf(c.toId);
|
|
248498
|
+
return signalWire !== void 0 && (pulseRefusals.has(`${node.id}:${signalWire.fromProperty}`) || pulseRefusals.has(node.id));
|
|
248499
|
+
};
|
|
248500
|
+
const headMaterialized = strays.length > 0 && rendered2.has(strays[0].toId);
|
|
248501
|
+
const unreadable = headMaterialized ? void 0 : strays.find(
|
|
248502
|
+
(c) => !rendered2.has(c.toId) && !ownSignalStray(c) && (record2.def.kind !== "function" || !isRowReader(nodeById.get(c.toId), c.toProperty))
|
|
248503
|
+
);
|
|
248504
|
+
if (unreadable) {
|
|
248505
|
+
return {
|
|
248506
|
+
defer: `its ${unreadable.fromProperty} output is consumed outside the Run chain by an unrendered sink \u2014 the last run's value is not statically expressible there`
|
|
248507
|
+
};
|
|
248508
|
+
}
|
|
248509
|
+
const readers = headMaterialized ? strays : strays.filter((c) => !ownSignalStray(c));
|
|
248510
|
+
const runWires = component.connections.filter((c) => c.toId === node.id && c.toProperty === "run");
|
|
248511
|
+
for (const reader of readers) {
|
|
248512
|
+
if (rendered2.has(reader.toId)) continue;
|
|
248513
|
+
const early = component.connections.find(
|
|
248514
|
+
(w, i) => w.toId === reader.toId && runWires.some((r) => r.fromId === w.fromId && r.fromProperty === w.fromProperty && i < component.connections.indexOf(r))
|
|
248515
|
+
);
|
|
248516
|
+
if (early) {
|
|
247484
248517
|
return {
|
|
247485
|
-
defer: `its ${
|
|
248518
|
+
defer: `its ${reader.fromProperty} output is read by ${nodeById.get(reader.toId).type}.${reader.toProperty}, whose ${early.toProperty} the same ${early.fromProperty} sends before it runs this \u2014 the runtime's reader waits for the run's value, the export's would read the one before`
|
|
247486
248519
|
};
|
|
247487
248520
|
}
|
|
248521
|
+
}
|
|
248522
|
+
if (readers.length === 0 && strays.length > 0 && chain.then.length === 0) {
|
|
248523
|
+
const stray = strays[0];
|
|
248524
|
+
const signalWire = signalTargetOf(stray.toId);
|
|
248525
|
+
const refused = pulseRefusals.get(`${node.id}:${signalWire.fromProperty}`) ?? pulseRefusals.get(node.id);
|
|
248526
|
+
return {
|
|
248527
|
+
defer: refused !== void 0 ? `its ${stray.fromProperty} output feeds ${nodeById.get(stray.toId).type}.${stray.toProperty}, which its "${signalWire.fromProperty.slice("out-".length)}" signal runs, and that chain is not exported: ${refused}` : `its ${stray.fromProperty} output is consumed outside the Run chain by an unrendered sink \u2014 the last run's value is not statically expressible there`
|
|
248528
|
+
};
|
|
248529
|
+
}
|
|
248530
|
+
const strayRead = readers[0];
|
|
248531
|
+
let materialize;
|
|
248532
|
+
if (strayRead) {
|
|
247488
248533
|
const def = record2.def;
|
|
247489
248534
|
const fields = def.outputs.map(
|
|
247490
248535
|
(o) => `${/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(o.name) ? o.name : JSON.stringify(o.name)}?: ${o.tsType}`
|
|
@@ -247505,6 +248550,13 @@ ${body}
|
|
|
247505
248550
|
if (chain.then.length === 0 && materialize === void 0) {
|
|
247506
248551
|
return { defer: "its Run drives nothing this slice translates \u2014 no done-chain action consumes its work" };
|
|
247507
248552
|
}
|
|
248553
|
+
if (pulses.then.length > 0 || materialize !== void 0 && record2.def.kind === "function") record2.def.pulsed = true;
|
|
248554
|
+
if (ticked3.watch.length > 0 && (pulses.then.length > 0 || materialize !== void 0)) {
|
|
248555
|
+
tickedRunPieces.set(node.id, {
|
|
248556
|
+
watch: ticked3.watch,
|
|
248557
|
+
action: { kind: "jsfun-run", nodeId: node.id, then: pulses.then, ...materialize !== void 0 ? { materialize } : {}, ticked: true }
|
|
248558
|
+
});
|
|
248559
|
+
}
|
|
247508
248560
|
return {
|
|
247509
248561
|
action: {
|
|
247510
248562
|
kind: "jsfun-run",
|
|
@@ -247576,6 +248628,18 @@ ${body}
|
|
|
247576
248628
|
if (literalParam(node, "idSource") === "foreach" || authoredOrWired("repeaterComponent")) {
|
|
247577
248629
|
return { defer: "its Id Source is the enclosing repeater's row \u2014 row identity is not statically knowable in this slice" };
|
|
247578
248630
|
}
|
|
248631
|
+
const namesProperties = (name) => {
|
|
248632
|
+
const value = literalParam(node, name);
|
|
248633
|
+
if (wiredPorts.has(`${node.id}:${name}`)) return true;
|
|
248634
|
+
if (typeof value !== "string") return false;
|
|
248635
|
+
return name === "upsertOn" ? value.trim() !== "" : value.split(",").some((n) => n.trim() !== "");
|
|
248636
|
+
};
|
|
248637
|
+
if (verb === "create" && namesProperties("upsertOn")) {
|
|
248638
|
+
return { defer: "its Upsert On names a unique property \u2014 a clash would update the existing row in the runtime, and the exported create always creates" };
|
|
248639
|
+
}
|
|
248640
|
+
if (verb === "update" && namesProperties("onlyIfUnchanged")) {
|
|
248641
|
+
return { defer: "its Only If Unchanged names properties \u2014 the runtime writes only if they still hold what the record was read with, and the exported update writes unconditionally" };
|
|
248642
|
+
}
|
|
247579
248643
|
if (verb === "update") {
|
|
247580
248644
|
if (literalParam(node, "storeType") === "local" || wiredPorts.has(`${node.id}:storeType`)) {
|
|
247581
248645
|
return { defer: "Store to is Local only \u2014 an in-memory-only write, and the export holds no record to write into" };
|
|
@@ -247591,8 +248655,8 @@ ${body}
|
|
|
247591
248655
|
};
|
|
247592
248656
|
}
|
|
247593
248657
|
for (const wire of component.connections.filter((c) => c.fromId === node.id)) {
|
|
247594
|
-
if (wire.fromProperty === "
|
|
247595
|
-
return { defer: `its ${wire.fromProperty} output is consumed \u2014 only the done
|
|
248658
|
+
if (wire.fromProperty === "completed") {
|
|
248659
|
+
return { defer: `its ${wire.fromProperty} output is consumed \u2014 only the done and failure chains and the Error value are translated in this slice` };
|
|
247596
248660
|
}
|
|
247597
248661
|
if (wire.fromProperty === "id") {
|
|
247598
248662
|
return { defer: "its Id output is consumed \u2014 the record it names exists only inside the invoking chain, which the relation verbs would need" };
|
|
@@ -247649,6 +248713,8 @@ ${body}
|
|
|
247649
248713
|
}
|
|
247650
248714
|
const chain = doneChainOf(node);
|
|
247651
248715
|
if ("defer" in chain) return { defer: chain.defer };
|
|
248716
|
+
const failChain = verbFailChainOf(node);
|
|
248717
|
+
if ("defer" in failChain) return { defer: failChain.defer };
|
|
247652
248718
|
const { typeName: typeName3, moduleBase } = collectionModuleNames(collectionName4);
|
|
247653
248719
|
const fnName = `${verb}${typeName3}`;
|
|
247654
248720
|
plan.mutations.push({
|
|
@@ -247678,11 +248744,12 @@ ${body}
|
|
|
247678
248744
|
// EXP-011 §72. An `unknown` id (an untyped Variable, a store key nothing types) into a `string` parameter.
|
|
247679
248745
|
...idExpr !== void 0 && idExpr.kind !== "literal" && exprTsType(idExpr) === "unknown" ? { coerceId: "string" } : {},
|
|
247680
248746
|
errorState: verbErrorStateOf(node).name,
|
|
247681
|
-
then: chain.then
|
|
248747
|
+
then: chain.then,
|
|
248748
|
+
failThen: failChain.then
|
|
247682
248749
|
},
|
|
247683
|
-
consumes: [...consumes, ...chain.consumes, ...ctx.consumes],
|
|
247684
|
-
collapses: [...ctx.logicNodeIds, ...chain.collapses],
|
|
247685
|
-
subscribes: [...ctx.subscriberIds, ...chain.subscribes]
|
|
248750
|
+
consumes: [...consumes, ...chain.consumes, ...failChain.consumes, ...ctx.consumes],
|
|
248751
|
+
collapses: [...ctx.logicNodeIds, ...chain.collapses, ...failChain.collapses],
|
|
248752
|
+
subscribes: [...ctx.subscriberIds, ...chain.subscribes, ...failChain.subscribes]
|
|
247686
248753
|
};
|
|
247687
248754
|
};
|
|
247688
248755
|
const compileRelationOp = (node) => {
|
|
@@ -247701,8 +248768,8 @@ ${body}
|
|
|
247701
248768
|
return { defer: "its Id Source is the enclosing repeater's row \u2014 row identity is not statically knowable in this slice" };
|
|
247702
248769
|
}
|
|
247703
248770
|
for (const wire of component.connections.filter((c) => c.fromId === node.id)) {
|
|
247704
|
-
if (wire.fromProperty === "
|
|
247705
|
-
return { defer: `its ${wire.fromProperty} output is consumed \u2014 only the done
|
|
248771
|
+
if (wire.fromProperty === "completed") {
|
|
248772
|
+
return { defer: `its ${wire.fromProperty} output is consumed \u2014 only the done and failure chains and the Error value are translated in this slice` };
|
|
247706
248773
|
}
|
|
247707
248774
|
if (wire.fromProperty === "id") {
|
|
247708
248775
|
return { defer: "its Id output is consumed \u2014 it republishes the Id it was given, and that read is not translated in this slice" };
|
|
@@ -247752,6 +248819,8 @@ ${body}
|
|
|
247752
248819
|
consumes.push(targetWires[0].key);
|
|
247753
248820
|
const chain = doneChainOf(node);
|
|
247754
248821
|
if ("defer" in chain) return { defer: chain.defer };
|
|
248822
|
+
const failChain = verbFailChainOf(node);
|
|
248823
|
+
if ("defer" in failChain) return { defer: failChain.defer };
|
|
247755
248824
|
const { typeName: typeName3, moduleBase } = collectionModuleNames(collectionName4);
|
|
247756
248825
|
const fnName = `${spec.fnPrefix}${typeName3}Relation`;
|
|
247757
248826
|
plan.mutations.push({ nodeId: node.id, verb: spec.verb, collectionName: collectionName4, fnName, typeName: typeName3, moduleBase, writes: [] });
|
|
@@ -247776,11 +248845,12 @@ ${body}
|
|
|
247776
248845
|
guardId: false,
|
|
247777
248846
|
...guards.length > 0 ? { guards } : {},
|
|
247778
248847
|
errorState: verbErrorStateOf(node).name,
|
|
247779
|
-
then: chain.then
|
|
248848
|
+
then: chain.then,
|
|
248849
|
+
failThen: failChain.then
|
|
247780
248850
|
},
|
|
247781
|
-
consumes: [...consumes, ...chain.consumes, ...ctx.consumes],
|
|
247782
|
-
collapses: [...ctx.logicNodeIds, ...chain.collapses],
|
|
247783
|
-
subscribes: [...ctx.subscriberIds, ...chain.subscribes]
|
|
248851
|
+
consumes: [...consumes, ...chain.consumes, ...failChain.consumes, ...ctx.consumes],
|
|
248852
|
+
collapses: [...ctx.logicNodeIds, ...chain.collapses, ...failChain.collapses],
|
|
248853
|
+
subscribes: [...ctx.subscriberIds, ...chain.subscribes, ...failChain.subscribes]
|
|
247784
248854
|
};
|
|
247785
248855
|
};
|
|
247786
248856
|
const compileUserOp = (node) => {
|
|
@@ -247788,9 +248858,9 @@ ${body}
|
|
|
247788
248858
|
const ctx = newCtx();
|
|
247789
248859
|
const consumes = [];
|
|
247790
248860
|
for (const wire of component.connections.filter((c) => c.fromId === node.id)) {
|
|
247791
|
-
if (wire.fromProperty === "
|
|
248861
|
+
if (wire.fromProperty === "completed") {
|
|
247792
248862
|
return {
|
|
247793
|
-
defer: `its ${wire.fromProperty} output is consumed \u2014 only the done
|
|
248863
|
+
defer: `its ${wire.fromProperty} output is consumed \u2014 only the done and failure chains and the Error value are translated in this slice`
|
|
247794
248864
|
};
|
|
247795
248865
|
}
|
|
247796
248866
|
}
|
|
@@ -247850,6 +248920,8 @@ ${body}
|
|
|
247850
248920
|
}
|
|
247851
248921
|
const chain = doneChainOf(node);
|
|
247852
248922
|
if ("defer" in chain) return { defer: chain.defer };
|
|
248923
|
+
const failChain = verbFailChainOf(node);
|
|
248924
|
+
if ("defer" in failChain) return { defer: failChain.defer };
|
|
247853
248925
|
const call5 = { nodeId: node.id, verb: spec.verb, fnName: spec.fnName };
|
|
247854
248926
|
if (spec.columns === true) {
|
|
247855
248927
|
call5.writes = columns.map(({ key: key2, expr }) => {
|
|
@@ -247874,11 +248946,12 @@ ${body}
|
|
|
247874
248946
|
guardId: false,
|
|
247875
248947
|
errorState: verbErrorStateOf(node).name,
|
|
247876
248948
|
...spec.clearsErrorOnDone === true ? { clearErrorOnDone: true } : {},
|
|
247877
|
-
then: chain.then
|
|
248949
|
+
then: chain.then,
|
|
248950
|
+
failThen: failChain.then
|
|
247878
248951
|
},
|
|
247879
|
-
consumes: [...consumes, ...chain.consumes, ...ctx.consumes],
|
|
247880
|
-
collapses: [...ctx.logicNodeIds, ...chain.collapses],
|
|
247881
|
-
subscribes: [...ctx.subscriberIds, ...chain.subscribes]
|
|
248952
|
+
consumes: [...consumes, ...chain.consumes, ...failChain.consumes, ...ctx.consumes],
|
|
248953
|
+
collapses: [...ctx.logicNodeIds, ...chain.collapses, ...failChain.collapses],
|
|
248954
|
+
subscribes: [...ctx.subscriberIds, ...chain.subscribes, ...failChain.subscribes]
|
|
247882
248955
|
};
|
|
247883
248956
|
};
|
|
247884
248957
|
const compileHttpFetch = (node) => {
|
|
@@ -247930,7 +249003,7 @@ ${body}
|
|
|
247930
249003
|
const ctx = newCtx();
|
|
247931
249004
|
const args = [];
|
|
247932
249005
|
const takenParams = /* @__PURE__ */ new Set();
|
|
247933
|
-
const
|
|
249006
|
+
const valueOf2 = (port5, name, paramBase) => {
|
|
247934
249007
|
const wires = component.connections.filter((c) => c.toId === node.id && c.toProperty === port5);
|
|
247935
249008
|
if (wires.length > 1) {
|
|
247936
249009
|
return { defer: `two wires feed ${port5} \u2014 last-writer-wins is not statically ordered` };
|
|
@@ -247957,7 +249030,7 @@ ${body}
|
|
|
247957
249030
|
const stringList = (param) => String(literalParam(node, param) ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
247958
249031
|
const collect3 = (names3, portPrefix, paramPrefix, into) => {
|
|
247959
249032
|
for (const name of names3) {
|
|
247960
|
-
const value =
|
|
249033
|
+
const value = valueOf2(`${portPrefix}${name}`, name, `${paramPrefix}${pascalCase2(name.replace(/[^A-Za-z0-9]+/g, " "))}`);
|
|
247961
249034
|
if (isDefer(value)) return value;
|
|
247962
249035
|
if (value !== null) into.push(value);
|
|
247963
249036
|
}
|
|
@@ -247979,7 +249052,7 @@ ${body}
|
|
|
247979
249052
|
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
247980
249053
|
const bodyType = String(literalParam(node, "bodyType") ?? "json");
|
|
247981
249054
|
if (bodyType === "raw") {
|
|
247982
|
-
const raw =
|
|
249055
|
+
const raw = valueOf2("body-raw", "raw", "bodyRaw");
|
|
247983
249056
|
if (isDefer(raw)) return raw;
|
|
247984
249057
|
if (raw !== null) body = { type: "raw", raw };
|
|
247985
249058
|
} else if (bodyType === "json" || bodyType === "form" || bodyType === "urlencoded") {
|
|
@@ -247994,19 +249067,19 @@ ${body}
|
|
|
247994
249067
|
let auth = null;
|
|
247995
249068
|
const authType = String(literalParam(node, "authType") ?? "none");
|
|
247996
249069
|
if (authType === "bearer") {
|
|
247997
|
-
const token =
|
|
249070
|
+
const token = valueOf2("auth-authToken", "authToken", "authToken");
|
|
247998
249071
|
if (isDefer(token)) return token;
|
|
247999
249072
|
if (token !== null) auth = { kind: "bearer", token };
|
|
248000
249073
|
} else if (authType === "basic") {
|
|
248001
|
-
const username =
|
|
249074
|
+
const username = valueOf2("auth-authUsername", "authUsername", "authUsername");
|
|
248002
249075
|
if (isDefer(username)) return username;
|
|
248003
|
-
const password =
|
|
249076
|
+
const password = valueOf2("auth-authPassword", "authPassword", "authPassword");
|
|
248004
249077
|
if (isDefer(password)) return password;
|
|
248005
249078
|
if (username !== null && password !== null) auth = { kind: "basic", username, password };
|
|
248006
249079
|
} else if (authType === "apiKey") {
|
|
248007
|
-
const name =
|
|
249080
|
+
const name = valueOf2("auth-authApiKeyName", "authApiKeyName", "authKeyName");
|
|
248008
249081
|
if (isDefer(name)) return name;
|
|
248009
|
-
const value =
|
|
249082
|
+
const value = valueOf2("auth-authApiKeyValue", "authApiKeyValue", "authKeyValue");
|
|
248010
249083
|
if (isDefer(value)) return value;
|
|
248011
249084
|
if (name !== null && value !== null) {
|
|
248012
249085
|
auth = {
|
|
@@ -248408,7 +249481,9 @@ ${body}
|
|
|
248408
249481
|
answerLocal: names2.answerLocal,
|
|
248409
249482
|
messageLocal: names2.messageLocal,
|
|
248410
249483
|
idLocal: names2.idLocal,
|
|
248411
|
-
reads
|
|
249484
|
+
reads,
|
|
249485
|
+
// With no backend the read is a stub that throws, the row is never set, and there is no client to hold a copy.
|
|
249486
|
+
live: ir.project.cloudservices !== void 0 && literalParam(node, "runOnChange-record") !== false
|
|
248412
249487
|
});
|
|
248413
249488
|
return {
|
|
248414
249489
|
action: {
|
|
@@ -248428,6 +249503,16 @@ ${body}
|
|
|
248428
249503
|
subscribes: [...ctx.subscriberIds, ...chain.subscribes, ...failChain.subscribes]
|
|
248429
249504
|
};
|
|
248430
249505
|
};
|
|
249506
|
+
const compileQueryFetch = (node) => {
|
|
249507
|
+
const settings = querySettingsFor(node);
|
|
249508
|
+
if ("refuse" in settings) return { defer: `the Query Records it fetches is not exported: ${settings.refuse}` };
|
|
249509
|
+
return {
|
|
249510
|
+
action: { kind: "query-fetch", nodeId: node.id, values: queryWirings.get(node.id)?.values ?? [] },
|
|
249511
|
+
consumes: [],
|
|
249512
|
+
collapses: [],
|
|
249513
|
+
subscribes: []
|
|
249514
|
+
};
|
|
249515
|
+
};
|
|
248431
249516
|
const fileOutputsRefusal = (node, valueOutputs, unchanged) => {
|
|
248432
249517
|
for (const wire of component.connections.filter((c) => c.fromId === node.id)) {
|
|
248433
249518
|
const p = wire.fromProperty;
|
|
@@ -249088,6 +250173,10 @@ ${body}
|
|
|
249088
250173
|
const compileRouterNavigate = (node, page) => {
|
|
249089
250174
|
const ctx = newCtx();
|
|
249090
250175
|
const consumes = [];
|
|
250176
|
+
if (wiredPorts.has(`${node.id}:openInNewTab`)) {
|
|
250177
|
+
return { defer: "its Open In New Tab port is wired \u2014 whether it opens a tab or navigates in place is a runtime value" };
|
|
250178
|
+
}
|
|
250179
|
+
const newTab = literalParam(node, "openInNewTab") === true;
|
|
249091
250180
|
const values = /* @__PURE__ */ new Map();
|
|
249092
250181
|
for (const param of node.parameters) {
|
|
249093
250182
|
if (!param.name.startsWith("pm-")) continue;
|
|
@@ -249117,7 +250206,7 @@ ${body}
|
|
|
249117
250206
|
}
|
|
249118
250207
|
const query2 = Array.from(values, ([name, expr]) => ({ name, expr }));
|
|
249119
250208
|
return {
|
|
249120
|
-
action: { kind: "navigate", to: page.urlPath, pathParams, query: query2 },
|
|
250209
|
+
action: { kind: "navigate", to: page.urlPath, pathParams, query: query2, ...newTab ? { newTab: true } : {} },
|
|
249121
250210
|
consumes: [...consumes, ...ctx.consumes],
|
|
249122
250211
|
collapses: ctx.logicNodeIds,
|
|
249123
250212
|
subscribes: ctx.subscriberIds
|
|
@@ -249216,7 +250305,7 @@ ${body}
|
|
|
249216
250305
|
const level = LOG_LEVELS.includes(String(authoredLevel)) ? authoredLevel : "info";
|
|
249217
250306
|
const ctx = newCtx();
|
|
249218
250307
|
const consumes = [];
|
|
249219
|
-
const
|
|
250308
|
+
const valueOf2 = (port5) => {
|
|
249220
250309
|
const wires = component.connections.filter((c) => c.toId === node.id && c.toProperty === port5);
|
|
249221
250310
|
if (wires.length > 1) return { defer: `two wires feed its ${port5} input \u2014 last-writer-wins is not statically ordered` };
|
|
249222
250311
|
if (wires.length === 1) {
|
|
@@ -249231,9 +250320,9 @@ ${body}
|
|
|
249231
250320
|
const literal2 = literalParam(node, port5);
|
|
249232
250321
|
return literal2 === void 0 ? void 0 : { kind: "literal", value: literal2 };
|
|
249233
250322
|
};
|
|
249234
|
-
const message2 =
|
|
250323
|
+
const message2 = valueOf2("message");
|
|
249235
250324
|
if (isDefer(message2)) return message2;
|
|
249236
|
-
const data =
|
|
250325
|
+
const data = valueOf2("data");
|
|
249237
250326
|
if (isDefer(data)) return data;
|
|
249238
250327
|
const done = doneChainOf(node, "done");
|
|
249239
250328
|
if ("defer" in done) return { defer: done.defer };
|
|
@@ -249822,6 +250911,10 @@ ${body}
|
|
|
249822
250911
|
for (const key2 of consumes) consumed.add(key2);
|
|
249823
250912
|
for (const id of collapses) dispositions[id] = { kind: "collapsed", into };
|
|
249824
250913
|
for (const id of subscribes) boundSubscribers.add(id);
|
|
250914
|
+
const errorPort = outputPorts.find((p) => p.name === contract.error);
|
|
250915
|
+
if (errorPort !== void 0) {
|
|
250916
|
+
notes.push(`node ${node.id} (${RUN_TASKS_TYPE}): a failed task is reported without its Template's "${contract.error}" output \u2014 the runtime puts that value in the run-tasks/task-failed error, and the exported host has no value channel from a task`);
|
|
250917
|
+
}
|
|
249825
250918
|
return core;
|
|
249826
250919
|
};
|
|
249827
250920
|
const compileRunTasks = (node, port5) => {
|
|
@@ -250204,12 +251297,14 @@ ${body}
|
|
|
250204
251297
|
if (!Array.isArray(snapped)) return refuse(snapped.defer);
|
|
250205
251298
|
arrive2 = snapped;
|
|
250206
251299
|
}
|
|
251300
|
+
const castToNumber = ["duration", "delay"].filter((input) => exprTsType(input === "duration" ? duration3 : delay) !== "number");
|
|
250207
251301
|
const registered = {
|
|
250208
251302
|
nodeId: node.id,
|
|
250209
251303
|
local: mintLocal(animateLocals, node, "Animated", ""),
|
|
250210
251304
|
target,
|
|
250211
251305
|
duration: duration3,
|
|
250212
251306
|
delay,
|
|
251307
|
+
...castToNumber.length > 0 ? { castToNumber: [...castToNumber] } : {},
|
|
250213
251308
|
ease: easeParam,
|
|
250214
251309
|
...arrive2 !== void 0 ? { arrive: arrive2 } : {},
|
|
250215
251310
|
comment: `${node.authoredLabel ?? "Animate To Value"} \u2014 moves towards its Target Value over Duration after Delay (animate-to-value.ts); the first target is adopted outright.`
|
|
@@ -250270,7 +251365,7 @@ ${body}
|
|
|
250270
251365
|
consumes.push(wire.key);
|
|
250271
251366
|
}
|
|
250272
251367
|
}
|
|
250273
|
-
const
|
|
251368
|
+
const valueOf2 = (port5) => {
|
|
250274
251369
|
let expr;
|
|
250275
251370
|
const authored = node.parameters.find((param) => param.name === port5);
|
|
250276
251371
|
if (authored?.value.kind === "literal") expr = { kind: "literal", value: authored.value.value };
|
|
@@ -250295,7 +251390,7 @@ ${body}
|
|
|
250295
251390
|
}
|
|
250296
251391
|
let expr = resolvedParams.get(name);
|
|
250297
251392
|
if (expr === void 0) {
|
|
250298
|
-
const resolved =
|
|
251393
|
+
const resolved = valueOf2(`p-${name}`);
|
|
250299
251394
|
if (isDefer(resolved)) return { defer: resolved.defer };
|
|
250300
251395
|
expr = resolved ?? { kind: "literal", value: "" };
|
|
250301
251396
|
resolvedParams.set(name, expr);
|
|
@@ -250306,7 +251401,7 @@ ${body}
|
|
|
250306
251401
|
const query2 = [];
|
|
250307
251402
|
if (typeof authoredQueryNames === "string" && authoredQueryNames !== "") {
|
|
250308
251403
|
for (const name of authoredQueryNames.split(",")) {
|
|
250309
|
-
const resolved =
|
|
251404
|
+
const resolved = valueOf2(`q-${name}`);
|
|
250310
251405
|
if (isDefer(resolved)) return { defer: resolved.defer };
|
|
250311
251406
|
if (resolved === void 0) continue;
|
|
250312
251407
|
query2.push({ name, expr: resolved, omittable: maybeUndefinedExpr(resolved) });
|
|
@@ -250423,7 +251518,10 @@ ${body}
|
|
|
250423
251518
|
if (keyType === "number" || keyType === "boolean") {
|
|
250424
251519
|
return { defer: `key "${key2}" is ${keyType}-typed by the initial state; only string writes translate in this slice` };
|
|
250425
251520
|
}
|
|
250426
|
-
const wire2 =
|
|
251521
|
+
const wire2 = lastValueWireOf(node);
|
|
251522
|
+
if (wire2 === void 0 && valueWiresInto(node).length > 1) {
|
|
251523
|
+
return { defer: "two wires feed its Value input \u2014 last-writer-wins is not statically ordered" };
|
|
251524
|
+
}
|
|
250427
251525
|
if (!wire2) {
|
|
250428
251526
|
const authored = literalParam(node, "value");
|
|
250429
251527
|
if (authored === void 0) return { defer: "nothing is wired into value" };
|
|
@@ -250492,6 +251590,7 @@ ${body}
|
|
|
250492
251590
|
if (node.type === HTTP_TYPE) return compileHttpFetch(node);
|
|
250493
251591
|
if (node.type === CLOUD_FUNCTION_TYPE2) return compileCloudCall(node);
|
|
250494
251592
|
if (node.type === RECORD_TYPE) return compileRecordFetch(node);
|
|
251593
|
+
if (node.type === "DbCollection2") return compileQueryFetch(node);
|
|
250495
251594
|
if (node.type === FILE_PICKER_TYPE) return compileFilePick(node);
|
|
250496
251595
|
if (node.type === UPLOAD_FILE_TYPE) return compileFileUpload(node);
|
|
250497
251596
|
if (node.type === SIGN_FILE_URL_TYPE) return compileFileSign(node);
|
|
@@ -250505,13 +251604,16 @@ ${body}
|
|
|
250505
251604
|
const variableName = variableNameOf(node);
|
|
250506
251605
|
if (variableName === void 0) return { defer: "variable name is not a literal" };
|
|
250507
251606
|
const setWith = literalParam(node, "setWith");
|
|
250508
|
-
const wire =
|
|
251607
|
+
const wire = lastValueWireOf(node);
|
|
250509
251608
|
if (setWith === "object" || setWith === "array") {
|
|
250510
251609
|
return { defer: `setWith "${String(setWith)}" conversion is not translated in step 5` };
|
|
250511
251610
|
}
|
|
250512
251611
|
if (setWith === "emptyString") {
|
|
250513
251612
|
return { action: { kind: "store-set", variableName, expr: { kind: "literal", value: "" } }, consumes: wire ? [wire.key] : [], collapses: [], subscribes: [] };
|
|
250514
251613
|
}
|
|
251614
|
+
if (wire === void 0 && valueWiresInto(node).length > 1) {
|
|
251615
|
+
return { defer: "two wires feed its Value input \u2014 last-writer-wins is not statically ordered" };
|
|
251616
|
+
}
|
|
250515
251617
|
if (setWith === "boolean" && wire) {
|
|
250516
251618
|
return { defer: `setWith "boolean" conversion is not translated in step 5` };
|
|
250517
251619
|
}
|
|
@@ -250694,10 +251796,17 @@ ${body}
|
|
|
250694
251796
|
return compileConditionBranch(node);
|
|
250695
251797
|
};
|
|
250696
251798
|
const compileConditionBranch = (node) => {
|
|
251799
|
+
const snapshotVerdict = snapshotVerdicts().get(node.id);
|
|
251800
|
+
const snapshotWires = snapshotVerdict === null ? snapshotWiresOf(node) : [];
|
|
250697
251801
|
const stray = component.connections.find(
|
|
250698
|
-
(c) => c.fromId === node.id && c.fromProperty !== "ontrue" && c.fromProperty !== "onfalse"
|
|
251802
|
+
(c) => c.fromId === node.id && c.fromProperty !== "ontrue" && c.fromProperty !== "onfalse" && !snapshotWires.includes(c)
|
|
250699
251803
|
);
|
|
250700
|
-
if (stray)
|
|
251804
|
+
if (stray) {
|
|
251805
|
+
const valueOut = stray.fromProperty === "result" || stray.fromProperty === "isfalse";
|
|
251806
|
+
return {
|
|
251807
|
+
defer: valueOut && typeof snapshotVerdict === "string" ? snapshotVerdict : `its ${stray.fromProperty} output drives logic this slice does not translate`
|
|
251808
|
+
};
|
|
251809
|
+
}
|
|
250701
251810
|
const ctx = newCtx();
|
|
250702
251811
|
let cond;
|
|
250703
251812
|
const condWire = component.connections.find((c) => c.toId === node.id && c.toProperty === "condition");
|
|
@@ -250748,6 +251857,25 @@ ${body}
|
|
|
250748
251857
|
if ("defer" in whenTrue) return whenTrue;
|
|
250749
251858
|
const whenFalse = arm("onfalse");
|
|
250750
251859
|
if ("defer" in whenFalse) return whenFalse;
|
|
251860
|
+
if (snapshotWires.length > 0) {
|
|
251861
|
+
const writes = (answer3) => snapshotWires.map((w) => ({
|
|
251862
|
+
kind: "state-set",
|
|
251863
|
+
name: snapshotCellOf(w.toId, w.toProperty).name,
|
|
251864
|
+
expr: { kind: "literal", value: w.fromProperty === "result" ? answer3 : !answer3 }
|
|
251865
|
+
}));
|
|
251866
|
+
const truth = truthyExpr(cond);
|
|
251867
|
+
if (truth.kind === "literal") {
|
|
251868
|
+
const side = [...writes(truth.value === true), ...truth.value === true ? whenTrue : whenFalse];
|
|
251869
|
+
const action = side.length === 1 ? side[0] : { kind: "branch", cond: { kind: "literal", value: true }, whenTrue: side, whenFalse: [], flat: true };
|
|
251870
|
+
return { action, consumes, collapses, subscribes };
|
|
251871
|
+
}
|
|
251872
|
+
return {
|
|
251873
|
+
action: { kind: "branch", cond, whenTrue: [...writes(true), ...whenTrue], whenFalse: [...writes(false), ...whenFalse] },
|
|
251874
|
+
consumes,
|
|
251875
|
+
collapses,
|
|
251876
|
+
subscribes
|
|
251877
|
+
};
|
|
251878
|
+
}
|
|
250751
251879
|
if (whenTrue.length === 0 && whenFalse.length === 0) {
|
|
250752
251880
|
return { defer: "neither branch drives a translatable action" };
|
|
250753
251881
|
}
|
|
@@ -250758,14 +251886,35 @@ ${body}
|
|
|
250758
251886
|
const recordEffectDefers = /* @__PURE__ */ new Map();
|
|
250759
251887
|
const compiling = /* @__PURE__ */ new Set();
|
|
250760
251888
|
const compiledOf = (node, port5) => {
|
|
250761
|
-
const
|
|
250762
|
-
const
|
|
250763
|
-
|
|
251889
|
+
const valueWires = (node.type === "Set Variable" || node.type === GLOBAL_STORE_SET) && valueWiresInto(node).length > 1;
|
|
251890
|
+
const plainKey = `${node.id}:${port5}${valueWires ? `@value:${lastValueWireOf(node)?.key ?? "none"}` : ""}`;
|
|
251891
|
+
const scoped = [...verbChainScope].filter(([, s]) => s === "failure").map(([id]) => id);
|
|
251892
|
+
const scopeKey = scoped.length > 0 ? `${plainKey}@failure:${[...scoped].sort().join(",")}` : void 0;
|
|
251893
|
+
const readsScoped = (result2, form) => {
|
|
251894
|
+
if ("defer" in result2 || scoped.length === 0) return false;
|
|
251895
|
+
const rows = form === "row" ? new Set(scoped.map((id) => verbErrorVars.get(id)?.name).filter((n) => n !== void 0)) : void 0;
|
|
251896
|
+
let found2 = false;
|
|
251897
|
+
const visit = (value) => {
|
|
251898
|
+
if (found2 || value === null || typeof value !== "object") return;
|
|
251899
|
+
if (Array.isArray(value)) return value.forEach(visit);
|
|
251900
|
+
const v = value;
|
|
251901
|
+
if (form === "local" && v.kind === "outcome-error" && v.viaState === void 0 && scoped.includes(v.nodeId)) found2 = true;
|
|
251902
|
+
if (form === "row" && v.kind === "state-get" && rows.has(v.name)) found2 = true;
|
|
251903
|
+
for (const child of Object.values(value)) visit(child);
|
|
251904
|
+
};
|
|
251905
|
+
visit(result2.action);
|
|
251906
|
+
return found2;
|
|
251907
|
+
};
|
|
251908
|
+
const cachedScoped = scopeKey !== void 0 ? compiledSinks.get(scopeKey) : void 0;
|
|
251909
|
+
if (cachedScoped !== void 0) return cachedScoped;
|
|
251910
|
+
const cached3 = compiledSinks.get(plainKey);
|
|
251911
|
+
if (cached3 !== void 0 && !readsScoped(cached3, "row")) return cached3;
|
|
251912
|
+
const key2 = scopeKey !== void 0 && (cached3 !== void 0 || compiling.has(plainKey)) ? scopeKey : plainKey;
|
|
250764
251913
|
if (compiling.has(key2)) return { defer: "its trigger chain is cyclic" };
|
|
250765
251914
|
compiling.add(key2);
|
|
250766
251915
|
const result = compileSink(node, port5);
|
|
250767
251916
|
compiling.delete(key2);
|
|
250768
|
-
compiledSinks.set(key2, result);
|
|
251917
|
+
compiledSinks.set(key2 === plainKey && readsScoped(result, "local") ? scopeKey : key2, result);
|
|
250769
251918
|
return result;
|
|
250770
251919
|
};
|
|
250771
251920
|
const exprValidIn = (expr, context, invokedScope) => {
|
|
@@ -250947,7 +252096,7 @@ ${body}
|
|
|
250947
252096
|
case "api-call":
|
|
250948
252097
|
return action.args.every(
|
|
250949
252098
|
(arg) => arg.kind === "expr" ? exprValidIn(arg.expr, context, invokedScope) : arg.props.every((p) => exprValidIn(p.expr, context, invokedScope))
|
|
250950
|
-
) && actionsValidIn(action.then, context, invokedScope);
|
|
252099
|
+
) && actionsValidIn(action.then, context, invokedScope) && actionsValidIn(action.failThen, context, invokedScope);
|
|
250951
252100
|
case "jsfun-run": {
|
|
250952
252101
|
const def = plan.jsFunctions[action.nodeId];
|
|
250953
252102
|
if (def === void 0) return false;
|
|
@@ -251069,6 +252218,13 @@ ${body}
|
|
|
251069
252218
|
return exprValidIn(action.items, context, invokedScope);
|
|
251070
252219
|
case "runtasks-abort":
|
|
251071
252220
|
return true;
|
|
252221
|
+
// P18-S116-QUERYFETCH. The wired filter values are read at the pulse, in the handler that pulses.
|
|
252222
|
+
case "query-fetch":
|
|
252223
|
+
return action.values.every((v) => exprValidIn(v.expr, context, invokedScope));
|
|
252224
|
+
// EXP-011 §86. A run's pulse loop and its arms sit inside the run's own `then`, so its scope already holds the node.
|
|
252225
|
+
case "jsfun-pulses":
|
|
252226
|
+
case "jsfun-pulse":
|
|
252227
|
+
return actionsValidIn(action.then, context, invokedScope);
|
|
251072
252228
|
// EXP-011 §58. The data is read at the pulse, in the handler that pulses; the listeners are validated where they compile.
|
|
251073
252229
|
case "stream-action":
|
|
251074
252230
|
return action.value === void 0 || exprValidIn(action.value, context, invokedScope);
|
|
@@ -251150,6 +252306,9 @@ ${body}
|
|
|
251150
252306
|
);
|
|
251151
252307
|
return !firedByOwnChange;
|
|
251152
252308
|
}
|
|
252309
|
+
if (sink.type === JS_FUNCTION && c.toProperty.startsWith("in-")) {
|
|
252310
|
+
return component.connections.some((w) => w.toId === sink.id && w.toProperty === "run" && w.fromId !== node.id);
|
|
252311
|
+
}
|
|
251153
252312
|
return rendered2.has(sink.id) && !isTriggerInto(sink, c.toProperty);
|
|
251154
252313
|
});
|
|
251155
252314
|
if (!stateWired && !actionWired && !outputRead) continue;
|
|
@@ -251301,7 +252460,8 @@ ${body}
|
|
|
251301
252460
|
}
|
|
251302
252461
|
consumed.add(wire.key);
|
|
251303
252462
|
stateLandedKeys.add(wire.key);
|
|
251304
|
-
|
|
252463
|
+
const pushedType = exprTsType(expr);
|
|
252464
|
+
plan.pushEffects.push({ prop: vp.prop, expr, ...vp.tsType !== "any" && pushedType !== vp.tsType ? { castTo: vp.tsType } : {} });
|
|
251305
252465
|
plan.liftedOutputProps.push({ port: port5, prop: vp.prop, tsType: vp.tsType });
|
|
251306
252466
|
for (const k of ctx.consumes) consumed.add(k);
|
|
251307
252467
|
for (const s of ctx.subscriberIds) boundSubscribers.add(s);
|
|
@@ -251565,8 +252725,60 @@ ${body}
|
|
|
251565
252725
|
if ("defer" in r) return r;
|
|
251566
252726
|
out.push(r);
|
|
251567
252727
|
}
|
|
252728
|
+
const late2 = lateQueryFetchWrite(out);
|
|
252729
|
+
if (late2 !== void 0) return { defer: late2 };
|
|
251568
252730
|
return out;
|
|
251569
252731
|
}
|
|
252732
|
+
function lateQueryFetchWrite(actions) {
|
|
252733
|
+
const writes = (a8) => a8.kind === "store-set" ? `store:${a8.variableName}` : a8.kind === "globalstore-set" ? `key:${a8.storeName}:${a8.key}` : a8.kind === "state-set" ? `state:${a8.name}` : void 0;
|
|
252734
|
+
const reads = (e, into) => {
|
|
252735
|
+
if (e.kind === "store-get") into.add(`store:${e.variableName}`);
|
|
252736
|
+
else if (e.kind === "store-key-get") into.add(`key:${e.storeName}:${e.key}`);
|
|
252737
|
+
else if (e.kind === "state-get") into.add(`state:${e.name}`);
|
|
252738
|
+
else if (e.kind === "format") e.parts.forEach((p) => typeof p !== "string" && reads(p, into));
|
|
252739
|
+
else if (e.kind === "logical") e.operands.forEach((o) => reads(o, into));
|
|
252740
|
+
else if (e.kind === "not" || e.kind === "truthy") reads(e.operand, into);
|
|
252741
|
+
return into;
|
|
252742
|
+
};
|
|
252743
|
+
for (const [i, a8] of actions.entries()) {
|
|
252744
|
+
if (a8.kind !== "query-fetch") continue;
|
|
252745
|
+
for (const v of a8.values) {
|
|
252746
|
+
const read2 = reads(v.expr, /* @__PURE__ */ new Set());
|
|
252747
|
+
if (actions.slice(i + 1).some((b) => read2.has(writes(b) ?? ""))) {
|
|
252748
|
+
return `its Fetch is sent before the chain writes "${v.name}", which its Filter reads \u2014 the runtime fetches after the pulse's writes (scheduleFetch), and that order is not exported in this slice`;
|
|
252749
|
+
}
|
|
252750
|
+
}
|
|
252751
|
+
}
|
|
252752
|
+
for (const [i, a8] of actions.entries()) {
|
|
252753
|
+
if (!isAwaitedAction(a8)) continue;
|
|
252754
|
+
const sent2 = /* @__PURE__ */ new Set();
|
|
252755
|
+
const collect3 = (value, top) => {
|
|
252756
|
+
if (value === null || typeof value !== "object") return;
|
|
252757
|
+
if (Array.isArray(value)) return value.forEach((v) => collect3(v, false));
|
|
252758
|
+
const kind = value.kind;
|
|
252759
|
+
if (!top && typeof kind === "string" && (kind === "store-get" || kind === "store-key-get" || kind === "state-get")) {
|
|
252760
|
+
reads(value, sent2);
|
|
252761
|
+
return;
|
|
252762
|
+
}
|
|
252763
|
+
for (const [key2, child] of Object.entries(value)) if (!/Then$|^then$/.test(key2)) collect3(child, false);
|
|
252764
|
+
};
|
|
252765
|
+
collect3(a8, true);
|
|
252766
|
+
const laterWrites = /* @__PURE__ */ new Map();
|
|
252767
|
+
const writtenIn = (b) => {
|
|
252768
|
+
if (b === null || typeof b !== "object") return;
|
|
252769
|
+
if (Array.isArray(b)) return b.forEach(writtenIn);
|
|
252770
|
+
const key2 = writes(b);
|
|
252771
|
+
if (key2 !== void 0 && !laterWrites.has(key2)) laterWrites.set(key2, key2.split(":").pop() ?? key2);
|
|
252772
|
+
for (const child of Object.values(b)) writtenIn(child);
|
|
252773
|
+
};
|
|
252774
|
+
writtenIn(actions.slice(i + 1));
|
|
252775
|
+
const clash = [...sent2].find((k) => laterWrites.has(k));
|
|
252776
|
+
if (clash !== void 0) {
|
|
252777
|
+
return `its node sends "${laterWrites.get(clash)}" after the click's later wires have written it \u2014 the runtime reads what it sends after the update, the export where the call starts, and that order is not exported in this slice`;
|
|
252778
|
+
}
|
|
252779
|
+
}
|
|
252780
|
+
return void 0;
|
|
252781
|
+
}
|
|
251570
252782
|
function snapAction(action, snap2) {
|
|
251571
252783
|
switch (action.kind) {
|
|
251572
252784
|
case "state-set": {
|
|
@@ -251719,7 +252931,9 @@ ${body}
|
|
|
251719
252931
|
}
|
|
251720
252932
|
const then2 = snapActionList(action.then, snap2);
|
|
251721
252933
|
if (!Array.isArray(then2)) return then2;
|
|
251722
|
-
|
|
252934
|
+
const failThen = snapActionList(action.failThen, snap2);
|
|
252935
|
+
if (!Array.isArray(failThen)) return failThen;
|
|
252936
|
+
return { ...action, args, then: then2, failThen };
|
|
251723
252937
|
}
|
|
251724
252938
|
// Same rule for a request (EXP-011 Tier 1.2): the values it sends are read where the
|
|
251725
252939
|
// handler is, before the await, so a `Set Variable` earlier in the chain must reach them —
|
|
@@ -251795,6 +253009,16 @@ ${body}
|
|
|
251795
253009
|
}
|
|
251796
253010
|
case "runtasks-abort":
|
|
251797
253011
|
return action;
|
|
253012
|
+
// P18-S116-QUERYFETCH. A value the chain wrote a statement earlier is read as written, as `stream-action`'s.
|
|
253013
|
+
case "query-fetch": {
|
|
253014
|
+
const values = [];
|
|
253015
|
+
for (const v of action.values) {
|
|
253016
|
+
const expr = snapExpr(v.expr, snap2);
|
|
253017
|
+
if ("defer" in expr) return expr;
|
|
253018
|
+
values.push({ name: v.name, expr });
|
|
253019
|
+
}
|
|
253020
|
+
return { ...action, values };
|
|
253021
|
+
}
|
|
251798
253022
|
case "jsfun-run": {
|
|
251799
253023
|
if ((plan.jsFunctions[action.nodeId]?.inputs ?? []).some((i) => i.expr !== void 0 && exprTouchesSnap(i.expr, snap2))) {
|
|
251800
253024
|
return { defer: "a Function argument reads state written earlier in this chain \u2014 not translated in this slice" };
|
|
@@ -251803,6 +253027,13 @@ ${body}
|
|
|
251803
253027
|
if (!Array.isArray(then2)) return then2;
|
|
251804
253028
|
return { ...action, then: then2 };
|
|
251805
253029
|
}
|
|
253030
|
+
// EXP-011 §86. The arms run in the run's closure, after the body: a write earlier in the chain reaches them.
|
|
253031
|
+
case "jsfun-pulses":
|
|
253032
|
+
case "jsfun-pulse": {
|
|
253033
|
+
const then2 = snapActionList(action.then, snap2);
|
|
253034
|
+
if (!Array.isArray(then2)) return then2;
|
|
253035
|
+
return { ...action, then: then2 };
|
|
253036
|
+
}
|
|
251806
253037
|
/**
|
|
251807
253038
|
* EXP-011 Tier 2.5. Same rule as a request: the link is read where the handler is, before
|
|
251808
253039
|
* the browser is touched, so a `Set Variable` earlier in the chain must reach it — and both
|
|
@@ -251942,7 +253173,22 @@ ${body}
|
|
|
251942
253173
|
return action;
|
|
251943
253174
|
}
|
|
251944
253175
|
}
|
|
253176
|
+
const feedersCompiled = /* @__PURE__ */ new Set();
|
|
253177
|
+
const compileFunctionFeedersOf = (node) => {
|
|
253178
|
+
if (feedersCompiled.has(node.id)) return;
|
|
253179
|
+
feedersCompiled.add(node.id);
|
|
253180
|
+
for (const c of component.connections) {
|
|
253181
|
+
if (c.toId !== node.id || !c.fromProperty.startsWith("out-")) continue;
|
|
253182
|
+
const from = nodeById.get(c.fromId);
|
|
253183
|
+
if (!from || jsNodeKindOf(from.type) !== "function" || !wiredPorts.has(`${from.id}:run`)) continue;
|
|
253184
|
+
const runs = component.connections.filter((w) => w.toId === from.id && w.toProperty === "run");
|
|
253185
|
+
if (!runs.every((w) => w.fromProperty === "didMount" || rendered2.has(w.fromId))) continue;
|
|
253186
|
+
compileFunctionFeedersOf(from);
|
|
253187
|
+
compiledOf(from, "run");
|
|
253188
|
+
}
|
|
253189
|
+
};
|
|
251945
253190
|
for (const node of component.nodes) {
|
|
253191
|
+
compileFunctionFeedersOf(node);
|
|
251946
253192
|
if (TRIGGER_PORTS[node.type] !== void 0) compiledOf(node, TRIGGER_PORTS[node.type]);
|
|
251947
253193
|
else if (node.type === "NavigationShowPopup") compiledOf(node, "show");
|
|
251948
253194
|
else if (node.type === "NavigationClosePopup") {
|
|
@@ -252163,6 +253409,16 @@ ${body}
|
|
|
252163
253409
|
}
|
|
252164
253410
|
}
|
|
252165
253411
|
const receiverActions = /* @__PURE__ */ new Map();
|
|
253412
|
+
const unreachedOwnerOf = (connection) => excluded.owners.get(connection.fromId) ?? (connection.fromProperty === "didMount" ? excluded.mounts.get(connection.fromId) : void 0);
|
|
253413
|
+
const refuseUnreached = (connection, toNode, compiled2, reason2, outputsSink) => {
|
|
253414
|
+
notes.push(wireNote(connection, reason2));
|
|
253415
|
+
for (const key2 of compiled2.consumes) consumed.add(key2);
|
|
253416
|
+
if (outputsSink) return;
|
|
253417
|
+
if (dispositions[toNode.id] === void 0) dispositions[toNode.id] = { kind: "deferred", to: "EXP-003", reason: reason2 };
|
|
253418
|
+
for (const id of compiled2.collapses ?? []) {
|
|
253419
|
+
if (dispositions[id] === void 0) dispositions[id] = { kind: "deferred", to: "EXP-003", reason: reason2 };
|
|
253420
|
+
}
|
|
253421
|
+
};
|
|
252166
253422
|
for (const connection of component.connections) {
|
|
252167
253423
|
if (consumed.has(connection.key)) continue;
|
|
252168
253424
|
const toNode = nodeById.get(connection.toId);
|
|
@@ -252173,6 +253429,7 @@ ${body}
|
|
|
252173
253429
|
if (fromNode?.type === "Condition" && (connection.fromProperty === "ontrue" || connection.fromProperty === "onfalse")) {
|
|
252174
253430
|
continue;
|
|
252175
253431
|
}
|
|
253432
|
+
if (pageMountFetch(connection)) continue;
|
|
252176
253433
|
if ((fromNode?.type === "NavigationShowPopup" || fromNode?.type === "NavigationClosePopup") && connection.fromProperty === "done") {
|
|
252177
253434
|
continue;
|
|
252178
253435
|
}
|
|
@@ -252217,6 +253474,11 @@ ${body}
|
|
|
252217
253474
|
continue;
|
|
252218
253475
|
}
|
|
252219
253476
|
if (fromNode && rendered2.has(fromNode.id) && isTextInputType(fromNode.type) && connection.fromProperty === "textChanged") {
|
|
253477
|
+
const unreached = unreachedOwnerOf(connection);
|
|
253478
|
+
if (unreached !== void 0) {
|
|
253479
|
+
refuseUnreached(connection, toNode, compiled2, unreached, outputsSink);
|
|
253480
|
+
continue;
|
|
253481
|
+
}
|
|
252220
253482
|
if (!actionsValidIn([compiled2.action], { kind: "dom", nodeId: fromNode.id, port: "textChanged" })) {
|
|
252221
253483
|
const reason2 = "the action reads values that only exist in another handler";
|
|
252222
253484
|
if (outputsSink) {
|
|
@@ -252249,6 +253511,11 @@ ${body}
|
|
|
252249
253511
|
const customSignal = fromNode !== void 0 && plan.roleOf[fromNode.id] === "custom" && customSignalOutputs(fromNode).has(connection.fromProperty);
|
|
252250
253512
|
const repeaterRowSignal = fromNode !== void 0 && fromNode.type === "For Each" && connection.fromProperty.startsWith(ITEM_OUTPUT_SIGNAL);
|
|
252251
253513
|
if (fromNode && rendered2.has(fromNode.id) && (connection.kind === "signal" || instanceSignal || customSignal || repeaterRowSignal)) {
|
|
253514
|
+
const unreached = unreachedOwnerOf(connection);
|
|
253515
|
+
if (unreached !== void 0) {
|
|
253516
|
+
refuseUnreached(connection, toNode, compiled2, unreached, outputsSink);
|
|
253517
|
+
continue;
|
|
253518
|
+
}
|
|
252252
253519
|
if (!actionsValidIn([compiled2.action], { kind: "dom", nodeId: fromNode.id, port: connection.fromProperty })) {
|
|
252253
253520
|
const reason2 = "the action reads values that only exist in another handler";
|
|
252254
253521
|
if (outputsSink) {
|
|
@@ -252264,6 +253531,12 @@ ${body}
|
|
|
252264
253531
|
compiled2.action,
|
|
252265
253532
|
chainSnapshotFor(`dom:${fromNode.id}:${connection.fromProperty}`, seedId)
|
|
252266
253533
|
);
|
|
253534
|
+
const lateWrite = "defer" in snapped ? void 0 : lateQueryFetchWrite([...plan.handlers[fromNode.id]?.[connection.fromProperty] ?? [], snapped]);
|
|
253535
|
+
if (lateWrite !== void 0) {
|
|
253536
|
+
dispositions[toNode.id] = { kind: "deferred", to: "EXP-003", reason: lateWrite };
|
|
253537
|
+
notes.push(wireNote(connection, lateWrite));
|
|
253538
|
+
continue;
|
|
253539
|
+
}
|
|
252267
253540
|
if ("defer" in snapped) {
|
|
252268
253541
|
if (outputsSink) {
|
|
252269
253542
|
if (!failedOutputsNodes.has(toNode.id)) failedOutputsNodes.set(toNode.id, snapped.defer);
|
|
@@ -252300,6 +253573,12 @@ ${body}
|
|
|
252300
253573
|
continue;
|
|
252301
253574
|
}
|
|
252302
253575
|
const snappedRecv = snapAction(compiled2.action, chainSnapshotFor(`recv:${fromNode.id}`));
|
|
253576
|
+
const lateRecvWrite = "defer" in snappedRecv ? void 0 : lateQueryFetchWrite([...receiverActions.get(fromNode.id) ?? [], snappedRecv]);
|
|
253577
|
+
if (lateRecvWrite !== void 0) {
|
|
253578
|
+
dispositions[toNode.id] = { kind: "deferred", to: "EXP-003", reason: lateRecvWrite };
|
|
253579
|
+
notes.push(wireNote(connection, lateRecvWrite));
|
|
253580
|
+
continue;
|
|
253581
|
+
}
|
|
252303
253582
|
if ("defer" in snappedRecv) {
|
|
252304
253583
|
if (outputsSink) {
|
|
252305
253584
|
if (!failedOutputsNodes.has(toNode.id)) failedOutputsNodes.set(toNode.id, snappedRecv.defer);
|
|
@@ -252316,13 +253595,15 @@ ${body}
|
|
|
252316
253595
|
for (const id of compiled2.subscribes ?? []) boundSubscribers.add(id);
|
|
252317
253596
|
continue;
|
|
252318
253597
|
}
|
|
252319
|
-
const
|
|
253598
|
+
const sendingRun = fromNode !== void 0 && wiredPorts.has(`${fromNode.id}:run`) ? compiledSinks.get(`${fromNode.id}:run`) : void 0;
|
|
253599
|
+
const pulseRefusal = fromNode !== void 0 && jsNodeKindOf(fromNode.type) === "function" ? pulseRefusals.get(`${fromNode.id}:${connection.fromProperty}`) ?? pulseRefusals.get(fromNode.id) ?? (sendingRun !== void 0 && "defer" in sendingRun && connection.fromProperty.startsWith("out-") ? `the Function that sends its "${connection.fromProperty.slice("out-".length)}" signal is not exported: ${sendingRun.defer}` : void 0) : void 0;
|
|
253600
|
+
const untranslatable = pulseRefusal ?? `trigger ${connection.fromId}.${connection.fromProperty} is not a rendered element event or a receiver`;
|
|
252320
253601
|
if (outputsSink) {
|
|
252321
253602
|
if (!failedOutputsNodes.has(toNode.id)) failedOutputsNodes.set(toNode.id, untranslatable);
|
|
252322
253603
|
} else {
|
|
252323
253604
|
dispositions[toNode.id] = { kind: "deferred", to: "EXP-003", reason: untranslatable };
|
|
252324
253605
|
}
|
|
252325
|
-
notes.push(wireNote(connection, `the trigger is not a rendered element event or a receiver`));
|
|
253606
|
+
notes.push(wireNote(connection, pulseRefusal ?? `the trigger is not a rendered element event or a receiver`));
|
|
252326
253607
|
}
|
|
252327
253608
|
for (const node of component.nodes) {
|
|
252328
253609
|
if (node.type !== "Component Outputs" || dispositions[node.id] !== void 0) continue;
|
|
@@ -252378,6 +253659,7 @@ ${body}
|
|
|
252378
253659
|
} else if (action.kind === "api-call") {
|
|
252379
253660
|
attachedMutations.add(action.nodeId);
|
|
252380
253661
|
scanActions(action.then);
|
|
253662
|
+
scanActions(action.failThen);
|
|
252381
253663
|
} else if (action.kind === "http-call") {
|
|
252382
253664
|
attachedHttpNodes.add(action.nodeId);
|
|
252383
253665
|
scanActions(action.then);
|
|
@@ -252452,6 +253734,8 @@ ${body}
|
|
|
252452
253734
|
scanActions(action.minted?.failThen ?? []);
|
|
252453
253735
|
} else if (action.kind === "array-new") {
|
|
252454
253736
|
scanActions(action.then);
|
|
253737
|
+
} else if (action.kind === "jsfun-run" || action.kind === "jsfun-pulses" || action.kind === "jsfun-pulse") {
|
|
253738
|
+
scanActions(action.then);
|
|
252455
253739
|
}
|
|
252456
253740
|
}
|
|
252457
253741
|
};
|
|
@@ -252605,6 +253889,22 @@ ${body}
|
|
|
252605
253889
|
for (const key2 of [inputs[0].key, ...ctx.consumes, ...chain.consumes]) consumed.add(key2);
|
|
252606
253890
|
for (const id of [...ctx.subscriberIds, ...chain.subscribes]) boundSubscribers.add(id);
|
|
252607
253891
|
}
|
|
253892
|
+
for (const node of component.nodes) {
|
|
253893
|
+
const pieces = tickedRunPieces.get(node.id);
|
|
253894
|
+
if (pieces === void 0 || dispositions[node.id]?.kind !== "collapsed" || !plan.file) continue;
|
|
253895
|
+
const snapped = snapAction(pieces.action, chainSnapshotFor(`ticked:${node.id}`));
|
|
253896
|
+
if ("defer" in snapped) {
|
|
253897
|
+
dispositions[node.id] = { kind: "deferred", to: "EXP-003", reason: `the run a ticked input starts: ${snapped.defer}` };
|
|
253898
|
+
notes.push(`node ${node.id} (${node.type}) deferred: the run a ticked input starts: ${snapped.defer}`);
|
|
253899
|
+
continue;
|
|
253900
|
+
}
|
|
253901
|
+
plan.tickedRuns.push({
|
|
253902
|
+
nodeId: node.id,
|
|
253903
|
+
watch: pieces.watch,
|
|
253904
|
+
action: snapped,
|
|
253905
|
+
comment: `${node.authoredLabel ?? node.type} \u2014 also runs when a ticked input gets a new value, sending its signals and never Done (NDA-017 \xA72).`
|
|
253906
|
+
});
|
|
253907
|
+
}
|
|
252608
253908
|
for (const node of component.nodes) {
|
|
252609
253909
|
if (node.type !== "Condition" || dispositions[node.id] !== void 0) continue;
|
|
252610
253910
|
if (literalParam(node, "runOnChange-condition") === false) continue;
|
|
@@ -252645,6 +253945,11 @@ ${body}
|
|
|
252645
253945
|
for (const node of component.nodes) {
|
|
252646
253946
|
if (node.type !== RECORD_TYPE || dispositions[node.id] !== void 0) continue;
|
|
252647
253947
|
if (wiredPorts.has(`${node.id}:fetch`)) continue;
|
|
253948
|
+
const refusal2 = recordBindRefusal(node);
|
|
253949
|
+
if (refusal2 !== void 0) {
|
|
253950
|
+
recordEffectDefers.set(node.id, refusal2);
|
|
253951
|
+
continue;
|
|
253952
|
+
}
|
|
252648
253953
|
if (!plan.file) {
|
|
252649
253954
|
recordEffectDefers.set(node.id, "component emits no file to host the effect");
|
|
252650
253955
|
continue;
|
|
@@ -252666,9 +253971,13 @@ ${body}
|
|
|
252666
253971
|
const into = `src/${plan.file.dir}/${plan.file.fileBase}.tsx`;
|
|
252667
253972
|
plan.recordEffects.push({
|
|
252668
253973
|
nodeId: node.id,
|
|
252669
|
-
action: snapped
|
|
252670
|
-
comment: `${node.authoredLabel ?? "Record"} \u2014
|
|
253974
|
+
action: snapped,
|
|
253975
|
+
comment: `${node.authoredLabel ?? "Record"} \u2014 bound to the app's copy of the record its Id names, whenever the Id changes; binding reads nothing (dbmodelnode2.ts setModelID).`,
|
|
253976
|
+
held: ir.project.cloudservices !== void 0,
|
|
253977
|
+
follows: literalParam(node, "runOnChange-record") !== false
|
|
252671
253978
|
});
|
|
253979
|
+
const read2 = plan.recordReads.find((r) => r.nodeId === node.id);
|
|
253980
|
+
if (read2 !== void 0) read2.binds = true;
|
|
252672
253981
|
dispositions[node.id] = { kind: "collapsed", into };
|
|
252673
253982
|
for (const id of compiled2.collapses ?? []) dispositions[id] = { kind: "collapsed", into };
|
|
252674
253983
|
for (const key2 of compiled2.consumes) consumed.add(key2);
|
|
@@ -252676,6 +253985,7 @@ ${body}
|
|
|
252676
253985
|
}
|
|
252677
253986
|
for (const effect of plan.branchEffects) scanActions([effect.action]);
|
|
252678
253987
|
for (const effect of plan.valueChangedEffects) scanActions(effect.actions);
|
|
253988
|
+
for (const ticked3 of plan.tickedRuns) scanActions([ticked3.action]);
|
|
252679
253989
|
for (const effect of plan.recordEffects) scanActions([effect.action]);
|
|
252680
253990
|
for (const machine of plan.statesMachines) for (const chain of statesListenerChains(machine)) scanActions(chain);
|
|
252681
253991
|
for (const drag of plan.drags) for (const chain of dragListenerChains(drag)) scanActions(chain);
|
|
@@ -252783,6 +254093,10 @@ ${body}
|
|
|
252783
254093
|
notes.push(wireNote(connection, `variable name is not a literal`));
|
|
252784
254094
|
continue;
|
|
252785
254095
|
}
|
|
254096
|
+
if (literalParam(fromNode, "runOnChange-variable") === false) {
|
|
254097
|
+
notes.push(wireNote(connection, VARIABLE_FROZEN));
|
|
254098
|
+
continue;
|
|
254099
|
+
}
|
|
252786
254100
|
if (connection.toProperty === "mounted" && toNode.id === plan.rootId) {
|
|
252787
254101
|
notes.push(wireNote(connection, `a mounted wire into the component root is a router concern \u2014 not translated in this slice`));
|
|
252788
254102
|
continue;
|
|
@@ -252842,6 +254156,19 @@ ${body}
|
|
|
252842
254156
|
const toNode = nodeById.get(connection.toId);
|
|
252843
254157
|
if (!toNode || !rendered2.has(toNode.id)) continue;
|
|
252844
254158
|
consumed.add(connection.key);
|
|
254159
|
+
const snapshotVerdict = fromNode.type === "Condition" ? snapshotVerdicts().get(fromNode.id) : void 0;
|
|
254160
|
+
if (snapshotVerdict === null) {
|
|
254161
|
+
plan.bindings[toNode.id] = plan.bindings[toNode.id] ?? {};
|
|
254162
|
+
plan.bindings[toNode.id][connection.toProperty] = {
|
|
254163
|
+
kind: "computed",
|
|
254164
|
+
expr: { kind: "state-get", name: snapshotCellOf(toNode.id, connection.toProperty).name }
|
|
254165
|
+
};
|
|
254166
|
+
continue;
|
|
254167
|
+
}
|
|
254168
|
+
if (snapshotVerdict !== void 0) {
|
|
254169
|
+
notes.push(wireNote(connection, snapshotVerdict));
|
|
254170
|
+
continue;
|
|
254171
|
+
}
|
|
252845
254172
|
const ctx = newCtx();
|
|
252846
254173
|
const expr = resolveExpr(fromNode, connection.fromProperty, ctx);
|
|
252847
254174
|
if (expr === null) {
|
|
@@ -252925,6 +254252,12 @@ ${body}
|
|
|
252925
254252
|
styleSinkOf(toNode, connection.toProperty) !== void 0;
|
|
252926
254253
|
if (!bindable) continue;
|
|
252927
254254
|
if (connection.toProperty === "mounted" && toNode.id === plan.rootId) continue;
|
|
254255
|
+
const deadRow = excluded.rows.get(fromNode.id);
|
|
254256
|
+
if (deadRow !== void 0) {
|
|
254257
|
+
consumed.add(connection.key);
|
|
254258
|
+
notes.push(wireNote(connection, deadRow));
|
|
254259
|
+
continue;
|
|
254260
|
+
}
|
|
252928
254261
|
const ctx = newCtx();
|
|
252929
254262
|
const expr = resolveExpr(fromNode, connection.fromProperty, ctx);
|
|
252930
254263
|
if (expr === null) continue;
|
|
@@ -253136,6 +254469,12 @@ ${body}
|
|
|
253136
254469
|
continue;
|
|
253137
254470
|
}
|
|
253138
254471
|
if (toNode?.type === "For Each" && connection.toProperty === "items" && fromNode?.type === "DbCollection2" && plan.repeaters[toNode.id]) {
|
|
254472
|
+
const settings = querySettingsFor(fromNode);
|
|
254473
|
+
if ("refuse" in settings) {
|
|
254474
|
+
consumed.add(connection.key);
|
|
254475
|
+
notes.push(wireNote(connection, `the Query Records it reads is not exported: ${settings.refuse}`));
|
|
254476
|
+
continue;
|
|
254477
|
+
}
|
|
253139
254478
|
plan.repeaters[toNode.id].itemsQueryId = fromNode.id;
|
|
253140
254479
|
consumed.add(connection.key);
|
|
253141
254480
|
continue;
|
|
@@ -253454,6 +254793,7 @@ ${body}
|
|
|
253454
254793
|
else for (const p of arg.props) walkExpr(p.expr);
|
|
253455
254794
|
}
|
|
253456
254795
|
walkActions(action.then);
|
|
254796
|
+
walkActions(action.failThen);
|
|
253457
254797
|
break;
|
|
253458
254798
|
// A session read is at its most ordinary inside a request — the signed-in user's id in
|
|
253459
254799
|
// a path parameter, their token in a header. Missing it here would leave the page
|
|
@@ -253562,6 +254902,10 @@ ${body}
|
|
|
253562
254902
|
break;
|
|
253563
254903
|
case "runtasks-abort":
|
|
253564
254904
|
break;
|
|
254905
|
+
// P18-S116-QUERYFETCH. The wired filter values, read at the pulse.
|
|
254906
|
+
case "query-fetch":
|
|
254907
|
+
for (const v of action.values) walkExpr(v.expr);
|
|
254908
|
+
break;
|
|
253565
254909
|
// EXP-011 §58. The data read at the pulse; the config and listeners are walked off the plan below.
|
|
253566
254910
|
case "stream-action":
|
|
253567
254911
|
if (action.value !== void 0) walkExpr(action.value);
|
|
@@ -253574,6 +254918,8 @@ ${body}
|
|
|
253574
254918
|
case "popup-show":
|
|
253575
254919
|
case "popup-close":
|
|
253576
254920
|
case "jsfun-run":
|
|
254921
|
+
case "jsfun-pulses":
|
|
254922
|
+
case "jsfun-pulse":
|
|
253577
254923
|
// EXP-011 §30, and the same omission the sibling walker had: a session read inside an
|
|
253578
254924
|
// array mutator's Done chain is as ordinary as one inside a request's, and missing it
|
|
253579
254925
|
// here leaves the page importing a `useSession` the module never exported.
|
|
@@ -253603,6 +254949,11 @@ ${body}
|
|
|
253603
254949
|
walkExpr(effect.watch);
|
|
253604
254950
|
walkActions(effect.actions);
|
|
253605
254951
|
}
|
|
254952
|
+
for (const ticked3 of plan.tickedRuns) {
|
|
254953
|
+
ticked3.watch.forEach(walkExpr);
|
|
254954
|
+
walkActions([ticked3.action]);
|
|
254955
|
+
}
|
|
254956
|
+
for (const query2 of plan.queries) for (const value of query2.wiredValues ?? []) walkExpr(value.expr);
|
|
253606
254957
|
for (const machine of plan.statesMachines) {
|
|
253607
254958
|
if (machine.follow !== void 0) walkExpr(machine.follow);
|
|
253608
254959
|
for (const chain of statesListenerChains(machine)) walkActions(chain);
|
|
@@ -253646,6 +254997,16 @@ ${body}
|
|
|
253646
254997
|
if (dispositions[node.id]?.kind === "collapsed") delete dispositions[node.id];
|
|
253647
254998
|
}
|
|
253648
254999
|
}
|
|
255000
|
+
for (const node of component.nodes) {
|
|
255001
|
+
if (node.type !== "DbCollection2" || !queryLands(node)) continue;
|
|
255002
|
+
for (const wire of component.connections) if (wire.toId === node.id && pageMountFetch(wire)) consumed.add(wire.key);
|
|
255003
|
+
const wiring = queryWirings.get(node.id);
|
|
255004
|
+
if (wiring === void 0) continue;
|
|
255005
|
+
const into = plan.file ? `src/${plan.file.dir}/${plan.file.fileBase}.tsx` : plan.path;
|
|
255006
|
+
for (const key2 of wiring.consumes) consumed.add(key2);
|
|
255007
|
+
for (const id of wiring.collapses) dispositions[id] = { kind: "collapsed", into };
|
|
255008
|
+
for (const id of wiring.subscribes) boundSubscribers.add(id);
|
|
255009
|
+
}
|
|
253649
255010
|
for (const connection of component.connections) {
|
|
253650
255011
|
if (consumed.has(connection.key)) continue;
|
|
253651
255012
|
const kitEndpoint = undeclaredKitPortOf(connection, nodeById, kits);
|
|
@@ -253675,6 +255036,54 @@ ${body}
|
|
|
253675
255036
|
comment: `${label2} \u2014 its authored Value is stored on every mount of this component (variablenode2.ts: the value setter runs at node creation).`
|
|
253676
255037
|
});
|
|
253677
255038
|
}
|
|
255039
|
+
{
|
|
255040
|
+
const parentOf = parentMapOf(component);
|
|
255041
|
+
for (const node of component.nodes) {
|
|
255042
|
+
if (!isTextInputType(node.type) || !rendered2.has(node.id)) continue;
|
|
255043
|
+
const stateVar = controlStateVars.get(node.id);
|
|
255044
|
+
const chain = plan.changeHandlers[node.id] ?? [];
|
|
255045
|
+
if (chain.length === 0) continue;
|
|
255046
|
+
const label2 = node.authoredLabel ? `"${node.authoredLabel}"` : "The text input";
|
|
255047
|
+
const refuse = (why) => notes.push(`node ${node.id} (${node.type}): its Value at mount is not announced \u2014 ${why}; typing still runs its chain (P18-S116-FIELDMOUNT)`);
|
|
255048
|
+
let away;
|
|
255049
|
+
for (let id = node.id; id !== void 0; id = parentOf.get(id)) {
|
|
255050
|
+
const bound = plan.bindings[id]?.["mounted"];
|
|
255051
|
+
if (bound !== void 0) {
|
|
255052
|
+
const literal2 = bound.kind === "computed" && (bound.expr.kind === "literal" || bound.expr.kind === "undefined");
|
|
255053
|
+
if (!literal2) away = away ?? "later";
|
|
255054
|
+
else if (bound.expr.kind === "undefined" || !bound.expr.value) away = "never";
|
|
255055
|
+
} else if (literalParam(nodeById.get(id), "mounted") === false) away = "never";
|
|
255056
|
+
}
|
|
255057
|
+
if (away === "never") continue;
|
|
255058
|
+
if (away === "later") {
|
|
255059
|
+
refuse("it or a group it sits in mounts from a wire, so it announces when that wire first says true, not as the component mounts");
|
|
255060
|
+
continue;
|
|
255061
|
+
}
|
|
255062
|
+
const type = literalParam(node, "type");
|
|
255063
|
+
if (wiredPorts.has(`${node.id}:type`) || type !== void 0 && typeof type !== "string") {
|
|
255064
|
+
refuse("its Type is wired, so whether it announces text or a number is not known here");
|
|
255065
|
+
continue;
|
|
255066
|
+
}
|
|
255067
|
+
if (type === "number") {
|
|
255068
|
+
refuse("a Number field announces a number or null, and the exported field carries text (P18-S118-NUMBERFIELD)");
|
|
255069
|
+
continue;
|
|
255070
|
+
}
|
|
255071
|
+
const sync = stateVar === void 0 ? void 0 : plan.syncEffects.find((e) => e.stateName === stateVar.name);
|
|
255072
|
+
if (sync === void 0 && wiredPorts.has(`${node.id}:startValue`) && literalParam(node, "runOnChange-startValue") !== false) {
|
|
255073
|
+
refuse("its Value is wired and the export does not carry that wire");
|
|
255074
|
+
continue;
|
|
255075
|
+
}
|
|
255076
|
+
const authored = literalParam(node, "startValue");
|
|
255077
|
+
const catalogDefault = node.catalogRef ? catalog2.inputDefault(node.catalogRef, "startValue") : void 0;
|
|
255078
|
+
const raw = authored !== void 0 ? authored : typeof catalogDefault === "string" || typeof catalogDefault === "number" ? catalogDefault : void 0;
|
|
255079
|
+
plan.fieldMounts.push({
|
|
255080
|
+
inputId: node.id,
|
|
255081
|
+
start: raw === void 0 ? "" : String(raw),
|
|
255082
|
+
...sync !== void 0 ? { source: sync.source } : {},
|
|
255083
|
+
comment: `${label2} announces its Value as it mounts, and its chain runs with it (TextInput.tsx componentDidMount \u2192 setText(startValue)).`
|
|
255084
|
+
});
|
|
255085
|
+
}
|
|
255086
|
+
}
|
|
253678
255087
|
for (const node of component.nodes) {
|
|
253679
255088
|
if (node.type !== "Variable2" || dispositions[node.id] !== void 0) continue;
|
|
253680
255089
|
if (variableNameOf(node) !== void 0) {
|
|
@@ -253747,9 +255156,13 @@ ${body}
|
|
|
253747
255156
|
}
|
|
253748
255157
|
for (const node of component.nodes) {
|
|
253749
255158
|
if (node.type !== "DbCollection2") continue;
|
|
253750
|
-
const
|
|
253751
|
-
|
|
253752
|
-
|
|
255159
|
+
const settings = querySettingsFor(node);
|
|
255160
|
+
if ("refuse" in settings) {
|
|
255161
|
+
plan.queries = plan.queries.filter((q) => q.nodeId !== node.id);
|
|
255162
|
+
dispositions[node.id] = { kind: "deferred", to: "EXP-003", reason: settings.refuse };
|
|
255163
|
+
continue;
|
|
255164
|
+
}
|
|
255165
|
+
if (!queryLands(node)) {
|
|
253753
255166
|
plan.queries = plan.queries.filter((q) => q.nodeId !== node.id);
|
|
253754
255167
|
dispositions[node.id] = {
|
|
253755
255168
|
kind: "deferred",
|
|
@@ -253932,6 +255345,7 @@ ${body}
|
|
|
253932
255345
|
// EXP-011 §53. Nothing nested on the action; the listeners are filled off the plan below.
|
|
253933
255346
|
case "runtasks-run":
|
|
253934
255347
|
case "runtasks-abort":
|
|
255348
|
+
case "query-fetch":
|
|
253935
255349
|
break;
|
|
253936
255350
|
// EXP-011 §58. Nothing nested on the action; the listeners are filled off the plan below.
|
|
253937
255351
|
case "stream-action":
|
|
@@ -254044,8 +255458,14 @@ ${body}
|
|
|
254044
255458
|
case "popup-show":
|
|
254045
255459
|
case "popup-close":
|
|
254046
255460
|
case "jsfun-run":
|
|
255461
|
+
case "jsfun-pulses":
|
|
255462
|
+
case "jsfun-pulse":
|
|
255463
|
+
fillMaterialize(action.then);
|
|
255464
|
+
break;
|
|
255465
|
+
// EXP-011 §90. Both chains.
|
|
254047
255466
|
case "api-call":
|
|
254048
255467
|
fillMaterialize(action.then);
|
|
255468
|
+
fillMaterialize(action.failThen);
|
|
254049
255469
|
break;
|
|
254050
255470
|
default:
|
|
254051
255471
|
break;
|
|
@@ -254057,6 +255477,7 @@ ${body}
|
|
|
254057
255477
|
for (const receiver of plan.receivers) fillMaterialize(receiver.actions);
|
|
254058
255478
|
for (const effect of plan.branchEffects) fillMaterialize([effect.action]);
|
|
254059
255479
|
for (const effect of plan.valueChangedEffects) fillMaterialize(effect.actions);
|
|
255480
|
+
for (const ticked3 of plan.tickedRuns) fillMaterialize([ticked3.action]);
|
|
254060
255481
|
for (const effect of plan.recordEffects) fillMaterialize([effect.action]);
|
|
254061
255482
|
for (const machine of plan.statesMachines) for (const chain of statesListenerChains(machine)) fillMaterialize(chain);
|
|
254062
255483
|
for (const drag of plan.drags) for (const chain of dragListenerChains(drag)) fillMaterialize(chain);
|
|
@@ -254069,6 +255490,22 @@ ${body}
|
|
|
254069
255490
|
for (const stream of plan.streams) for (const chain of Object.values(stream.listeners)) fillMaterialize(chain);
|
|
254070
255491
|
plan.screenResolutions = plan.screenResolutions.filter((s) => dispositions[s.nodeId]?.kind === "collapsed");
|
|
254071
255492
|
}
|
|
255493
|
+
{
|
|
255494
|
+
const seen = /* @__PURE__ */ new Set();
|
|
255495
|
+
const visit = (value) => {
|
|
255496
|
+
if (value === null || typeof value !== "object" || seen.has(value)) return;
|
|
255497
|
+
seen.add(value);
|
|
255498
|
+
if (Array.isArray(value)) {
|
|
255499
|
+
value.forEach((item, i) => {
|
|
255500
|
+
if (i < value.length - 1 && isAwaitedAction(item)) item.detached = true;
|
|
255501
|
+
visit(item);
|
|
255502
|
+
});
|
|
255503
|
+
return;
|
|
255504
|
+
}
|
|
255505
|
+
for (const child of Object.values(value)) visit(child);
|
|
255506
|
+
};
|
|
255507
|
+
visit(plan);
|
|
255508
|
+
}
|
|
254072
255509
|
for (const node of component.nodes) {
|
|
254073
255510
|
if (node.type !== RUN_TASKS_TYPE || dispositions[node.id] !== void 0) continue;
|
|
254074
255511
|
const dangling = component.connections.find((c) => c.fromId === node.id && RUN_TASKS_OUTPUTS.includes(c.fromProperty));
|
|
@@ -254712,7 +256149,7 @@ function kitFile(module2, bindings) {
|
|
|
254712
256149
|
const prop = binding.propOf.get(input.name);
|
|
254713
256150
|
if (prop === void 0) continue;
|
|
254714
256151
|
if (input.displayName !== void 0) lines.push(` /** ${escapeComment(input.displayName)} */`);
|
|
254715
|
-
lines.push(` ${prop}?: ${
|
|
256152
|
+
lines.push(` ${prop}?: ${inputTsTypeOf(input)};`);
|
|
254716
256153
|
}
|
|
254717
256154
|
for (const [port5, prop] of binding.signalPropOf) {
|
|
254718
256155
|
lines.push(` /** Signal output "${escapeComment(port5)}". */`);
|
|
@@ -254800,6 +256237,10 @@ function tsTypeOf2(port5) {
|
|
|
254800
256237
|
return "unknown";
|
|
254801
256238
|
}
|
|
254802
256239
|
}
|
|
256240
|
+
function inputTsTypeOf(port5) {
|
|
256241
|
+
const type = tsTypeOf2(port5);
|
|
256242
|
+
return type === "Record<string, unknown>" ? "Record<string, unknown> | readonly unknown[]" : type;
|
|
256243
|
+
}
|
|
254803
256244
|
function runtimeFile() {
|
|
254804
256245
|
return GENERATED_TS2 + `/*
|
|
254805
256246
|
* The custom-node bridge (EXP-010, Route B).
|
|
@@ -265371,6 +266812,108 @@ function signInLibSource() {
|
|
|
265371
266812
|
].join("\n");
|
|
265372
266813
|
}
|
|
265373
266814
|
|
|
266815
|
+
// ../nodegx-export/src/emit/functionRunLib.ts
|
|
266816
|
+
var FUNCTION_RUN_LIB_PATH = "src/lib/functionRun.ts";
|
|
266817
|
+
function functionRunLibSource() {
|
|
266818
|
+
return `/**
|
|
266819
|
+
* A Function node's run, as the running app's Function node publishes it.
|
|
266820
|
+
*
|
|
266821
|
+
* The body writes \`Outputs.x = \u2026\` and sends \`Outputs.go()\`. A write is published only when the value changes, and a
|
|
266822
|
+
* write of the value already held is published with the next signal. Each signal is a pulse its chain acts on after the
|
|
266823
|
+
* body, reading the outputs in step with the pulse: the k-th pulse reads the k-th value published since the run began,
|
|
266824
|
+
* or the last one when there are fewer, or what the output last sent when this run published nothing.
|
|
266825
|
+
*/
|
|
266826
|
+
|
|
266827
|
+
/** One Function node's state, kept across runs as the running app's node keeps it. */
|
|
266828
|
+
export interface FunctionNode<O> {
|
|
266829
|
+
/** The signal names its body may send. */
|
|
266830
|
+
signals: readonly string[];
|
|
266831
|
+
/** What each output holds: the last value written to it, in any run. */
|
|
266832
|
+
outputs: Partial<O>;
|
|
266833
|
+
/** What each output last sent down its wire. \`undefined\` is never sent, so a reader keeps the value before it. */
|
|
266834
|
+
delivered: Partial<O>;
|
|
266835
|
+
}
|
|
266836
|
+
|
|
266837
|
+
/** One signal the body sent, and the outputs as that pulse's chain reads them. */
|
|
266838
|
+
export interface FunctionPulse<O> {
|
|
266839
|
+
signal: string;
|
|
266840
|
+
values: Partial<O>;
|
|
266841
|
+
}
|
|
266842
|
+
|
|
266843
|
+
export interface FunctionRun<O> {
|
|
266844
|
+
/** The node's outputs after the run. */
|
|
266845
|
+
outputs: Partial<O>;
|
|
266846
|
+
/** Every signal the body sent, in the order it sent them. */
|
|
266847
|
+
pulses: Array<FunctionPulse<O>>;
|
|
266848
|
+
}
|
|
266849
|
+
|
|
266850
|
+
export function functionNode<O>(signals: readonly string[]): FunctionNode<O> {
|
|
266851
|
+
return { signals, outputs: {}, delivered: {} };
|
|
266852
|
+
}
|
|
266853
|
+
|
|
266854
|
+
export function runFunction<I, O>(body: (inputs: I, outputs: O) => unknown, inputs: I, node: FunctionNode<O>): FunctionRun<O> {
|
|
266855
|
+
const current = node.outputs as Record<string, unknown>;
|
|
266856
|
+
const delivered = node.delivered as Record<string, unknown>;
|
|
266857
|
+
const before: Record<string, unknown> = { ...delivered };
|
|
266858
|
+
const published: Record<string, unknown[]> = {};
|
|
266859
|
+
const held = new Set<string>();
|
|
266860
|
+
const sent: string[] = [];
|
|
266861
|
+
const publish = (name: string, value: unknown) => {
|
|
266862
|
+
if (value === undefined) return;
|
|
266863
|
+
(published[name] = published[name] ?? []).push(value);
|
|
266864
|
+
delivered[name] = value;
|
|
266865
|
+
};
|
|
266866
|
+
const signals: Record<string, unknown> = {};
|
|
266867
|
+
for (const signal of node.signals) {
|
|
266868
|
+
const send = () => {
|
|
266869
|
+
held.forEach((name) => publish(name, current[name]));
|
|
266870
|
+
held.clear();
|
|
266871
|
+
sent.push(signal);
|
|
266872
|
+
};
|
|
266873
|
+
signals[signal] = Object.assign(send, { send });
|
|
266874
|
+
}
|
|
266875
|
+
const outputs = new Proxy(signals, {
|
|
266876
|
+
get: (target, name) => (typeof name === 'string' && name in target ? target[name] : current[name as string]),
|
|
266877
|
+
set: (_target, name, value) => {
|
|
266878
|
+
const key = String(name);
|
|
266879
|
+
if (value !== current[key]) {
|
|
266880
|
+
current[key] = value;
|
|
266881
|
+
held.delete(key);
|
|
266882
|
+
publish(key, value);
|
|
266883
|
+
} else {
|
|
266884
|
+
held.add(key);
|
|
266885
|
+
}
|
|
266886
|
+
return true;
|
|
266887
|
+
}
|
|
266888
|
+
});
|
|
266889
|
+
body(inputs, outputs as unknown as O);
|
|
266890
|
+
const count: Record<string, number> = {};
|
|
266891
|
+
const pulses = sent.map((signal) => {
|
|
266892
|
+
const k = (count[signal] = (count[signal] ?? 0) + 1);
|
|
266893
|
+
const values: Record<string, unknown> = { ...before };
|
|
266894
|
+
for (const name of Object.keys(published)) values[name] = published[name][Math.min(k, published[name].length) - 1];
|
|
266895
|
+
return { signal, values: values as Partial<O> };
|
|
266896
|
+
});
|
|
266897
|
+
return { outputs: node.outputs, pulses };
|
|
266898
|
+
}
|
|
266899
|
+
|
|
266900
|
+
/**
|
|
266901
|
+
* Whether the values on a node's ticked inputs (those whose Run On Value Change box is ticked) are an arrival that runs it:
|
|
266902
|
+
* one of them is not \`undefined\`, which is never delivered, and is not the value that input already holds. Records each
|
|
266903
|
+
* one that arrived. A Run records what it read the same way, so a value set and a Run sent in one event make one run.
|
|
266904
|
+
*/
|
|
266905
|
+
export function tickedArrival(held: unknown[], values: readonly unknown[]): boolean {
|
|
266906
|
+
let arrived = false;
|
|
266907
|
+
values.forEach((value, i) => {
|
|
266908
|
+
if (value === undefined || Object.is(value, held[i])) return;
|
|
266909
|
+
held[i] = value;
|
|
266910
|
+
arrived = true;
|
|
266911
|
+
});
|
|
266912
|
+
return arrived;
|
|
266913
|
+
}
|
|
266914
|
+
`;
|
|
266915
|
+
}
|
|
266916
|
+
|
|
265374
266917
|
// ../nodegx-export/src/emit/cryptoLib.ts
|
|
265375
266918
|
var CRYPTO_LIB_PATH = "src/lib/crypto.ts";
|
|
265376
266919
|
function cryptoLibSource() {
|
|
@@ -266404,6 +267947,33 @@ function popupDialogLibSource() {
|
|
|
266404
267947
|
}
|
|
266405
267948
|
|
|
266406
267949
|
// ../nodegx-export/src/emit/component.ts
|
|
267950
|
+
function queryHasEffect(query2) {
|
|
267951
|
+
const fetched = query2.fetchWired;
|
|
267952
|
+
return fetched === void 0 || fetched.mount || fetched.onValues && query2.params?.wired !== void 0;
|
|
267953
|
+
}
|
|
267954
|
+
function queryParamsLiteral(params, whereLocal) {
|
|
267955
|
+
const parts = [];
|
|
267956
|
+
if (whereLocal !== void 0) parts.push(whereLocal === "where" ? "where" : `where: ${whereLocal}`);
|
|
267957
|
+
else if (params.where !== void 0) parts.push(`where: ${JSON.stringify(params.where)}`);
|
|
267958
|
+
if (params.sort !== void 0) parts.push(`sort: [${params.sort.map((s) => tsLiteral(s)).join(", ")}]`);
|
|
267959
|
+
if (params.limit !== void 0) parts.push(`limit: ${params.limit}`);
|
|
267960
|
+
if (params.skip !== void 0) parts.push(`skip: ${params.skip}`);
|
|
267961
|
+
return `{ ${parts.join(", ")} }`;
|
|
267962
|
+
}
|
|
267963
|
+
function wiredWhereExpr(wired, locals, valueCode) {
|
|
267964
|
+
const at = (index, mask) => {
|
|
267965
|
+
if (index === wired.params.length) {
|
|
267966
|
+
const where = wired.cases[mask];
|
|
267967
|
+
return where === void 0 ? "undefined" : wiredWhereCode(where, valueCode);
|
|
267968
|
+
}
|
|
267969
|
+
const unset = at(index + 1, mask);
|
|
267970
|
+
const set2 = at(index + 1, mask | 1 << index);
|
|
267971
|
+
if (unset === set2) return set2;
|
|
267972
|
+
const arm = (code) => code.includes(" ? ") ? `(${code})` : code;
|
|
267973
|
+
return `${locals[index]} === undefined ? ${arm(unset)} : ${arm(set2)}`;
|
|
267974
|
+
};
|
|
267975
|
+
return at(0, 0);
|
|
267976
|
+
}
|
|
266407
267977
|
var GENERATED_TS3 = "// @nodegx:generated (visual \u2014 provenance markers complete in EXP-007)\n";
|
|
266408
267978
|
var GENERATED_CSS2 = "/* @nodegx:generated (visual) */\n";
|
|
266409
267979
|
var PRINT_WIDTH = 100;
|
|
@@ -266643,6 +268213,45 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
266643
268213
|
const usedTimerHelpers = /* @__PURE__ */ new Set();
|
|
266644
268214
|
const jsFunByNode = plan.jsFunctions;
|
|
266645
268215
|
const referencedJsIds = /* @__PURE__ */ new Set();
|
|
268216
|
+
const pulsedRunIds = /* @__PURE__ */ new Set();
|
|
268217
|
+
const tickedRunIds = new Set(plan.tickedRuns.map((t) => t.nodeId));
|
|
268218
|
+
const tickedRecordArgs = (nodeId) => {
|
|
268219
|
+
const ticked3 = plan.tickedRuns.find((t) => t.nodeId === nodeId);
|
|
268220
|
+
return `${pulseNamesOf(jsFunByNode[nodeId]).held}, [${ticked3.watch.map((e) => exprCode(e, "handler")).join(", ")}]`;
|
|
268221
|
+
};
|
|
268222
|
+
const mountChains = [];
|
|
268223
|
+
const mountRefusals = /* @__PURE__ */ new Map();
|
|
268224
|
+
{
|
|
268225
|
+
const mountGateOf = (id) => {
|
|
268226
|
+
for (let at = id; at !== void 0; at = nodeById.get(at)?.parent) {
|
|
268227
|
+
const bound = plan.bindings[at]?.["mounted"];
|
|
268228
|
+
const literal2 = nodeById.get(at)?.parameters.find((p) => p.name === "mounted")?.value;
|
|
268229
|
+
const never2 = bound !== void 0 && bound.kind === "computed" && (bound.expr.kind === "undefined" || bound.expr.kind === "literal" && !bound.expr.value) || bound === void 0 && literal2?.kind === "literal" && literal2.value === false;
|
|
268230
|
+
const whose = at === id ? "it is" : `an element above it (${at}) is`;
|
|
268231
|
+
if (never2) return `${whose} never mounted, so its Did Mount never fires`;
|
|
268232
|
+
if (bound !== void 0 && !(bound.kind === "computed" && bound.expr.kind === "literal")) {
|
|
268233
|
+
return `${whose} mounted by a value, so its Did Mount fires when that value says, and a mount effect runs when the component mounts`;
|
|
268234
|
+
}
|
|
268235
|
+
if (at === plan.rootId) break;
|
|
268236
|
+
}
|
|
268237
|
+
return null;
|
|
268238
|
+
};
|
|
268239
|
+
const consider = (id) => {
|
|
268240
|
+
const actions = plan.handlers[id]?.["didMount"];
|
|
268241
|
+
if (actions === void 0) return;
|
|
268242
|
+
const why = mountGateOf(id);
|
|
268243
|
+
if (why === null) mountChains.push({ nodeId: id, actions });
|
|
268244
|
+
else mountRefusals.set(id, why);
|
|
268245
|
+
};
|
|
268246
|
+
const visit = (id) => {
|
|
268247
|
+
for (const child of plan.childrenOf[id] ?? []) visit(child);
|
|
268248
|
+
if (id === plan.rootId && plan.collapsedGroupId) consider(plan.collapsedGroupId);
|
|
268249
|
+
consider(id);
|
|
268250
|
+
};
|
|
268251
|
+
if (plan.rootId !== null) visit(plan.rootId);
|
|
268252
|
+
}
|
|
268253
|
+
const mountHandled = new Set(mountChains.map((m) => m.nodeId));
|
|
268254
|
+
const activePulses = /* @__PURE__ */ new Map();
|
|
266646
268255
|
const referencedStateNames = /* @__PURE__ */ new Set();
|
|
266647
268256
|
const stateVarByName = new Map(plan.stateVars.map((v) => [v.name, v]));
|
|
266648
268257
|
const stateSetterOf = (name) => stateVarByName.get(name)?.setterName ?? `set${name}`;
|
|
@@ -266685,6 +268294,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
266685
268294
|
if (expr.field === "name") usedFileHelpers.add("cloudFileName");
|
|
266686
268295
|
}
|
|
266687
268296
|
if (expr.kind === "id-out" && expr.viaState !== void 0) referencedStateNames.add(expr.viaState);
|
|
268297
|
+
if (expr.kind === "record-out" && expr.viaState !== void 0) referencedStateNames.add(expr.viaState);
|
|
266688
268298
|
if (expr.kind === "crypto-out" && expr.viaState !== void 0) referencedStateNames.add(expr.viaState);
|
|
266689
268299
|
if (expr.kind === "screen-out") usedScreenNodeIds.add(expr.nodeId);
|
|
266690
268300
|
if (expr.kind === "component-object-out" && expr.parent) usedParentObject = true;
|
|
@@ -266708,6 +268318,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
266708
268318
|
const collectActionUse = (action) => {
|
|
266709
268319
|
if (action.kind === "runtasks-run") collectExprUse(action.items);
|
|
266710
268320
|
if (action.kind === "stream-action" && action.value !== void 0) collectExprUse(action.value);
|
|
268321
|
+
if (action.kind === "query-fetch") for (const v of action.values) collectExprUse(v.expr);
|
|
266711
268322
|
if (action.kind === "state-set") {
|
|
266712
268323
|
referencedStateNames.add(action.name);
|
|
266713
268324
|
if (action.expr !== void 0) collectExprUse(action.expr);
|
|
@@ -266774,6 +268385,9 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
266774
268385
|
if (action.kind === "popup-show" || action.kind === "popup-close") {
|
|
266775
268386
|
action.then.forEach(collectActionUse);
|
|
266776
268387
|
}
|
|
268388
|
+
if (action.kind === "jsfun-pulses" || action.kind === "jsfun-pulse") {
|
|
268389
|
+
action.then.forEach(collectActionUse);
|
|
268390
|
+
}
|
|
266777
268391
|
if (action.kind === "api-call") {
|
|
266778
268392
|
for (const arg of action.args) {
|
|
266779
268393
|
if (arg.kind === "expr") collectExprUse(arg.expr);
|
|
@@ -266783,6 +268397,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
266783
268397
|
}
|
|
266784
268398
|
}
|
|
266785
268399
|
action.then.forEach(collectActionUse);
|
|
268400
|
+
action.failThen.forEach(collectActionUse);
|
|
266786
268401
|
}
|
|
266787
268402
|
if (action.kind === "http-call" || action.kind === "cloud-call") {
|
|
266788
268403
|
action.args.forEach((a8) => collectExprUse(a8.expr));
|
|
@@ -266867,6 +268482,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
266867
268482
|
referencedJsIds.add(action.nodeId);
|
|
266868
268483
|
jsArgExprs(action.nodeId).forEach(collectExprUse);
|
|
266869
268484
|
action.then.forEach(collectActionUse);
|
|
268485
|
+
if (runIsPulsed(action) || action.materialize !== void 0 && jsFunByNode[action.nodeId]?.pulsed === true) pulsedRunIds.add(action.nodeId);
|
|
266870
268486
|
for (const name of jsFunByNode[action.nodeId]?.variables ?? []) {
|
|
266871
268487
|
if (variableByName.has(name)) usedVariableNames.add(name);
|
|
266872
268488
|
}
|
|
@@ -266881,14 +268497,15 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
266881
268497
|
...plan.branchEffects.map((e) => e.action),
|
|
266882
268498
|
// EXP-011 §39. A Value Changed's chain earns imports and references exactly as a handler's does.
|
|
266883
268499
|
...plan.valueChangedEffects.flatMap((e) => e.actions),
|
|
268500
|
+
// P18-S119-TICKEDRUN. The run a ticked input starts is a chain like any other.
|
|
268501
|
+
...plan.tickedRuns.map((t) => t.action),
|
|
266884
268502
|
// EXP-011 §49. The listeners and the arrive chain are chains like any other: a store write
|
|
266885
268503
|
// inside one earns its import here, a state setter its row.
|
|
266886
268504
|
...plan.statesMachines.flatMap(
|
|
266887
268505
|
(m) => [m.listeners.stateChanged, m.listeners.done, m.listeners.unchanged, m.listeners.failure, ...Object.values(m.listeners.reached)].flatMap((chain) => chain ?? [])
|
|
266888
268506
|
),
|
|
266889
268507
|
...plan.animations.flatMap((a8) => a8.arrive ?? []),
|
|
266890
|
-
//
|
|
266891
|
-
...plan.recordEffects.map((e) => e.action),
|
|
268508
|
+
// P18-S113-IDBINDREAD. A Record's Id bind runs no chain and reads nothing: it earns only its own lines (`recordBinds`).
|
|
266892
268509
|
// EXP-011 §67. A Variable's authored Value is a store write run from a mount effect — it earns its import here.
|
|
266893
268510
|
...plan.variableSeeds.map(variableSeedAction),
|
|
266894
268511
|
// EXP-011 §71. An Object's own authored property values are a store write run from a mount effect — it earns its import here.
|
|
@@ -266908,36 +268525,40 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
266908
268525
|
allActions.forEach(collectActionUse);
|
|
266909
268526
|
for (const stream of plan.streams) if (stream.store !== void 0) usedStoreNames.add(stream.store);
|
|
266910
268527
|
const deepActions = (actions) => actions.flatMap(
|
|
266911
|
-
(a8) => a8.kind === "branch" ? [a8, ...deepActions(a8.whenTrue), ...deepActions(a8.whenFalse)] :
|
|
266912
|
-
// EXP-011 §
|
|
266913
|
-
a8.kind === "
|
|
266914
|
-
// EXP-011 §
|
|
266915
|
-
|
|
266916
|
-
|
|
266917
|
-
|
|
266918
|
-
//
|
|
266919
|
-
|
|
266920
|
-
|
|
266921
|
-
//
|
|
266922
|
-
a8.kind === "
|
|
266923
|
-
// EXP-011 §
|
|
266924
|
-
|
|
266925
|
-
|
|
266926
|
-
//
|
|
266927
|
-
a8.kind === "
|
|
266928
|
-
// EXP-011 §
|
|
266929
|
-
|
|
266930
|
-
|
|
266931
|
-
|
|
266932
|
-
|
|
266933
|
-
|
|
266934
|
-
|
|
266935
|
-
|
|
266936
|
-
|
|
266937
|
-
|
|
266938
|
-
|
|
266939
|
-
|
|
266940
|
-
|
|
268528
|
+
(a8) => a8.kind === "branch" ? [a8, ...deepActions(a8.whenTrue), ...deepActions(a8.whenFalse)] : (
|
|
268529
|
+
// EXP-011 §90. A record or user verb's Failure chain rides the same line.
|
|
268530
|
+
a8.kind === "api-call" || a8.kind === "http-call" || a8.kind === "cloud-call" || a8.kind === "record-fetch" || a8.kind === "external-link" ? [a8, ...deepActions(a8.then), ...deepActions(a8.failThen)] : (
|
|
268531
|
+
// EXP-011 §45. The two-arm files actions ride the line above's shape; the picker has a third arm.
|
|
268532
|
+
a8.kind === "file-upload" || a8.kind === "file-sign" ? [a8, ...deepActions(a8.then), ...deepActions(a8.failThen)] : a8.kind === "file-pick" ? [a8, ...deepActions(a8.then), ...deepActions(a8.unchangedThen), ...deepActions(a8.failThen)] : (
|
|
268533
|
+
// EXP-011 §15. `Navigate To Path` carries two chains and neither is `failThen`;
|
|
268534
|
+
// without this line `usesNavigate` below cannot see a second navigation nested in
|
|
268535
|
+
// the first one's Done, and the `useNavigate()` hook it needs goes undeclared.
|
|
268536
|
+
a8.kind === "navigate-path" ? [a8, ...deepActions(a8.then), ...deepActions(a8.failThen), ...deepActions(a8.completedThen)] : (
|
|
268537
|
+
// EXP-011 §37. Two arms, so it cannot join the single-chain list below — a popup
|
|
268538
|
+
// opened from a UUID's Failure chain is a popup nothing here knows is attached.
|
|
268539
|
+
a8.kind === "id-new" ? [a8, ...deepActions(a8.then), ...deepActions(a8.failThen)] : (
|
|
268540
|
+
// EXP-011 §76. Four arms: the extractor's two result chains print inside the Done arm.
|
|
268541
|
+
a8.kind === "crypto-call" ? [a8, ...deepActions(a8.then), ...deepActions(a8.failThen), ...deepActions(a8.foundThen), ...deepActions(a8.notFoundThen)] : (
|
|
268542
|
+
// EXP-011 §60. Three chains — a popup opened from a record write's Done, or a raise in a parent write's
|
|
268543
|
+
// Failure, is nowhere without this line.
|
|
268544
|
+
a8.kind === "component-object-set" ? [a8, ...deepActions(a8.then), ...deepActions(a8.completedThen), ...deepActions(a8.failThen)] : (
|
|
268545
|
+
// EXP-011 §61. The pusher's three callback families; the Pop's two arms.
|
|
268546
|
+
a8.kind === "stack-push" ? [a8, ...deepActions(a8.then), ...deepActions(a8.unchangedThen), ...a8.backActions.flatMap((b) => deepActions(b.then))] : a8.kind === "stack-pop" ? [a8, ...deepActions(a8.then), ...deepActions(a8.failThen)] : (
|
|
268547
|
+
// EXP-011 §39. Four chains — a popup opened from a Delay's Finished is the shape
|
|
268548
|
+
// this node exists for, and it is nowhere without this line.
|
|
268549
|
+
a8.kind === "delay" ? [
|
|
268550
|
+
a8,
|
|
268551
|
+
...deepActions(a8.then),
|
|
268552
|
+
...deepActions(a8.unchangedThen),
|
|
268553
|
+
...deepActions(a8.startedThen),
|
|
268554
|
+
...deepActions(a8.finishedThen)
|
|
268555
|
+
] : (
|
|
268556
|
+
// EXP-011 §55. The array mutators own chains too (§30.3's walker had the same hole): a Clear's
|
|
268557
|
+
// two outcome arms and its minted Failure arm, an Insert's minted Failure arm, a Remove's Done.
|
|
268558
|
+
a8.kind === "collection-clear" ? [a8, ...deepActions(a8.then), ...deepActions(a8.unchangedThen), ...deepActions(a8.minted?.failThen ?? [])] : a8.kind === "collection-add" ? [a8, ...deepActions(a8.minted?.failThen ?? [])] : a8.kind === "log" || a8.kind === "popup-show" || a8.kind === "popup-close" || a8.kind === "jsfun-run" || // EXP-011 §86. The pulse loop and each signal's arm.
|
|
268559
|
+
a8.kind === "jsfun-pulses" || a8.kind === "jsfun-pulse" || a8.kind === "date-now-read" || a8.kind === "collection-remove" || a8.kind === "array-new" || // EXP-011 §61. A Reset's one chain.
|
|
268560
|
+
a8.kind === "stack-reset" ? [a8, ...deepActions(a8.then)] : [a8]
|
|
268561
|
+
)
|
|
266941
268562
|
)
|
|
266942
268563
|
)
|
|
266943
268564
|
)
|
|
@@ -267005,6 +268626,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
267005
268626
|
}
|
|
267006
268627
|
if (expr.kind === "now-out" && expr.viaState !== void 0) referencedStateNames.add(expr.viaState);
|
|
267007
268628
|
if (expr.kind === "id-out" && expr.viaState !== void 0) referencedStateNames.add(expr.viaState);
|
|
268629
|
+
if (expr.kind === "record-out" && expr.viaState !== void 0) referencedStateNames.add(expr.viaState);
|
|
267008
268630
|
if (expr.kind === "crypto-out" && expr.viaState !== void 0) referencedStateNames.add(expr.viaState);
|
|
267009
268631
|
if (expr.kind === "screen-out") usedScreenNodeIds.add(expr.nodeId);
|
|
267010
268632
|
if (expr.kind === "component-object-out" && expr.parent) usedParentObject = true;
|
|
@@ -267051,21 +268673,23 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
267051
268673
|
if (effect.action.kind === "branch") hookExprSources(effect.action.cond);
|
|
267052
268674
|
}
|
|
267053
268675
|
for (const effect of plan.valueChangedEffects) hookExprSources(effect.watch);
|
|
268676
|
+
for (const ticked3 of plan.tickedRuns) ticked3.watch.forEach(hookExprSources);
|
|
267054
268677
|
for (const animation of plan.animations) {
|
|
267055
268678
|
hookExprSources(animation.target);
|
|
267056
268679
|
hookExprSources(animation.duration);
|
|
267057
268680
|
hookExprSources(animation.delay);
|
|
267058
268681
|
}
|
|
267059
268682
|
for (const machine of plan.statesMachines) if (machine.follow !== void 0) hookExprSources(machine.follow);
|
|
268683
|
+
for (const query2 of plan.queries) {
|
|
268684
|
+
if (!queryHasEffect(query2)) continue;
|
|
268685
|
+
for (const value of query2.wiredValues ?? []) hookExprSources(value.expr);
|
|
268686
|
+
}
|
|
267060
268687
|
for (const drag of plan.drags) for (const option of drag.options) hookExprSources(option.expr);
|
|
267061
268688
|
for (const stream of plan.streams) for (const entry of stream.config) hookExprSources(entry.expr);
|
|
267062
268689
|
for (const run6 of plan.runTasks) {
|
|
267063
268690
|
hookExprSources(run6.maxRunningTasks);
|
|
267064
268691
|
hookExprSources(run6.stopOnFailure);
|
|
267065
268692
|
}
|
|
267066
|
-
for (const effect of plan.recordEffects) {
|
|
267067
|
-
if (effect.action.kind === "record-fetch") hookExprSources(effect.action.id);
|
|
267068
|
-
}
|
|
267069
268693
|
for (const stateVar of plan.stateVars) {
|
|
267070
268694
|
if (stateVar.origin === "control" && plan.roleOf[stateVar.originNodeId] !== void 0) {
|
|
267071
268695
|
referencedStateNames.add(stateVar.name);
|
|
@@ -267096,7 +268720,19 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
267096
268720
|
}
|
|
267097
268721
|
}
|
|
267098
268722
|
const referencedStateVars = plan.stateVars.filter((v) => referencedStateNames.has(v.name));
|
|
267099
|
-
const
|
|
268723
|
+
const liveRecordRows = plan.recordReads.flatMap((read2) => {
|
|
268724
|
+
const row = read2.live && !read2.binds ? referencedStateVars.find((v) => v.originNodeId === read2.nodeId && v.origin === "record-row") : void 0;
|
|
268725
|
+
return row === void 0 ? [] : [{ read: read2, row }];
|
|
268726
|
+
});
|
|
268727
|
+
const recordBinds = plan.recordEffects.flatMap((effect) => {
|
|
268728
|
+
const action = effect.action;
|
|
268729
|
+
if (action.kind !== "record-fetch" || !effect.held) return [];
|
|
268730
|
+
const row = referencedStateVars.find((v) => v.originNodeId === effect.nodeId && v.origin === "record-row");
|
|
268731
|
+
const read2 = plan.recordReads.find((r) => r.nodeId === effect.nodeId);
|
|
268732
|
+
return row === void 0 || read2 === void 0 ? [] : [{ effect, action, read: read2, row }];
|
|
268733
|
+
});
|
|
268734
|
+
for (const { action } of recordBinds) hookExprSources(action.id);
|
|
268735
|
+
const usesNavigate = deepActions(allActions).some((a8) => a8.kind === "navigate" && !a8.newTab || a8.kind === "navigate-path");
|
|
267100
268736
|
const raisesAppErrors = deepActions(allActions).some((a8) => RAISING_ACTION_KINDS.has(a8.kind) || mintedGuards(a8) || a8.kind === "component-object-set" && a8.parent);
|
|
267101
268737
|
const reserved = /* @__PURE__ */ new Set([
|
|
267102
268738
|
"event",
|
|
@@ -267149,7 +268785,11 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
267149
268785
|
const deferrals = deferralsByNode.get(nodeId);
|
|
267150
268786
|
if (deferrals === void 0) return [];
|
|
267151
268787
|
flushed3.add(nodeId);
|
|
267152
|
-
const
|
|
268788
|
+
const undrawn = plan.roleOf[nodeId] !== void 0 ? undrawnReasonOf(plan, component, nodeId) : null;
|
|
268789
|
+
const out = [
|
|
268790
|
+
undrawn === null ? `TODO(export): node ${commentSafe(nodeId)} renders, and part of what the graph` : `TODO(export): node ${commentSafe(nodeId)} is not drawn (${commentSafe(undrawn)}), and part of what the graph`,
|
|
268791
|
+
"asked of it did not survive:"
|
|
268792
|
+
];
|
|
267153
268793
|
for (const d of deferrals) {
|
|
267154
268794
|
out.push(` - ${commentSafe(d.subject)} ${commentSafe(d.reason)}.`);
|
|
267155
268795
|
if (d.origin !== null) out.push(` In the graph it is fed by ${commentSafe(d.origin)}.`);
|
|
@@ -267440,6 +269080,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
267440
269080
|
return SIMPLE_REF.test(code) || /^[A-Za-z_$][\w$.]*\(\)$/.test(code) || code.endsWith(")") ? code : `(${code})`;
|
|
267441
269081
|
};
|
|
267442
269082
|
const jsArgsObject = (def, mode) => {
|
|
269083
|
+
if (def.kind === "expression" && def.inputs.length === 0) return "";
|
|
267443
269084
|
const entries2 = def.inputs.filter((i) => i.expr !== void 0).map((i) => {
|
|
267444
269085
|
const code = exprCode(i.expr, mode);
|
|
267445
269086
|
const key3 = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(i.name) ? i.name : JSON.stringify(i.name);
|
|
@@ -267478,12 +269119,13 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
267478
269119
|
if (node === "pattern") return `${local}.${field2 ?? "match"}`;
|
|
267479
269120
|
return `${local}.${node === "hash" ? "digest" : "value"}`;
|
|
267480
269121
|
};
|
|
269122
|
+
let announcedCode;
|
|
267481
269123
|
const exprCode = (expr, mode) => {
|
|
267482
269124
|
switch (expr.kind) {
|
|
267483
269125
|
case "prop":
|
|
267484
269126
|
return propName(expr.name);
|
|
267485
269127
|
case "input-text":
|
|
267486
|
-
return "event.target.value";
|
|
269128
|
+
return announcedCode?.inputId === expr.inputId ? announcedCode.code : "event.target.value";
|
|
267487
269129
|
/**
|
|
267488
269130
|
* A url parameter (EXP-011 Tier 2.5) — the query first, the path segment behind it.
|
|
267489
269131
|
*
|
|
@@ -267789,15 +269431,24 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
267789
269431
|
// Expression's typed getters, verbatim semantics (expression.ts).
|
|
267790
269432
|
case "jsfun-out": {
|
|
267791
269433
|
const def = jsFunByNode[expr.nodeId];
|
|
267792
|
-
if (expr.viaState !== void 0 && mode === "
|
|
269434
|
+
if (expr.viaState !== void 0 && mode === "handler" && def.kind === "function" && pulsedRunIds.has(expr.nodeId)) {
|
|
269435
|
+
const delivered = memberExpr(`${pulseNamesOf(def).node}.delivered`, expr.output);
|
|
269436
|
+
if (expr.fold === "string") return `String(${delivered} ?? '')`;
|
|
269437
|
+
if (expr.fold === "number") return `(typeof ${delivered} === 'number' ? ${delivered} : Number(${delivered}) || 0)`;
|
|
269438
|
+
if (expr.fold === "boolean") return `!!${delivered}`;
|
|
269439
|
+
return delivered;
|
|
269440
|
+
}
|
|
269441
|
+
if (expr.viaState !== void 0) {
|
|
267793
269442
|
const base2 = def.kind !== "expression" ? /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(expr.output) ? `${expr.viaState}?.${expr.output}` : `${expr.viaState}?.[${JSON.stringify(expr.output)}]` : expr.viaState;
|
|
267794
269443
|
if (expr.fold === "string") return `String(${base2} ?? '')`;
|
|
267795
269444
|
if (expr.fold === "number") return `(typeof ${base2} === 'number' ? ${base2} : Number(${base2}) || 0)`;
|
|
267796
269445
|
if (expr.fold === "boolean") return `!!${base2}`;
|
|
267797
269446
|
return base2;
|
|
267798
269447
|
}
|
|
269448
|
+
const pulse = mode === "handler" ? activePulses.get(expr.nodeId) : void 0;
|
|
269449
|
+
const ran2 = mode === "handler" && pulse === void 0 && pulsedRunIds.has(expr.nodeId) ? `${pulseNamesOf(def).run}.outputs` : void 0;
|
|
267799
269450
|
const local = mode === "render" ? jsLocals.get(expr.nodeId) : void 0;
|
|
267800
|
-
const callee = local ?? `${def.fnName}(${jsArgsObject(def, mode)})`;
|
|
269451
|
+
const callee = pulse !== void 0 ? `${pulse}.values` : ran2 ?? local ?? `${def.fnName}(${jsArgsObject(def, mode)})`;
|
|
267801
269452
|
const base = def.kind === "function" ? memberExpr(callee, expr.output) : callee;
|
|
267802
269453
|
if (expr.fold === "string") return `String(${base} ?? '')`;
|
|
267803
269454
|
if (expr.fold === "number") return `(typeof ${base} === 'number' ? ${base} : Number(${base}) || 0)`;
|
|
@@ -267914,7 +269565,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
267914
269565
|
break;
|
|
267915
269566
|
case "jsfun-out": {
|
|
267916
269567
|
if (e.viaState !== void 0) {
|
|
267917
|
-
add(e
|
|
269568
|
+
add(exprCode({ ...e, fold: void 0 }, "render"));
|
|
267918
269569
|
break;
|
|
267919
269570
|
}
|
|
267920
269571
|
const local = jsLocals.get(e.nodeId);
|
|
@@ -267951,7 +269602,14 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
267951
269602
|
// A jsfun-run is only its done-chain (EXP-003 §4 A2h): output reads inline the call
|
|
267952
269603
|
// at their sinks, so the run itself needs no statement — unless it materializes its
|
|
267953
269604
|
// output record (CONTROLLED-STATE §4f), which is one setter statement before the chain.
|
|
267954
|
-
a8.kind === "jsfun-run" ? a8.materialize !== void 0 ? [{ ...a8, then: [] }, ...expandActions(a8.then)] : expandActions(a8.then) :
|
|
269605
|
+
a8.kind === "jsfun-run" ? a8.materialize !== void 0 || runIsPulsed(a8) ? [{ ...a8, then: [] }, ...expandActions(a8.then)] : expandActions(a8.then) : (
|
|
269606
|
+
// P18-S116-QUERYFETCH. A Fetch into a query the export does not keep (nothing it renders reads the rows) asks
|
|
269607
|
+
// for nothing anyone sees; the query's own row in the report says why it is not kept.
|
|
269608
|
+
a8.kind === "query-fetch" && !plan.queries.some((q) => q.nodeId === a8.nodeId) ? [] : (
|
|
269609
|
+
// EXP-011 §87. A literal Condition's one side is following statements.
|
|
269610
|
+
a8.kind === "branch" && a8.flat === true ? expandActions(a8.whenTrue) : [a8]
|
|
269611
|
+
)
|
|
269612
|
+
)
|
|
267955
269613
|
)
|
|
267956
269614
|
)
|
|
267957
269615
|
)
|
|
@@ -268011,7 +269669,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
268011
269669
|
case "branch":
|
|
268012
269670
|
return reads(a8.cond) || a8.whenTrue.some(inAction) || a8.whenFalse.some(inAction);
|
|
268013
269671
|
case "api-call":
|
|
268014
|
-
return a8.args.some((arg) => arg.kind === "expr" ? reads(arg.expr) : arg.props.some((p) => reads(p.expr))) || a8.then.some(inAction);
|
|
269672
|
+
return a8.args.some((arg) => arg.kind === "expr" ? reads(arg.expr) : arg.props.some((p) => reads(p.expr))) || a8.then.some(inAction) || a8.failThen.some(inAction);
|
|
268015
269673
|
case "http-call":
|
|
268016
269674
|
// EXP-011 §41. The same, one node over.
|
|
268017
269675
|
case "cloud-call":
|
|
@@ -268062,12 +269720,17 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
268062
269720
|
return reads(a8.items);
|
|
268063
269721
|
case "runtasks-abort":
|
|
268064
269722
|
return false;
|
|
269723
|
+
// P18-S116-QUERYFETCH. The wired filter values are read at the pulse.
|
|
269724
|
+
case "query-fetch":
|
|
269725
|
+
return a8.values.some((v) => reads(v.expr));
|
|
268065
269726
|
// EXP-011 §58. The data is read at the pulse.
|
|
268066
269727
|
case "stream-action":
|
|
268067
269728
|
return a8.value !== void 0 && reads(a8.value);
|
|
268068
269729
|
case "popup-show":
|
|
268069
269730
|
case "popup-close":
|
|
268070
269731
|
case "jsfun-run":
|
|
269732
|
+
case "jsfun-pulses":
|
|
269733
|
+
case "jsfun-pulse":
|
|
268071
269734
|
case "date-now-read":
|
|
268072
269735
|
return a8.then.some(inAction);
|
|
268073
269736
|
// EXP-011 §37. Both arms, on the hazard the two notes above name: the Failure arm can
|
|
@@ -268120,6 +269783,11 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
|
|
|
268120
269783
|
return [`if (${m.stateName} === null) {`, ...guard, `${at}} else {`, ...elseLines.map((l) => `${inner}${l}`), `${at}}`].join("\n");
|
|
268121
269784
|
};
|
|
268122
269785
|
const actionCode = (action, indent = 0) => {
|
|
269786
|
+
if ("detached" in action && action.detached === true) {
|
|
269787
|
+
return `void (async () => {
|
|
269788
|
+
${pad(indent + 2)}${actionCode({ ...action, detached: void 0 }, indent + 2)}
|
|
269789
|
+
${pad(indent)}})()`;
|
|
269790
|
+
}
|
|
268123
269791
|
switch (action.kind) {
|
|
268124
269792
|
/**
|
|
268125
269793
|
* The url the runtime's `getRelativeURL` would build, built here instead.
|
|
@@ -268246,7 +269914,8 @@ ${at}`);
|
|
|
268246
269914
|
);
|
|
268247
269915
|
const query2 = action.query.map((param) => `${encodeURIComponent(param.name)}=\${${urlValue(param.expr)}}`).join("&");
|
|
268248
269916
|
const url = query2 === "" ? filled : `${filled}?${query2}`;
|
|
268249
|
-
|
|
269917
|
+
const literal2 = action.pathParams.length === 0 && action.query.length === 0 ? `'${url}'` : `\`${url}\``;
|
|
269918
|
+
return action.newTab ? `window.open(${literal2}, '_blank')` : `navigate(${literal2})`;
|
|
268250
269919
|
}
|
|
268251
269920
|
// The optional call is the runtime's hasOutput/unwired case: a parent that passes
|
|
268252
269921
|
// nothing gets nothing (COMPONENT-OUTPUTS-TARGET §3).
|
|
@@ -268400,16 +270069,63 @@ ${at}`);
|
|
|
268400
270069
|
return `${action.local}.run(${exprCode(action.items, "handler")})`;
|
|
268401
270070
|
case "runtasks-abort":
|
|
268402
270071
|
return `${action.local}.abort()`;
|
|
270072
|
+
/**
|
|
270073
|
+
* P18-S116-QUERYFETCH. A Query Records' Fetch: the query's own settings, a wired filter's `where` from the values as
|
|
270074
|
+
* the handler reads them at the pulse (a store fresh, `.get()`). Not awaited: the runtime schedules its fetch.
|
|
270075
|
+
*/
|
|
270076
|
+
case "query-fetch": {
|
|
270077
|
+
const query2 = plan.queries.find((q) => q.nodeId === action.nodeId);
|
|
270078
|
+
const wired = query2.params?.wired;
|
|
270079
|
+
if (query2.params === void 0) return `void ${query2.fetchName}().then(${query2.setterName})`;
|
|
270080
|
+
if (wired === void 0) return `void ${query2.fetchName}(${queryParamsLiteral(query2.params)}).then(${query2.setterName})`;
|
|
270081
|
+
const codes = action.values.map((v) => exprCode(v.expr, "handler"));
|
|
270082
|
+
const codeOf = (name) => codes[action.values.findIndex((v) => v.name === name)];
|
|
270083
|
+
const tested = codes.map((code) => SIMPLE_REF.test(code) ? code : `(${code})`);
|
|
270084
|
+
const valueCode = (name, date3) => query2.wiredClient ? `filterValue(${codeOf(name)}${date3 ? ", true" : ""})` : codeOf(name);
|
|
270085
|
+
return `void ${query2.fetchName}(${queryParamsLiteral(query2.params, wiredWhereExpr(wired, tested, valueCode))}).then(${query2.setterName})`;
|
|
270086
|
+
}
|
|
268403
270087
|
/** A streaming node's action (EXP-011 §58) — one call on the handle, the data read at the pulse where the verb takes it. */
|
|
268404
270088
|
case "stream-action":
|
|
268405
270089
|
return `${action.local}.${action.verb}(${action.value !== void 0 ? exprCode(action.value, "handler") : ""})`;
|
|
268406
270090
|
case "jsfun-run": {
|
|
270091
|
+
const def = jsFunByNode[action.nodeId];
|
|
270092
|
+
const record2 = action.ticked !== true && tickedRunIds.has(action.nodeId) ? `tickedArrival(${tickedRecordArgs(action.nodeId)});
|
|
270093
|
+
${pad(indent)}` : "";
|
|
270094
|
+
if (pulsedRunIds.has(action.nodeId) && action.then.length === 0) {
|
|
270095
|
+
const names2 = pulseNamesOf(def);
|
|
270096
|
+
const run6 = `${record2}const ${names2.run} = runFunction(${def.fnName}, ${jsArgsObject(def, "handler")}, ${names2.node})`;
|
|
270097
|
+
if (action.materialize === void 0) return run6;
|
|
270098
|
+
return `${run6};
|
|
270099
|
+
${pad(indent)}${stateSetterOf(action.materialize)}({ ...${names2.run}.outputs })`;
|
|
270100
|
+
}
|
|
268407
270101
|
if (action.materialize !== void 0) {
|
|
268408
|
-
|
|
268409
|
-
return `${stateSetterOf(action.materialize)}(${def.fnName}(${jsArgsObject(def, "handler")}))`;
|
|
270102
|
+
return `${record2}${stateSetterOf(action.materialize)}(${def.fnName}(${jsArgsObject(def, "handler")}))`;
|
|
268410
270103
|
}
|
|
268411
270104
|
return expandActions(action.then).map(actionCode).join("; ");
|
|
268412
270105
|
}
|
|
270106
|
+
/**
|
|
270107
|
+
* EXP-011 §86. One loop over the pulses the run sent, in the order it sent them; each arm is one signal's chain,
|
|
270108
|
+
* and inside it this Function's outputs read as that pulse's targets read them (`exprCode`'s `jsfun-out`).
|
|
270109
|
+
*/
|
|
270110
|
+
case "jsfun-pulses": {
|
|
270111
|
+
const names2 = pulseNamesOf(jsFunByNode[action.nodeId]);
|
|
270112
|
+
const arms = action.then.filter((a8) => a8.kind === "jsfun-pulse");
|
|
270113
|
+
activePulses.set(action.nodeId, names2.pulse);
|
|
270114
|
+
try {
|
|
270115
|
+
const lines = [`for (const ${names2.pulse} of ${names2.run}.pulses) {`];
|
|
270116
|
+
arms.forEach((arm, i) => {
|
|
270117
|
+
const head = `${i === 0 ? "" : "} else "}if (${names2.pulse}.signal === ${tsLiteral(arm.signal)}) {`;
|
|
270118
|
+
lines.push(`${pad(indent + 2)}${head}`, ...blockBody(expandActions(arm.then), indent + 4));
|
|
270119
|
+
});
|
|
270120
|
+
lines.push(`${pad(indent + 2)}}`, `${pad(indent)}}`);
|
|
270121
|
+
return lines.join("\n");
|
|
270122
|
+
} finally {
|
|
270123
|
+
activePulses.delete(action.nodeId);
|
|
270124
|
+
}
|
|
270125
|
+
}
|
|
270126
|
+
// Printed by its loop above; never on its own.
|
|
270127
|
+
case "jsfun-pulse":
|
|
270128
|
+
return expandActions(action.then).map((a8) => actionCode(a8, indent)).join("; ");
|
|
268413
270129
|
// The functional updates are §3.3's closure-staleness-immune forms.
|
|
268414
270130
|
case "state-set": {
|
|
268415
270131
|
const setter = stateSetterOf(action.name);
|
|
@@ -268428,7 +270144,11 @@ ${at}`);
|
|
|
268428
270144
|
);
|
|
268429
270145
|
if (action.coerceId === "string") args[0] = `String(${args[0]} ?? '')`;
|
|
268430
270146
|
const inner = pad(indent + 2);
|
|
270147
|
+
const boundId = action.guardId && action.coerceId !== "string" && args[0].endsWith(")") ? `${action.errorState.replace(/Error$/, "")}RecordId` : void 0;
|
|
270148
|
+
const idRead = args[0];
|
|
270149
|
+
if (boundId !== void 0) args[0] = boundId;
|
|
268431
270150
|
const body2 = [
|
|
270151
|
+
...boundId !== void 0 ? [`${inner}const ${boundId} = ${idRead};`] : [],
|
|
268432
270152
|
// The runtime's "Missing Record Id" (RECORD-VERBS-TARGET §1), thrown rather than
|
|
268433
270153
|
// branched so it lands in the catch below — which is already the graph's own error
|
|
268434
270154
|
// path — skips the call and the done chain, and narrows the id for the call itself.
|
|
@@ -268461,6 +270181,8 @@ ${at}`);
|
|
|
268461
270181
|
`${inner}${stateSetterOf(action.errorState)}(${action.errorState}Message);`,
|
|
268462
270182
|
// EXP-011 §54. The Error row first, then the raise, then nothing — the order the setError funnels keep.
|
|
268463
270183
|
`${inner}${raiseLine(action.nodeId, errorCodeOf(action), `${action.errorState}Message`)}`,
|
|
270184
|
+
// EXP-011 §90. Then the Failure pulse's chain: `reportOutcome` raises before it sends the signal.
|
|
270185
|
+
...expandActions(action.failThen).map((a8) => `${inner}${actionCode(a8, indent + 2)};`),
|
|
268464
270186
|
`${pad(indent)}}`
|
|
268465
270187
|
].join("\n");
|
|
268466
270188
|
}
|
|
@@ -268507,9 +270229,10 @@ ${at}`);
|
|
|
268507
270229
|
/**
|
|
268508
270230
|
* A `Record`'s Fetch (EXP-011 §43). One argument, the Id; the runtime's own `Missing Id.`
|
|
268509
270231
|
* thrown inside the try where the Id is empty — `scheduleFetch` reports it as Failure before
|
|
268510
|
-
* any request, so the catch is exactly the arm it belongs in. The row is replaced
|
|
268511
|
-
*
|
|
268512
|
-
*
|
|
270232
|
+
* any request, so the catch is exactly the arm it belongs in. The row is replaced by what
|
|
270233
|
+
* the read answers. Where the app has a live Record (P18-S111-RECORDLIVE) that answer is the
|
|
270234
|
+
* client's one copy of the record, the read merged into it field by field as `_fromJSON`
|
|
270235
|
+
* merges into `Model.get(id)`; otherwise it is the wire's answer alone.
|
|
268513
270236
|
*/
|
|
268514
270237
|
case "record-fetch": {
|
|
268515
270238
|
const names2 = recordNamesOf(action.nodeId);
|
|
@@ -268517,11 +270240,9 @@ ${at}`);
|
|
|
268517
270240
|
const idCode = action.coerceId === "string" ? `String(${exprCode(action.id, "handler")} ?? '')` : exprCode(action.id, "handler");
|
|
268518
270241
|
const idLocal = names2.idLocal;
|
|
268519
270242
|
const empty3 = `${idLocal} === undefined || ${idLocal} === null || ${idLocal} === ''`;
|
|
268520
|
-
const guard = action.id.kind === "literal" ? [] :
|
|
268521
|
-
const clear2 = action.viaEffect && action.materialize !== void 0 ? [`${inner}${stateSetterOf(action.materialize)}(undefined);`] : [];
|
|
270243
|
+
const guard = action.id.kind === "literal" ? [] : [`${inner}if (${empty3}) throw new Error('Missing Id.');`];
|
|
268522
270244
|
return [
|
|
268523
270245
|
"try {",
|
|
268524
|
-
...clear2,
|
|
268525
270246
|
`${inner}const ${idLocal} = ${idCode};`,
|
|
268526
270247
|
...guard,
|
|
268527
270248
|
`${inner}const ${names2.answerLocal} = await ${action.fnName}(${idLocal});`,
|
|
@@ -269044,7 +270765,9 @@ ${pad(indent)}}`;
|
|
|
269044
270765
|
}
|
|
269045
270766
|
};
|
|
269046
270767
|
function actionIsStatement(a8) {
|
|
269047
|
-
return a8.kind === "branch" ||
|
|
270768
|
+
return a8.kind === "branch" || // EXP-011 §86. A run with pulses declares its `const`, and its loop is a `for`.
|
|
270769
|
+
a8.kind === "jsfun-run" && pulsedRunIds.has(a8.nodeId) || // P18-S119-TICKEDRUN. A Run that records what it read is two statements: the record, then the run. The sixth instance.
|
|
270770
|
+
a8.kind === "jsfun-run" && a8.ticked !== true && tickedRunIds.has(a8.nodeId) || a8.kind === "jsfun-pulses" || a8.kind === "popup-close" && a8.then.length > 0 || a8.kind === "collection-clear" && (a8.then.length > 0 || a8.unchangedThen.length > 0) || // EXP-011 §55. The mint is statements; a guarded mutator is an `if`/`else`.
|
|
269048
270771
|
a8.kind === "array-new" || mintedGuards(a8) || /**
|
|
269049
270772
|
* 🔴 `Now`'s Read declares a `const`, and a `const` is a statement (EXP-011 Tier 1.3).
|
|
269050
270773
|
* `() => const clockRead = new Date(); setClock(clockRead)` does not parse — the fourth
|
|
@@ -269086,17 +270809,21 @@ ${pad(indent)}}`;
|
|
|
269086
270809
|
a8.kind === "stack-push" || a8.kind === "stack-pop" || a8.kind === "stack-reset" && a8.then.length > 0;
|
|
269087
270810
|
}
|
|
269088
270811
|
function actionTakesNoTerminator(a8) {
|
|
269089
|
-
|
|
269090
|
-
|
|
269091
|
-
|
|
269092
|
-
|
|
269093
|
-
|
|
269094
|
-
|
|
269095
|
-
|
|
269096
|
-
|
|
269097
|
-
|
|
269098
|
-
|
|
269099
|
-
|
|
270812
|
+
if ("detached" in a8 && a8.detached === true) return false;
|
|
270813
|
+
return (
|
|
270814
|
+
// EXP-011 §86. The pulse loop ends in `}`.
|
|
270815
|
+
a8.kind === "jsfun-pulses" || a8.kind === "api-call" || a8.kind === "http-call" || // EXP-011 §41. A try/catch, like the two above.
|
|
270816
|
+
a8.kind === "cloud-call" || // EXP-011 §43. A try/catch, like the three above.
|
|
270817
|
+
a8.kind === "record-fetch" || // EXP-011 §45. Three more try/catches.
|
|
270818
|
+
a8.kind === "file-pick" || a8.kind === "file-upload" || a8.kind === "file-sign" || a8.kind === "external-link" && externalLinkIsStatement(a8) || a8.kind === "navigate-path" && navigatePathIsStatement(a8) || // EXP-011 §39. The `if` form ends in `}` and must not take a terminator.
|
|
270819
|
+
a8.kind === "delay" && delayIsBlock(a8) || // EXP-011 §55. The guarded mutator ends in `}` too.
|
|
270820
|
+
mintedGuards(a8) || // EXP-011 §37. The block form ends in `}` and must not take a terminator; the
|
|
270821
|
+
// `Unique Id` form is a run of statements and takes one, exactly as a Now Read does.
|
|
270822
|
+
a8.kind === "id-new" && idNewIsBlock(a8) || // EXP-011 §59. The block form ends in `}` too.
|
|
270823
|
+
a8.kind === "crypto-call" || // EXP-011 §60. The parent write's `if` ends in `}` and takes no terminator.
|
|
270824
|
+
a8.kind === "component-object-set" && a8.parent || // EXP-011 §61. The pop's block form ends in `}`; the push ends in `)` and takes its `;`, as does a Reset.
|
|
270825
|
+
a8.kind === "stack-pop"
|
|
270826
|
+
);
|
|
269100
270827
|
}
|
|
269101
270828
|
function actionsAwait(actions) {
|
|
269102
270829
|
return deepActions(actions).some(
|
|
@@ -269107,7 +270834,7 @@ ${pad(indent)}}`;
|
|
|
269107
270834
|
}
|
|
269108
270835
|
function blockBody(expanded, indent) {
|
|
269109
270836
|
return expanded.map(
|
|
269110
|
-
(a8) => actionTakesNoTerminator(a8) ? `${pad(indent)}${actionCode(a8, indent)}` : `${pad(indent)}${actionCode(a8, a8.kind === "date-now-read" || a8.kind === "id-new" || a8.kind === "crypto-call" || a8.kind === "branch" || a8.kind === "array-new" || a8.kind === "component-object-set" || a8.kind === "stack-push" || a8.kind === "stack-reset" ? indent : 0)};`
|
|
270837
|
+
(a8) => actionTakesNoTerminator(a8) ? `${pad(indent)}${actionCode(a8, indent)}` : `${pad(indent)}${actionCode(a8, "detached" in a8 && a8.detached === true || a8.kind === "date-now-read" || a8.kind === "id-new" || a8.kind === "crypto-call" || a8.kind === "branch" || a8.kind === "array-new" || a8.kind === "component-object-set" || a8.kind === "stack-push" || a8.kind === "stack-reset" || a8.kind === "jsfun-run" ? indent : 0)};`
|
|
269111
270838
|
);
|
|
269112
270839
|
}
|
|
269113
270840
|
function effectBody(actions, indent) {
|
|
@@ -269148,12 +270875,14 @@ ${pad(indent)}}`;
|
|
|
269148
270875
|
externalImports.push(`import { ${coreHooks.sort().join(", ")} } from '@nodegx/core/react';`);
|
|
269149
270876
|
}
|
|
269150
270877
|
const reactImports = [];
|
|
269151
|
-
if (plan.queries.length > 0) reactImports.push("useEffect", "useState");
|
|
270878
|
+
if (plan.queries.length > 0) reactImports.push(...plan.queries.some((q) => queryHasEffect(q)) ? ["useEffect"] : [], "useState");
|
|
269152
270879
|
if (plan.popups.length > 0 && !reactImports.includes("useState")) reactImports.push("useState");
|
|
269153
270880
|
if (referencedStateVars.length > 0 && !reactImports.includes("useState")) reactImports.push("useState");
|
|
269154
270881
|
if ((plan.syncEffects.length > 0 || plan.pushEffects.length > 0 || plan.branchEffects.length > 0 || // EXP-011 §39. A Value Changed is an effect; a Delay owes the unmount cleanup effect.
|
|
269155
|
-
plan.valueChangedEffects.length > 0 || //
|
|
269156
|
-
plan.
|
|
270882
|
+
plan.valueChangedEffects.length > 0 || // P18-S119-TICKEDRUN. A ticked input's run is an effect.
|
|
270883
|
+
plan.tickedRuns.length > 0 || // EXP-011 §43, P18-S113-IDBINDREAD. A Record with Fetch unwired is a bind keyed on its Id.
|
|
270884
|
+
recordBinds.length > 0 || // P18-S111-RECORDLIVE. A live Record's row subscribes to the client's copy of its record.
|
|
270885
|
+
liveRecordRows.length > 0 || // EXP-011 §67. A Variable's authored Value is a mount effect.
|
|
269157
270886
|
plan.variableSeeds.length > 0 || // EXP-011 §71. An Object's own authored property values are a mount effect.
|
|
269158
270887
|
plan.objectSeeds.length > 0 || // EXP-011 §48. A CSS Definition is a mount effect.
|
|
269159
270888
|
plan.styleSheets.length > 0 || // EXP-011 §49. A States' wired State input is an effect keyed on the read.
|
|
@@ -269162,8 +270891,8 @@ ${pad(indent)}}`;
|
|
|
269162
270891
|
reactImports.push("useEffect");
|
|
269163
270892
|
}
|
|
269164
270893
|
if (radioNameLocals.size > 0) reactImports.push("useId");
|
|
269165
|
-
if (delayRefs.size > 0 || plan.valueChangedEffects.length > 0 || usedKeptLocals.size > 0) reactImports.push("useRef");
|
|
269166
|
-
if (plan.task !== void 0 || plan.repeaterItems.length > 0) {
|
|
270894
|
+
if (delayRefs.size > 0 || plan.valueChangedEffects.length > 0 || usedKeptLocals.size > 0 || pulsedRunIds.size > 0 || plan.tickedRuns.length > 0) reactImports.push("useRef");
|
|
270895
|
+
if (plan.task !== void 0 || plan.repeaterItems.length > 0 || plan.fieldMounts.length > 0 || mountChains.length > 0) {
|
|
269167
270896
|
if (!reactImports.includes("useEffect")) reactImports.push("useEffect");
|
|
269168
270897
|
if (!reactImports.includes("useRef")) reactImports.push("useRef");
|
|
269169
270898
|
}
|
|
@@ -269188,7 +270917,7 @@ ${pad(indent)}}`;
|
|
|
269188
270917
|
for (const read2 of plan.recordReads) stubModule(read2.moduleBase).reads.push(read2);
|
|
269189
270918
|
for (const [moduleBase, { queries, mutations, reads }] of stubModules) {
|
|
269190
270919
|
const specifier = `${relRoot}/api/${moduleBase}`;
|
|
269191
|
-
const fetchNames = [.../* @__PURE__ */ new Set([...queries.map((q) => q.fetchName), ...reads.map((r) => r.fnName)])].sort();
|
|
270920
|
+
const fetchNames = [.../* @__PURE__ */ new Set([...queries.map((q) => q.fetchName), ...reads.filter((r) => !r.binds).map((r) => r.fnName)])].sort();
|
|
269192
270921
|
const fnNames = [...new Set(mutations.map((m) => m.fnName))].sort();
|
|
269193
270922
|
const typeNames = [
|
|
269194
270923
|
.../* @__PURE__ */ new Set([
|
|
@@ -269201,6 +270930,11 @@ ${pad(indent)}}`;
|
|
|
269201
270930
|
`import { ${[...fetchNames, ...fnNames, ...typeNames.map((t) => `type ${t}`)].join(", ")} } from '${specifier}';`
|
|
269202
270931
|
);
|
|
269203
270932
|
}
|
|
270933
|
+
const clientNames = [
|
|
270934
|
+
...plan.queries.some((q) => q.wiredClient === true && q.params?.wired !== void 0) ? ["filterValue"] : [],
|
|
270935
|
+
...liveRecordRows.length > 0 || recordBinds.length > 0 ? ["subscribeRecord"] : []
|
|
270936
|
+
];
|
|
270937
|
+
if (clientNames.length > 0) internalImports.set(`${relRoot}/api/client`, `import { ${clientNames.join(", ")} } from '${relRoot}/api/client';`);
|
|
269204
270938
|
if (plan.sessionCalls.length > 0) {
|
|
269205
270939
|
const specifier = `${relRoot}/api/session`;
|
|
269206
270940
|
const fnNames = [...new Set(plan.sessionCalls.map((c) => c.fnName))].sort();
|
|
@@ -269372,6 +271106,11 @@ ${pad(indent)}}`;
|
|
|
269372
271106
|
const specifier = `${relRoot}/${ID_LIB_PATH.replace(/^src\//, "").replace(/\.ts$/, "")}`;
|
|
269373
271107
|
internalImports.set(specifier, `import { ${[...usedIdHelpers].sort().join(", ")} } from '${specifier}';`);
|
|
269374
271108
|
}
|
|
271109
|
+
if (pulsedRunIds.size > 0 || tickedRunIds.size > 0) {
|
|
271110
|
+
const specifier = `${relRoot}/${FUNCTION_RUN_LIB_PATH.replace(/^src\//, "").replace(/\.ts$/, "")}`;
|
|
271111
|
+
const helpers = [...pulsedRunIds.size > 0 ? ["functionNode", "runFunction"] : [], ...tickedRunIds.size > 0 ? ["tickedArrival"] : []];
|
|
271112
|
+
internalImports.set(specifier, `import { ${helpers.join(", ")} } from '${specifier}';`);
|
|
271113
|
+
}
|
|
269375
271114
|
if (usedVariableNames.size > 0) {
|
|
269376
271115
|
const specifier = `${relRoot}/stores/variables`;
|
|
269377
271116
|
const names2 = [...usedVariableNames].map((n) => variableByName.get(n).exportName).sort();
|
|
@@ -269669,6 +271408,12 @@ ${pad(indent)}}`;
|
|
|
269669
271408
|
const roleEvents = ROLE_EVENT_ATTRS[plan.roleOf[node.id]] ?? {};
|
|
269670
271409
|
for (const [port5, actions] of Object.entries(plan.handlers[node.id] ?? {})) {
|
|
269671
271410
|
if (port5 === "onChange" && controlVarByNode.has(node.id)) continue;
|
|
271411
|
+
if (port5 === "didMount" && mountHandled.has(node.id)) continue;
|
|
271412
|
+
if (port5 === "didMount" && mountRefusals.has(node.id)) {
|
|
271413
|
+
notes.push(`${plan.path}: signal ${node.id}.didMount dropped: ${mountRefusals.get(node.id)} \u2014 reported`);
|
|
271414
|
+
defer(node.id, 'the "didMount" signal', mountRefusals.get(node.id));
|
|
271415
|
+
continue;
|
|
271416
|
+
}
|
|
269672
271417
|
const eventAttr = roleEvents[port5] ?? EVENT_ATTRS[port5];
|
|
269673
271418
|
if (!eventAttr) {
|
|
269674
271419
|
notes.push(`${plan.path}: signal ${node.id}.${port5} has no DOM event equivalent \u2014 dropped, reported`);
|
|
@@ -270276,7 +272021,8 @@ ${pad(indent)}}`;
|
|
|
270276
272021
|
const specifier = `../kits/${binding.modulePath.split("/").pop()}`;
|
|
270277
272022
|
const existing = internalImports.get(specifier);
|
|
270278
272023
|
const symbols = new Set(existing ? existing.replace(/^import \{ | \} from .*$/g, "").split(", ") : []);
|
|
270279
|
-
|
|
272024
|
+
const kitLocal = binding.symbol === plan.file.symbol ? `${binding.symbol}Kit` : binding.symbol;
|
|
272025
|
+
symbols.add(kitLocal === binding.symbol ? binding.symbol : `${binding.symbol} as ${kitLocal}`);
|
|
270280
272026
|
internalImports.set(specifier, `import { ${[...symbols].sort().join(", ")} } from '${specifier}';`);
|
|
270281
272027
|
const attrs = [];
|
|
270282
272028
|
for (const param of node.parameters) {
|
|
@@ -270341,7 +272087,7 @@ ${pad(indent)}}`;
|
|
|
270341
272087
|
);
|
|
270342
272088
|
}
|
|
270343
272089
|
const blocks = renderChildBlocks(node.id, childIds, indent + 2).flat();
|
|
270344
|
-
return element(
|
|
272090
|
+
return element(kitLocal, attrs, blocks.length > 0 ? blocks : null, indent, blocks.length > 0);
|
|
270345
272091
|
};
|
|
270346
272092
|
const instanceAttrs = (node) => {
|
|
270347
272093
|
const attrs = [];
|
|
@@ -270474,9 +272220,11 @@ ${pad(indent)}}`;
|
|
|
270474
272220
|
const outputsType = outputFields.length > 0 ? `{ ${outputFields.join("; ")} }` : "Record<string, never>";
|
|
270475
272221
|
const seeds = def.signals.map((s) => `${fieldKey(s)}: () => {}`);
|
|
270476
272222
|
lines.push(`// From the Function node "${def.fnName}" \u2014 the body is preserved verbatim (EXP-003 \xA74).`);
|
|
270477
|
-
|
|
272223
|
+
const seedRecord = `{${seeds.length > 0 ? ` ${seeds.join(", ")} ` : ""}}`;
|
|
272224
|
+
const outputsParam = def.pulsed ? `, Outputs: ${outputsType} = ${seedRecord}` : "";
|
|
272225
|
+
lines.push(`function ${def.fnName}(${hasInputs ? INPUTS_PARAM : "Inputs"}: ${inputsType}${outputsParam}): ${outputsType} {`);
|
|
270478
272226
|
if (hasInputs) lines.push(` const Inputs = ${scopeCast(INPUTS_PARAM)};`);
|
|
270479
|
-
lines.push(` const Outputs: ${outputsType} =
|
|
272227
|
+
if (!def.pulsed) lines.push(` const Outputs: ${outputsType} = ${seedRecord};`);
|
|
270480
272228
|
lines.push(" try {");
|
|
270481
272229
|
if (def.scripts !== void 0 && def.scripts.length > 0) {
|
|
270482
272230
|
const rows = def.scripts.map((u) => `${fieldKey(u.alias)}: ${PROJECT_SCRIPTS_SYMBOL}.load(${JSON.stringify(u.path)})`);
|
|
@@ -270548,12 +272296,39 @@ ${pad(indent)}}`;
|
|
|
270548
272296
|
for (const alias of def.mathAliases) {
|
|
270549
272297
|
lines.push(alias === "pi" ? " const pi = Math.PI;" : ` const ${alias} = Math.${alias};`);
|
|
270550
272298
|
}
|
|
270551
|
-
|
|
270552
|
-
|
|
270553
|
-
|
|
270554
|
-
|
|
270555
|
-
|
|
270556
|
-
|
|
272299
|
+
const gate = def.loadGate;
|
|
272300
|
+
const unsetTest = gate !== void 0 ? gate.unset.map((n) => `${n} === undefined`).join(" && ") : "";
|
|
272301
|
+
if (gate?.atLoad === "wait") {
|
|
272302
|
+
lines.push(` // Evaluate At Load is unticked: no answer until an input arrives.`);
|
|
272303
|
+
lines.push(` if (${unsetTest}) return null;`);
|
|
272304
|
+
}
|
|
272305
|
+
if (gate?.atLoad === "evaluate" && gate.unset.length === 0) {
|
|
272306
|
+
lines.push(" // Nothing feeds its inputs: the load evaluation over them, which keeps null for a throw or a NaN.");
|
|
272307
|
+
lines.push(" try {");
|
|
272308
|
+
lines.push(` const __value = (${def.body});`);
|
|
272309
|
+
lines.push(" return Number.isNaN(__value) ? null : __value;");
|
|
272310
|
+
lines.push(" } catch {");
|
|
272311
|
+
lines.push(" return null;");
|
|
272312
|
+
lines.push(" }");
|
|
272313
|
+
} else if (gate?.atLoad === "evaluate") {
|
|
272314
|
+
lines.push(" // Until an input arrives this is the load evaluation, which keeps null for a throw or a NaN.");
|
|
272315
|
+
lines.push(` const __unset = ${unsetTest};`);
|
|
272316
|
+
lines.push(" try {");
|
|
272317
|
+
lines.push(` const __value = (${def.body});`);
|
|
272318
|
+
lines.push(" return __unset && Number.isNaN(__value) ? null : __value;");
|
|
272319
|
+
lines.push(" } catch (e) {");
|
|
272320
|
+
lines.push(" if (__unset) return null;");
|
|
272321
|
+
lines.push(` console.error('Expression node ${def.fnName} threw:', e);`);
|
|
272322
|
+
lines.push(" return 0;");
|
|
272323
|
+
lines.push(" }");
|
|
272324
|
+
} else {
|
|
272325
|
+
lines.push(" try {");
|
|
272326
|
+
lines.push(` return (${def.body});`);
|
|
272327
|
+
lines.push(" } catch (e) {");
|
|
272328
|
+
lines.push(` console.error('Expression node ${def.fnName} threw:', e);`);
|
|
272329
|
+
lines.push(" return 0;");
|
|
272330
|
+
lines.push(" }");
|
|
272331
|
+
}
|
|
270557
272332
|
lines.push("}");
|
|
270558
272333
|
}
|
|
270559
272334
|
return lines;
|
|
@@ -270629,9 +272404,11 @@ ${pad(indent)}}`;
|
|
|
270629
272404
|
internalImports.set(specifier, `import { ${[...usedMediaHelpers].sort().join(", ")} } from '${specifier}';`);
|
|
270630
272405
|
}
|
|
270631
272406
|
const usesProjectScripts = Object.values(jsFunByNode).some((def) => referencedJsIds.has(def.nodeId) && (def.scripts?.length ?? 0) > 0);
|
|
270632
|
-
|
|
270633
|
-
|
|
270634
|
-
|
|
272407
|
+
const functionFiles = Object.values(jsFunByNode).filter((def) => def.kind === "function" && referencedJsIds.has(def.nodeId)).map((def) => ({ def, file: `src/functions/${plan.file.dir}/${plan.file.fileBase}/${def.fnName}.ts` }));
|
|
272408
|
+
const ownFileIds = new Set(functionFiles.map(({ def }) => def.nodeId));
|
|
272409
|
+
for (const { def, file } of functionFiles) {
|
|
272410
|
+
const from = `${relRoot}/${file.replace(/^src\//, "").replace(/\.ts$/, "")}`;
|
|
272411
|
+
internalImports.set(from, `import { ${def.fnName} } from '${from}';`);
|
|
270635
272412
|
}
|
|
270636
272413
|
const importLines = [];
|
|
270637
272414
|
if (externalImports.length > 0) importLines.push(...externalImports.sort());
|
|
@@ -270642,6 +272419,7 @@ ${pad(indent)}}`;
|
|
|
270642
272419
|
}
|
|
270643
272420
|
const body = [];
|
|
270644
272421
|
for (const def of Object.values(jsFunByNode)) {
|
|
272422
|
+
if (ownFileIds.has(def.nodeId)) continue;
|
|
270645
272423
|
if (referencedJsIds.has(def.nodeId)) body.push(...jsWrapperLines(def), "");
|
|
270646
272424
|
else body.push(...refusedSourceLines(def), "");
|
|
270647
272425
|
}
|
|
@@ -270712,7 +272490,7 @@ ${pad(indent)}}`;
|
|
|
270712
272490
|
body.push(` ${ident}?: ${prop.tsType};`);
|
|
270713
272491
|
}
|
|
270714
272492
|
for (const output of plan.outputProps) body.push(` ${output.prop}?: () => void;`);
|
|
270715
|
-
for (const lifted6 of plan.liftedOutputProps) body.push(` ${lifted6.prop}?: (value: ${lifted6.tsType}) => void;`);
|
|
272493
|
+
for (const lifted6 of plan.liftedOutputProps) body.push(` ${lifted6.prop}?: (value: ${lifted6.tsType} | undefined) => void;`);
|
|
270716
272494
|
if (plan.closesPopup) body.push(" onClose?: (action?: string) => void;");
|
|
270717
272495
|
if (plan.popsStack) body.push(` /** The Component Stack that pushed this component \u2014 its Pop Component Stack's way back. */`, ` ${PAGE_STACK_ENTRY_PROP}?: PageStackEntryHandle;`);
|
|
270718
272496
|
if (plan.childSlot !== void 0) body.push(" children?: ReactNode;");
|
|
@@ -270773,12 +272551,46 @@ ${pad(indent)}}`;
|
|
|
270773
272551
|
}
|
|
270774
272552
|
for (const [, ref] of delayRefs) body.push(` const ${ref} = useRef<DelayHandle | null>(null);`);
|
|
270775
272553
|
for (const effect of plan.valueChangedEffects) body.push(` const ${effect.lastLocal} = useRef<unknown>(undefined);`);
|
|
270776
|
-
for (const
|
|
272554
|
+
for (const id of pulsedRunIds) {
|
|
272555
|
+
const def = jsFunByNode[id];
|
|
272556
|
+
body.push(` const ${pulseNamesOf(def).node} = useRef(functionNode<NonNullable<Parameters<typeof ${def.fnName}>[1]>>([${def.signals.map(tsLiteral).join(", ")}])).current;`);
|
|
272557
|
+
}
|
|
272558
|
+
for (const ticked3 of plan.tickedRuns) body.push(` const ${pulseNamesOf(jsFunByNode[ticked3.nodeId]).held} = useRef<unknown[]>([]).current;`);
|
|
272559
|
+
const hookLocalNames = [
|
|
272560
|
+
...plan.animations.map((a8) => a8.local),
|
|
272561
|
+
...plan.drags.map((d) => d.local),
|
|
272562
|
+
...plan.statesMachines.map((m) => m.local),
|
|
272563
|
+
...plan.scripts.map((s) => s.local),
|
|
272564
|
+
...plan.runTasks.map((r) => r.local),
|
|
272565
|
+
...plan.pageStacks.map((s) => s.local),
|
|
272566
|
+
...screenHooks.map((s) => s.local),
|
|
272567
|
+
...plan.appErrors.map((b) => b.local),
|
|
272568
|
+
...plan.streams.map((s) => s.local)
|
|
272569
|
+
];
|
|
272570
|
+
const codeReads = (code, name) => new RegExp(`(^|[^A-Za-z0-9_$.])${name.replace(/\$/g, "\\$")}(?![A-Za-z0-9_$])`).test(code);
|
|
272571
|
+
const renderLocalLines = [...jsLocals].map(([id, local]) => {
|
|
270777
272572
|
const def = jsFunByNode[id];
|
|
270778
|
-
|
|
272573
|
+
return { local, line: ` const ${local} = ${def.fnName}(${jsArgsObject(def, "render")});` };
|
|
272574
|
+
});
|
|
272575
|
+
const afterHooks = /* @__PURE__ */ new Set();
|
|
272576
|
+
for (let grew = true; grew; ) {
|
|
272577
|
+
grew = false;
|
|
272578
|
+
for (const { local, line } of renderLocalLines) {
|
|
272579
|
+
if (afterHooks.has(local)) continue;
|
|
272580
|
+
const rhs = line.slice(line.indexOf("="));
|
|
272581
|
+
if (hookLocalNames.some((name) => codeReads(rhs, name)) || [...afterHooks].some((name) => codeReads(rhs, name))) {
|
|
272582
|
+
afterHooks.add(local);
|
|
272583
|
+
grew = true;
|
|
272584
|
+
}
|
|
272585
|
+
}
|
|
270779
272586
|
}
|
|
272587
|
+
for (const { local, line } of renderLocalLines) if (!afterHooks.has(local)) body.push(line);
|
|
270780
272588
|
for (const animation of plan.animations) {
|
|
270781
|
-
const
|
|
272589
|
+
const optionCode = (input, expr) => {
|
|
272590
|
+
const code = exprCode(expr, "render");
|
|
272591
|
+
return animation.castToNumber?.includes(input) ? `${SIMPLE_REF.test(code) ? code : `(${code})`} as number` : code;
|
|
272592
|
+
};
|
|
272593
|
+
const options = `{ duration: ${optionCode("duration", animation.duration)}, delay: ${optionCode("delay", animation.delay)}, ease: ${tsLiteral(animation.ease)} }`;
|
|
270782
272594
|
const arrive2 = animation.arrive !== void 0 ? `, ${handlerArrow(animation.arrive, "()", 2)}` : "";
|
|
270783
272595
|
body.push(` // ${animation.comment}`, ` const ${animation.local} = useAnimatedValue(${exprCode(animation.target, "render")}, ${options}${arrive2});`);
|
|
270784
272596
|
}
|
|
@@ -270850,6 +272662,7 @@ ${pad(indent)}}`;
|
|
|
270850
272662
|
if (listenerLines.length === 0) body.push(` const ${stream.local} = ${spec.hook}(${source}, ${options});`);
|
|
270851
272663
|
else body.push(` const ${stream.local} = ${spec.hook}(${source}, ${options}, {`, listenerLines.join(",\n"), " });");
|
|
270852
272664
|
}
|
|
272665
|
+
for (const { local, line } of renderLocalLines) if (afterHooks.has(local)) body.push(line);
|
|
270853
272666
|
if (plan.task !== void 0) {
|
|
270854
272667
|
const started = dedupeLocal("started");
|
|
270855
272668
|
body.push(
|
|
@@ -270882,7 +272695,39 @@ ${pad(indent)}}`;
|
|
|
270882
272695
|
body.push("");
|
|
270883
272696
|
}
|
|
270884
272697
|
for (const query2 of plan.queries) {
|
|
270885
|
-
|
|
272698
|
+
const wired = query2.params?.wired;
|
|
272699
|
+
const label2 = nodeById.get(query2.nodeId)?.authoredLabel;
|
|
272700
|
+
const fetched = query2.fetchWired;
|
|
272701
|
+
if (!queryHasEffect(query2)) {
|
|
272702
|
+
body.push(` // ${label2 ? `"${label2}"` : "The query"} asks only when its Fetch is sent (dbcollectionnode2.ts storageFetch); its boxes are unticked.`, "");
|
|
272703
|
+
continue;
|
|
272704
|
+
}
|
|
272705
|
+
const once = fetched !== void 0 && !fetched.onValues;
|
|
272706
|
+
if (query2.params !== void 0 && wired !== void 0 && query2.wiredValues !== void 0) {
|
|
272707
|
+
const values = query2.wiredValues;
|
|
272708
|
+
const codes = values.map((v) => exprCode(v.expr, "render"));
|
|
272709
|
+
const locals = codes.map((code, i) => {
|
|
272710
|
+
if (/^[A-Za-z_$][\w$]*$/.test(code)) return code;
|
|
272711
|
+
let local = `${propIdentifier(values[i].name)}Value`;
|
|
272712
|
+
while (codes.some((c) => new RegExp(`\\b${local.replace(/\$/g, "\\$")}\\b`).test(c))) local = `${local}_`;
|
|
272713
|
+
return local;
|
|
272714
|
+
});
|
|
272715
|
+
const localOf = (name) => locals[values.findIndex((v) => v.name === name)];
|
|
272716
|
+
const valueCode = (name, date3) => query2.wiredClient ? `filterValue(${localOf(name)}${date3 ? ", true" : ""})` : localOf(name);
|
|
272717
|
+
const deps = [...new Set(values.flatMap((v) => effectDeps(v.expr)))].join(", ");
|
|
272718
|
+
body.push(
|
|
272719
|
+
once ? ` // ${label2 ? `"${label2}"` : "The query"} asks once as it is made (its Class or Query settings box is ticked); a new ${values.map((v) => `"${v.name}"`).join(" or ")} waits for its Fetch.` : ` // ${label2 ? `"${label2}"` : "The query"} asks again whenever ${values.map((v) => `"${v.name}"`).join(" or ")} changes (dbcollectionnode2.ts setQueryParameter).`,
|
|
272720
|
+
" useEffect(() => {",
|
|
272721
|
+
...codes.flatMap((code, i) => code === locals[i] ? [] : [` const ${locals[i]} = ${code};`]),
|
|
272722
|
+
` const where = ${wiredWhereExpr(wired, locals, valueCode)};`,
|
|
272723
|
+
` ${query2.fetchName}(${queryParamsLiteral(query2.params, "where")}).then(${query2.setterName});`,
|
|
272724
|
+
...once ? [" // eslint-disable-next-line react-hooks/exhaustive-deps -- once, as the node is made", " }, []);"] : [` }, [${deps}]);`],
|
|
272725
|
+
""
|
|
272726
|
+
);
|
|
272727
|
+
continue;
|
|
272728
|
+
}
|
|
272729
|
+
const params = query2.params === void 0 ? "" : queryParamsLiteral(query2.params);
|
|
272730
|
+
body.push(" useEffect(() => {", ` ${query2.fetchName}(${params}).then(${query2.setterName});`, " }, []);", "");
|
|
270886
272731
|
}
|
|
270887
272732
|
for (const seed of plan.variableSeeds) {
|
|
270888
272733
|
body.push(` // ${seed.comment}`, " useEffect(() => {", ...effectBody([variableSeedAction(seed)], 4), " }, []);", "");
|
|
@@ -270963,11 +272808,43 @@ ${pad(indent)}}`;
|
|
|
270963
272808
|
);
|
|
270964
272809
|
}
|
|
270965
272810
|
}
|
|
272811
|
+
for (const mount2 of plan.fieldMounts) {
|
|
272812
|
+
const chain = plan.changeHandlers[mount2.inputId] ?? [];
|
|
272813
|
+
if (chain.length === 0) continue;
|
|
272814
|
+
const once = dedupeLocal("announced");
|
|
272815
|
+
const lines = [];
|
|
272816
|
+
let code = tsLiteral(mount2.start);
|
|
272817
|
+
if (mount2.source !== void 0) {
|
|
272818
|
+
const arrival = dedupeLocal("arrival");
|
|
272819
|
+
code = dedupeLocal("mountValue");
|
|
272820
|
+
lines.push(
|
|
272821
|
+
` const ${arrival} = ${exprCode(mount2.source, "render")};`,
|
|
272822
|
+
` const ${code} = ${arrival} === undefined ? ${tsLiteral(mount2.start)} : ${arrival} === null ? '' : String(${arrival});`
|
|
272823
|
+
);
|
|
272824
|
+
}
|
|
272825
|
+
announcedCode = { inputId: mount2.inputId, code };
|
|
272826
|
+
const chainLines = effectBody(chain, 4);
|
|
272827
|
+
announcedCode = void 0;
|
|
272828
|
+
body.push(
|
|
272829
|
+
` // ${mount2.comment}`,
|
|
272830
|
+
` const ${once} = useRef(false);`,
|
|
272831
|
+
" useEffect(() => {",
|
|
272832
|
+
` if (${once}.current) return;`,
|
|
272833
|
+
` ${once}.current = true;`,
|
|
272834
|
+
...lines,
|
|
272835
|
+
...chainLines,
|
|
272836
|
+
" // eslint-disable-next-line react-hooks/exhaustive-deps -- once, as the field mounts",
|
|
272837
|
+
" }, []);",
|
|
272838
|
+
""
|
|
272839
|
+
);
|
|
272840
|
+
}
|
|
270966
272841
|
for (const push of plan.pushEffects) {
|
|
270967
272842
|
const deps = [...effectDeps(push.expr), push.prop].join(", ");
|
|
272843
|
+
const code = exprCode(push.expr, "render");
|
|
272844
|
+
const pushed = push.castTo === void 0 ? code : `${SIMPLE_REF.test(code) ? code : `(${code})`} as ${push.castTo} | undefined`;
|
|
270968
272845
|
body.push(
|
|
270969
272846
|
" useEffect(() => {",
|
|
270970
|
-
` ${push.prop}?.(${
|
|
272847
|
+
` ${push.prop}?.(${pushed});`,
|
|
270971
272848
|
` }, [${deps}]);`,
|
|
270972
272849
|
""
|
|
270973
272850
|
);
|
|
@@ -270977,10 +272854,27 @@ ${pad(indent)}}`;
|
|
|
270977
272854
|
const deps = effectDeps(action.cond).join(", ");
|
|
270978
272855
|
body.push(` // ${effect.comment}`, " useEffect(() => {", ...effectBody([action], 4), ` }, [${deps}]);`, "");
|
|
270979
272856
|
}
|
|
270980
|
-
for (const effect of
|
|
270981
|
-
const action = effect.action;
|
|
272857
|
+
for (const { effect, action, read: read2, row } of recordBinds) {
|
|
270982
272858
|
const deps = effectDeps(action.id).join(", ");
|
|
270983
|
-
|
|
272859
|
+
const idCode = action.coerceId === "string" ? `String(${exprCode(action.id, "render")} ?? '')` : exprCode(action.id, "render");
|
|
272860
|
+
const bind2 = `subscribeRecord<${read2.typeName}>(${read2.idLocal}, ${stateSetterOf(row.name)})`;
|
|
272861
|
+
body.push(
|
|
272862
|
+
` // ${effect.comment}`,
|
|
272863
|
+
" useEffect(() => {",
|
|
272864
|
+
` const ${read2.idLocal} = ${idCode};`,
|
|
272865
|
+
...action.id.kind === "literal" ? [] : [` if (${read2.idLocal} === undefined || ${read2.idLocal} === null || ${read2.idLocal} === '') return undefined;`],
|
|
272866
|
+
...effect.follows ? [` return ${bind2};`] : [' // "Record properties" is unticked: the copy as it is at the bind, and no later write.', ` ${bind2}();`, " return undefined;"],
|
|
272867
|
+
` }, [${deps}]);`,
|
|
272868
|
+
""
|
|
272869
|
+
);
|
|
272870
|
+
}
|
|
272871
|
+
for (const { read: read2, row } of liveRecordRows) {
|
|
272872
|
+
const label2 = nodeById.get(read2.nodeId)?.authoredLabel;
|
|
272873
|
+
body.push(
|
|
272874
|
+
` // ${label2 ? `"${label2}"` : "The Record"} shows the app's one copy of its record, so a write anywhere in the app reaches it (dbmodelnode2.ts, "Record properties" ticked).`,
|
|
272875
|
+
` useEffect(() => (${row.name}?.id === undefined ? undefined : subscribeRecord<${read2.typeName}>(${row.name}.id, ${stateSetterOf(row.name)})), [${row.name}?.id]);`,
|
|
272876
|
+
""
|
|
272877
|
+
);
|
|
270984
272878
|
}
|
|
270985
272879
|
for (const [, ref] of delayRefs) {
|
|
270986
272880
|
body.push(" useEffect(() => () => {", ` stopDelay(${ref});`, " }, []);", "");
|
|
@@ -271023,6 +272917,32 @@ ${pad(indent)}}`;
|
|
|
271023
272917
|
""
|
|
271024
272918
|
);
|
|
271025
272919
|
}
|
|
272920
|
+
for (const ticked3 of plan.tickedRuns) {
|
|
272921
|
+
const deps = [...new Set(ticked3.watch.flatMap(effectDeps))].join(", ");
|
|
272922
|
+
body.push(
|
|
272923
|
+
` // ${ticked3.comment}`,
|
|
272924
|
+
" useEffect(() => {",
|
|
272925
|
+
` if (!tickedArrival(${tickedRecordArgs(ticked3.nodeId)})) return;`,
|
|
272926
|
+
...effectBody([ticked3.action], 4),
|
|
272927
|
+
` }, [${deps}]);`,
|
|
272928
|
+
""
|
|
272929
|
+
);
|
|
272930
|
+
}
|
|
272931
|
+
for (const mount2 of mountChains) {
|
|
272932
|
+
const node = nodeById.get(mount2.nodeId);
|
|
272933
|
+
const mounted = dedupeLocal("mounted");
|
|
272934
|
+
body.push(
|
|
272935
|
+
` // ${node.authoredLabel ?? node.type} \u2014 Did Mount: once, when it mounts, as the running app sends it.`,
|
|
272936
|
+
` const ${mounted} = useRef(false);`,
|
|
272937
|
+
" useEffect(() => {",
|
|
272938
|
+
` if (${mounted}.current) return;`,
|
|
272939
|
+
` ${mounted}.current = true;`,
|
|
272940
|
+
...effectBody(mount2.actions, 4),
|
|
272941
|
+
" // eslint-disable-next-line react-hooks/exhaustive-deps -- once, like the element's own Did Mount",
|
|
272942
|
+
" }, []);",
|
|
272943
|
+
""
|
|
272944
|
+
);
|
|
272945
|
+
}
|
|
271026
272946
|
const actionExprsOf = (a8) => {
|
|
271027
272947
|
switch (a8.kind) {
|
|
271028
272948
|
case "emit":
|
|
@@ -271060,11 +272980,16 @@ ${pad(indent)}}`;
|
|
|
271060
272980
|
return [a8.items];
|
|
271061
272981
|
case "runtasks-abort":
|
|
271062
272982
|
return [];
|
|
272983
|
+
case "query-fetch":
|
|
272984
|
+
return a8.values.map((v) => v.expr);
|
|
271063
272985
|
// EXP-011 §58. The data; the config and listeners are walked off the plan.
|
|
271064
272986
|
case "stream-action":
|
|
271065
272987
|
return a8.value !== void 0 ? [a8.value] : [];
|
|
271066
272988
|
case "jsfun-run":
|
|
271067
272989
|
return [...jsArgExprs(a8.nodeId), ...a8.then.flatMap(actionExprsOf)];
|
|
272990
|
+
case "jsfun-pulses":
|
|
272991
|
+
case "jsfun-pulse":
|
|
272992
|
+
return a8.then.flatMap(actionExprsOf);
|
|
271068
272993
|
case "http-call":
|
|
271069
272994
|
// EXP-011 §41. The same, one node over.
|
|
271070
272995
|
case "cloud-call":
|
|
@@ -271088,7 +273013,8 @@ ${pad(indent)}}`;
|
|
|
271088
273013
|
case "api-call":
|
|
271089
273014
|
return [
|
|
271090
273015
|
...a8.args.flatMap((arg) => arg.kind === "expr" ? [arg.expr] : arg.props.map((p) => p.expr)),
|
|
271091
|
-
...a8.then.flatMap(actionExprsOf)
|
|
273016
|
+
...a8.then.flatMap(actionExprsOf),
|
|
273017
|
+
...a8.failThen.flatMap(actionExprsOf)
|
|
271092
273018
|
];
|
|
271093
273019
|
// EXP-011 Tier 1.3. The Read reads nothing of its own; the chain is where the expressions
|
|
271094
273020
|
// are, and a reader of this function wants them (the `usesPayload` test below is one).
|
|
@@ -271188,12 +273114,14 @@ ${pad(indent)}}`;
|
|
|
271188
273114
|
for (const action of expandActions(receiver.actions)) body.push(` ${actionCode(action)};`);
|
|
271189
273115
|
body.push(" });", "");
|
|
271190
273116
|
}
|
|
271191
|
-
const
|
|
273117
|
+
const removedRoot = jsxLines.length === 1 ? /^\s*\{\/\* (.*) \*\/\}$/.exec(jsxLines[0]) : null;
|
|
273118
|
+
const providedJsx = printsRecord && removedRoot === null && !(jsxLines.length === 1 && jsxLines[0] === " null") ? [
|
|
271192
273119
|
` <ParentComponentObjectContext.Provider value={${plan.componentObject.local}}>`,
|
|
271193
273120
|
...jsxLines.map((line) => line.trim().length === 0 ? line : ` ${line}`),
|
|
271194
273121
|
" </ParentComponentObjectContext.Provider>"
|
|
271195
273122
|
] : jsxLines;
|
|
271196
273123
|
if (jsxLines.length === 1 && jsxLines[0] === " null") body.push(...preReturnComment, " return null;", "}");
|
|
273124
|
+
else if (removedRoot !== null) body.push(...preReturnComment, ` // ${removedRoot[1]}`, " return null;", "}");
|
|
271197
273125
|
else body.push(...preReturnComment, " return (", ...providedJsx, " );", "}");
|
|
271198
273126
|
if (usesRowField) {
|
|
271199
273127
|
body.push(
|
|
@@ -271263,6 +273191,7 @@ ${printDecls(query2.decls, " ")}
|
|
|
271263
273191
|
files[`${baseDir}/${plan.file.fileBase}.module.css`] = GENERATED_CSS2 + "\n" + cssBlocks.join("\n\n") + "\n";
|
|
271264
273192
|
}
|
|
271265
273193
|
for (const script2 of plan.scripts) files[script2.file] = scriptFileSource(script2, plan.path, plan.legacyPath);
|
|
273194
|
+
for (const { def, file } of functionFiles) files[file] = functionFileSource(def, jsWrapperLines(def), plan.path);
|
|
271266
273195
|
return {
|
|
271267
273196
|
files,
|
|
271268
273197
|
notes,
|
|
@@ -271270,6 +273199,8 @@ ${printDecls(query2.decls, " ")}
|
|
|
271270
273199
|
utilHelpers: usedUtilHelpers,
|
|
271271
273200
|
idHelpers: usedIdHelpers,
|
|
271272
273201
|
timerHelpers: usedTimerHelpers,
|
|
273202
|
+
// EXP-011 §86.
|
|
273203
|
+
functionRunLib: pulsedRunIds.size > 0 || tickedRunIds.size > 0,
|
|
271273
273204
|
// EXP-011 §49. `states.ts` imports `animate.ts`, so a States alone earns both modules.
|
|
271274
273205
|
animateLib: plan.animations.length > 0 || plan.statesMachines.length > 0,
|
|
271275
273206
|
statesLib: plan.statesMachines.length > 0,
|
|
@@ -271366,6 +273297,24 @@ function scriptFileSource(script2, componentPath, legacyPath) {
|
|
|
271366
273297
|
];
|
|
271367
273298
|
return lines.join("\n");
|
|
271368
273299
|
}
|
|
273300
|
+
var GENERATED_FUNCTION_TS = "// @nodegx:generated (function node \u2014 provenance markers complete in EXP-007)";
|
|
273301
|
+
function functionFileSource(def, wrapper, componentPath) {
|
|
273302
|
+
const safe = (text2) => text2.split("*/").join("* /");
|
|
273303
|
+
const signature = `function ${def.fnName}(`;
|
|
273304
|
+
const lines = [
|
|
273305
|
+
"// @ts-nocheck",
|
|
273306
|
+
GENERATED_FUNCTION_TS,
|
|
273307
|
+
"//",
|
|
273308
|
+
`// The Function node "${safe(def.fnName)}" (node ${safe(def.nodeId)}) of ${safe(componentPath)}, in a file of its own.`,
|
|
273309
|
+
"// Type checking is off for this one file, as for a Script node's: the body is the author's JavaScript, not",
|
|
273310
|
+
"// TypeScript. The signature is typed, and every call to it is checked in the component that makes it.",
|
|
273311
|
+
...(def.scripts?.length ?? 0) > 0 ? [`import { ${PROJECT_SCRIPTS_SYMBOL} } from '../../../${PROJECT_SCRIPT_FILES_PATH.replace(/^src\//, "").replace(/\.ts$/, "")}';`] : [],
|
|
273312
|
+
"",
|
|
273313
|
+
...wrapper.map((line) => line.startsWith(signature) ? `export ${line}` : line),
|
|
273314
|
+
""
|
|
273315
|
+
];
|
|
273316
|
+
return lines.join("\n");
|
|
273317
|
+
}
|
|
271369
273318
|
function preOrder(plan) {
|
|
271370
273319
|
if (!plan.rootId) return [];
|
|
271371
273320
|
const out = [];
|
|
@@ -271424,7 +273373,7 @@ function jsxAttr(name, value) {
|
|
|
271424
273373
|
return `${name}={${value}}`;
|
|
271425
273374
|
}
|
|
271426
273375
|
function jsxText(text2) {
|
|
271427
|
-
if (/[{}
|
|
273376
|
+
if (/[{}<>&\r\n]/.test(text2) || /^\s|\s$/.test(text2)) return `{${JSON.stringify(text2)}}`;
|
|
271428
273377
|
return text2;
|
|
271429
273378
|
}
|
|
271430
273379
|
function withFallback(code, fallback) {
|
|
@@ -271506,6 +273455,14 @@ function filledArcPath(x, y, radius, startAngle, endAngle) {
|
|
|
271506
273455
|
svgNumber(start3.y)
|
|
271507
273456
|
].join(" ");
|
|
271508
273457
|
}
|
|
273458
|
+
function runIsPulsed(action) {
|
|
273459
|
+
return action.then.some((a8) => a8.kind === "jsfun-pulses");
|
|
273460
|
+
}
|
|
273461
|
+
function pulseNamesOf(def) {
|
|
273462
|
+
const camel = def.fnName.replace(/_+([A-Za-z0-9])/g, (_m, c) => c.toUpperCase()).replace(/_+$/, "");
|
|
273463
|
+
const base = camel.charAt(0).toLowerCase() + camel.slice(1);
|
|
273464
|
+
return { run: `${base}Run`, node: `${base}Node`, pulse: `${base}Pulse`, held: `${base}Held` };
|
|
273465
|
+
}
|
|
271509
273466
|
|
|
271510
273467
|
// ../nodegx-export/src/emit/dateLib.ts
|
|
271511
273468
|
var DATE_LIB_PATH = "src/lib/date.ts";
|
|
@@ -272067,8 +274024,8 @@ var coverage_ledger_default = {
|
|
|
272067
274024
|
},
|
|
272068
274025
|
{
|
|
272069
274026
|
typeName: "DbCollection2",
|
|
272070
|
-
status: "
|
|
272071
|
-
|
|
274027
|
+
status: "translated",
|
|
274028
|
+
note: "Was `stubbed` with \"no backend code export\", which EXP-009 overtook: the query is a fetch<Type>() in src/api/<class>.ts that calls the EXP-009 client's query() against the project's NodeGX backend, held in useState and run in a useEffect at mount, or in the handler of whatever sends Fetch (P18-S116-QUERYFETCH, the-query-fetch.test.ts). Reclassified 2026-10-17 by reading the api module emitted for tests/fixtures/puppy-test-3 (fetchPuppies() returns query<Puppy>('Puppy')); the internal disposition is still named 'stubbed' (visual.test.ts)."
|
|
272072
274029
|
},
|
|
272073
274030
|
{
|
|
272074
274031
|
typeName: "DbModel",
|
|
@@ -273377,6 +275334,17 @@ function emitApp(ir, catalog2) {
|
|
|
273377
275334
|
notes.push(cloud);
|
|
273378
275335
|
projectNotes.push(cloud);
|
|
273379
275336
|
}
|
|
275337
|
+
for (const store2 of project2.stores) {
|
|
275338
|
+
for (const n of store2.notes) {
|
|
275339
|
+
const line = `${n.componentPath}: node ${n.nodeId} (net.noodl.GlobalStore, store "${store2.name}"): ${n.text}`;
|
|
275340
|
+
notes.push(line);
|
|
275341
|
+
projectNotes.push(line);
|
|
275342
|
+
}
|
|
275343
|
+
}
|
|
275344
|
+
for (const line of routerNotes(ir)) {
|
|
275345
|
+
notes.push(line);
|
|
275346
|
+
projectNotes.push(line);
|
|
275347
|
+
}
|
|
273380
275348
|
const unreachable = /* @__PURE__ */ new Set();
|
|
273381
275349
|
for (const legacy of project2.reachability.unreachable) {
|
|
273382
275350
|
const path43 = legacy.replace(/^\//, "");
|
|
@@ -273389,6 +275357,7 @@ function emitApp(ir, catalog2) {
|
|
|
273389
275357
|
const utilHelpersUsed = /* @__PURE__ */ new Set();
|
|
273390
275358
|
const idHelpersUsed = /* @__PURE__ */ new Set();
|
|
273391
275359
|
const timerHelpersUsed = /* @__PURE__ */ new Set();
|
|
275360
|
+
let functionRunLibUsed = false;
|
|
273392
275361
|
let animateLibUsed = false;
|
|
273393
275362
|
let statesLibUsed = false;
|
|
273394
275363
|
let scriptLibUsed = false;
|
|
@@ -273459,6 +275428,7 @@ function emitApp(ir, catalog2) {
|
|
|
273459
275428
|
for (const helper of emitted.dateHelpers) dateHelpersUsed.add(helper);
|
|
273460
275429
|
for (const helper of emitted.utilHelpers) utilHelpersUsed.add(helper);
|
|
273461
275430
|
for (const helper of emitted.idHelpers) idHelpersUsed.add(helper);
|
|
275431
|
+
if (emitted.functionRunLib) functionRunLibUsed = true;
|
|
273462
275432
|
for (const helper of emitted.timerHelpers) timerHelpersUsed.add(helper);
|
|
273463
275433
|
if (emitted.animateLib) animateLibUsed = true;
|
|
273464
275434
|
if (emitted.statesLib) statesLibUsed = true;
|
|
@@ -273496,6 +275466,9 @@ function emitApp(ir, catalog2) {
|
|
|
273496
275466
|
if (idHelpersUsed.size > 0) {
|
|
273497
275467
|
files[ID_LIB_PATH] = GENERATED_MODULE_TS + idLibSource();
|
|
273498
275468
|
}
|
|
275469
|
+
if (functionRunLibUsed) {
|
|
275470
|
+
files[FUNCTION_RUN_LIB_PATH] = GENERATED_MODULE_TS + functionRunLibSource();
|
|
275471
|
+
}
|
|
273499
275472
|
if (timerHelpersUsed.size > 0) {
|
|
273500
275473
|
files[TIMER_LIB_PATH] = GENERATED_MODULE_TS + timerLibSource();
|
|
273501
275474
|
}
|
|
@@ -273647,7 +275620,7 @@ function apiModules(ir, project2) {
|
|
|
273647
275620
|
if (entry === void 0) {
|
|
273648
275621
|
byCollection.set(
|
|
273649
275622
|
collectionName4,
|
|
273650
|
-
entry = { typeName: typeName3, moduleBase, querySites: [], readSites: [], mutations: /* @__PURE__ */ new Map(), writes: /* @__PURE__ */ new Map(), reads: /* @__PURE__ */ new Map() }
|
|
275623
|
+
entry = { typeName: typeName3, moduleBase, querySites: [], queryParams: false, readSites: [], mutations: /* @__PURE__ */ new Map(), writes: /* @__PURE__ */ new Map(), reads: /* @__PURE__ */ new Map() }
|
|
273651
275624
|
);
|
|
273652
275625
|
}
|
|
273653
275626
|
return entry;
|
|
@@ -273657,11 +275630,14 @@ function apiModules(ir, project2) {
|
|
|
273657
275630
|
const entry = moduleFor(query2.collectionName, query2.typeName, query2.moduleBase);
|
|
273658
275631
|
entry.fetchName = query2.fetchName;
|
|
273659
275632
|
entry.querySites.push({ componentPath: plan.path, nodeId: query2.nodeId });
|
|
275633
|
+
if (query2.params !== void 0) entry.queryParams = true;
|
|
273660
275634
|
}
|
|
273661
275635
|
for (const read2 of plan.recordReads) {
|
|
273662
275636
|
const entry = moduleFor(read2.collectionName, read2.typeName, read2.moduleBase);
|
|
273663
|
-
|
|
273664
|
-
|
|
275637
|
+
if (!read2.binds) {
|
|
275638
|
+
entry.fetchOneName = read2.fnName;
|
|
275639
|
+
entry.readSites.push({ componentPath: plan.path, nodeId: read2.nodeId });
|
|
275640
|
+
}
|
|
273665
275641
|
for (const column of read2.reads) {
|
|
273666
275642
|
if (!entry.reads.has(column.name)) entry.reads.set(column.name, column.tsType);
|
|
273667
275643
|
}
|
|
@@ -273682,6 +275658,8 @@ function apiModules(ir, project2) {
|
|
|
273682
275658
|
const hasSubscriptions = project2.plans.some((plan) => plan.streams.some((stream) => stream.kind === "subscription"));
|
|
273683
275659
|
const hasSignIns = project2.plans.some((plan) => plan.streams.some((stream) => stream.kind === "signin"));
|
|
273684
275660
|
const hasReturnLeg = hasSignIns || project2.plans.some((plan) => plan.sessionCalls.some((call5) => call5.verb === "magic-link"));
|
|
275661
|
+
const liveRecords = project2.plans.some((plan) => plan.recordReads.some((read2) => read2.live || read2.binds));
|
|
275662
|
+
const wiredFilters = project2.plans.some((plan) => plan.queries.some((query2) => query2.params?.wired !== void 0 && query2.wiredClient === true));
|
|
273685
275663
|
const hasApi = byCollection.size > 0 || hasSessionCalls || hasCloudCalls || hasFileOps || hasSubscriptions || hasSignIns;
|
|
273686
275664
|
const notes = [];
|
|
273687
275665
|
const backend = ir.project.cloudservices;
|
|
@@ -273704,7 +275682,7 @@ function apiModules(ir, project2) {
|
|
|
273704
275682
|
let anyFileColumn = false;
|
|
273705
275683
|
for (const [collectionName4, module2] of byCollection) {
|
|
273706
275684
|
const schema = ir.project.collections.find((c) => c.name === collectionName4);
|
|
273707
|
-
const { typeName: typeName3, moduleBase, fetchName, querySites, fetchOneName, readSites, mutations, writes, reads } = module2;
|
|
275685
|
+
const { typeName: typeName3, moduleBase, fetchName, querySites, queryParams, fetchOneName, readSites, mutations, writes, reads } = module2;
|
|
273708
275686
|
const columns = /* @__PURE__ */ new Map();
|
|
273709
275687
|
for (const col of schema?.columns ?? []) columns.set(col.name, tsColumnType(col.type));
|
|
273710
275688
|
for (const [name, tsType] of writes) if (!columns.has(name)) columns.set(name, tsType);
|
|
@@ -273726,16 +275704,20 @@ ${fields.join("\n")}${fields.length > 0 ? "\n" : ""}}
|
|
|
273726
275704
|
${querySites.map((s) => siteLine(s, "DbCollection2")).join("\n")}
|
|
273727
275705
|
* Fetches the \`${collectionName4}\` collection from the project's NodeGX backend
|
|
273728
275706
|
* (src/api/client.ts); the export report lists every call site.
|
|
273729
|
-
|
|
273730
|
-
|
|
275707
|
+
` + (queryParams ? ` * Each site passes its own filter, order and limit, as the running app sends them.
|
|
275708
|
+
` : "") + ` */
|
|
275709
|
+
` + (queryParams ? `export async function ${fetchName}(params?: QueryParams): Promise<${typeName3}[]> {
|
|
275710
|
+
return query<${typeName3}>(${tsStringLiteral(collectionName4)}, params);
|
|
275711
|
+
}
|
|
275712
|
+
` : `export async function ${fetchName}(): Promise<${typeName3}[]> {
|
|
273731
275713
|
return query<${typeName3}>(${tsStringLiteral(collectionName4)});
|
|
273732
275714
|
}
|
|
273733
|
-
` : `/**
|
|
275715
|
+
`) : `/**
|
|
273734
275716
|
${querySites.map((s) => siteLine(s, "DbCollection2")).join("\n")}
|
|
273735
275717
|
* fetched the \`${collectionName4}\` collection from the project's NodeGX backend. Connect this to your
|
|
273736
275718
|
* own data source; the export report lists every call site.
|
|
273737
275719
|
*/
|
|
273738
|
-
export async function ${fetchName}(): Promise<${typeName3}[]> {
|
|
275720
|
+
export async function ${fetchName}(${queryParams ? "_params?: unknown" : ""}): Promise<${typeName3}[]> {
|
|
273739
275721
|
return [];
|
|
273740
275722
|
}
|
|
273741
275723
|
`
|
|
@@ -273824,9 +275806,11 @@ export async function ${fnName}${signature} {
|
|
|
273824
275806
|
verb === "create" ? "create" : verb === "update" ? "update" : verb === "add-relation" ? "addRelation" : verb === "remove-relation" ? "removeRelation" : "remove"
|
|
273825
275807
|
);
|
|
273826
275808
|
}
|
|
273827
|
-
const
|
|
275809
|
+
const clientNames = [...[...clientImports].sort(), ...fetchName !== void 0 && queryParams ? ["type QueryParams"] : []];
|
|
275810
|
+
const importLine = clientNames.length === 0 ? "" : `import { ${clientNames.join(", ")} } from './client';
|
|
273828
275811
|
`;
|
|
273829
|
-
|
|
275812
|
+
const imports = importLine + filesImport;
|
|
275813
|
+
stubs.push([`src/api/${moduleBase}.ts`, GENERATED_MODULE_TS + (imports === "" ? "" : imports + "\n") + parts.join("\n")]);
|
|
273830
275814
|
} else {
|
|
273831
275815
|
stubs.push([`src/api/${moduleBase}.ts`, GENERATED_TS4 + (filesImport === "" ? "" : filesImport + "\n") + parts.join("\n")]);
|
|
273832
275816
|
}
|
|
@@ -273843,7 +275827,7 @@ export async function ${fnName}${signature} {
|
|
|
273843
275827
|
const files = filesModule(project2, backend, siteLine, anyFileColumn);
|
|
273844
275828
|
if (files !== null) stubs.push(files);
|
|
273845
275829
|
if (backend !== void 0 && hasApi) {
|
|
273846
|
-
stubs.push(["src/api/client.ts", clientModule(backend, hasReturnLeg)]);
|
|
275830
|
+
stubs.push(["src/api/client.ts", clientModule(backend, hasReturnLeg, liveRecords, wiredFilters)]);
|
|
273847
275831
|
stubs.push([".env.example", envExample(backend)]);
|
|
273848
275832
|
}
|
|
273849
275833
|
return { files: stubs, notes, usesBackend: hasApi };
|
|
@@ -274449,7 +276433,87 @@ function envLine(name, value) {
|
|
|
274449
276433
|
return `${name}=${value.replace(/[\r\n]/g, "")}
|
|
274450
276434
|
`;
|
|
274451
276435
|
}
|
|
274452
|
-
function
|
|
276436
|
+
var PLAIN_UPDATE_SOURCE = `export async function update<T extends { id: string }>(collection: string, id: string, data: Partial<T>): Promise<T> {
|
|
276437
|
+
// The wire answers a PUT with the changed fields only (\`updatedAt\`), so the
|
|
276438
|
+
// caller's own data is the record the graph continues with.
|
|
276439
|
+
const response = await request<Record<string, unknown>>(\`/classes/\${collection}/\${encodeURIComponent(id)}\`, {
|
|
276440
|
+
method: 'PUT',
|
|
276441
|
+
body: data
|
|
276442
|
+
});
|
|
276443
|
+
const { objectId: _objectId, ...fields } = response;
|
|
276444
|
+
return { ...data, ...fields, id } as unknown as T;
|
|
276445
|
+
}
|
|
276446
|
+
`;
|
|
276447
|
+
var HELD_RECORDS_SOURCE = `
|
|
276448
|
+
/**
|
|
276449
|
+
* The app's one copy of each record, keyed by its id alone as the running app keys them
|
|
276450
|
+
* (\`Model.get(id)\`). Every read merges into it field by field, as the running app's
|
|
276451
|
+
* \`_fromJSON\` does, and nothing removes a field. A Record shows this copy, so a write
|
|
276452
|
+
* anywhere in the app reaches it at once.
|
|
276453
|
+
*/
|
|
276454
|
+
type HeldRecord = { id: string } & Record<string, unknown>;
|
|
276455
|
+
const heldRecords = new Map<string, HeldRecord>();
|
|
276456
|
+
const recordListeners = new Map<string, Set<(row: HeldRecord) => void>>();
|
|
276457
|
+
|
|
276458
|
+
function holdRecord<T extends { id: string }>(fields: T): T {
|
|
276459
|
+
const row = { ...heldRecords.get(fields.id), ...fields } as HeldRecord;
|
|
276460
|
+
heldRecords.set(fields.id, row);
|
|
276461
|
+
recordListeners.get(fields.id)?.forEach((listener) => listener(row));
|
|
276462
|
+
return row as unknown as T;
|
|
276463
|
+
}
|
|
276464
|
+
|
|
276465
|
+
/** Hands \`listener\` the record's copy now, and again after every write to it, until the returned function is called. */
|
|
276466
|
+
export function subscribeRecord<T extends { id: string }>(id: string, listener: (row: T) => void): () => void {
|
|
276467
|
+
const call = listener as unknown as (row: HeldRecord) => void;
|
|
276468
|
+
const listeners = recordListeners.get(id) ?? new Set<(row: HeldRecord) => void>();
|
|
276469
|
+
recordListeners.set(id, listeners);
|
|
276470
|
+
listeners.add(call);
|
|
276471
|
+
const held = heldRecords.get(id);
|
|
276472
|
+
if (held !== undefined) call(held);
|
|
276473
|
+
return () => {
|
|
276474
|
+
listeners.delete(call);
|
|
276475
|
+
};
|
|
276476
|
+
}
|
|
276477
|
+
`;
|
|
276478
|
+
var HELD_UPDATE_SOURCE = `export async function update<T extends { id: string }>(collection: string, id: string, data: Partial<T>): Promise<T> {
|
|
276479
|
+
// The running app writes the record before the save and puts back what it held if the
|
|
276480
|
+
// save fails, so a Record showing it moves at the press. The wire answers a PUT with
|
|
276481
|
+
// the changed fields only (\`updatedAt\`), so the caller's own data is the record the
|
|
276482
|
+
// graph continues with.
|
|
276483
|
+
const held: Record<string, unknown> = heldRecords.get(id) ?? {};
|
|
276484
|
+
const before: Record<string, unknown> = {};
|
|
276485
|
+
for (const key of Object.keys(data)) before[key] = held[key];
|
|
276486
|
+
holdRecord({ ...data, id });
|
|
276487
|
+
let response: Record<string, unknown>;
|
|
276488
|
+
try {
|
|
276489
|
+
response = await request<Record<string, unknown>>(\`/classes/\${collection}/\${encodeURIComponent(id)}\`, {
|
|
276490
|
+
method: 'PUT',
|
|
276491
|
+
body: data
|
|
276492
|
+
});
|
|
276493
|
+
} catch (error) {
|
|
276494
|
+
holdRecord({ ...before, id });
|
|
276495
|
+
throw error;
|
|
276496
|
+
}
|
|
276497
|
+
const { objectId: _objectId, ...fields } = response;
|
|
276498
|
+
holdRecord({ ...fields, id });
|
|
276499
|
+
return { ...data, ...fields, id } as unknown as T;
|
|
276500
|
+
}
|
|
276501
|
+
`;
|
|
276502
|
+
var FILTER_VALUE_SOURCE = `
|
|
276503
|
+
/**
|
|
276504
|
+
* A filter value as the running app puts it on the wire: a Date goes as the backend's date envelope, and so does a
|
|
276505
|
+
* date written as text when its column is a Date. Anything else goes as it is.
|
|
276506
|
+
*/
|
|
276507
|
+
export function filterValue(value: unknown, dateColumn = false): unknown {
|
|
276508
|
+
if (value instanceof Date) return { __type: 'Date', iso: value.toISOString() };
|
|
276509
|
+
if (dateColumn && value !== null && value !== undefined) {
|
|
276510
|
+
const asDate = new Date(String(value));
|
|
276511
|
+
if (!Number.isNaN(asDate.getTime())) return { __type: 'Date', iso: asDate.toISOString() };
|
|
276512
|
+
}
|
|
276513
|
+
return value;
|
|
276514
|
+
}
|
|
276515
|
+
`;
|
|
276516
|
+
function clientModule(backend, returnLeg, liveRecords, wiredFilters = false) {
|
|
274453
276517
|
return GENERATED_CLIENT_TS + `/**
|
|
274454
276518
|
* The NodeGX backend client \u2014 the Parse wire protocol the running app speaks
|
|
274455
276519
|
* (noodl-runtime's ParseWireAdapter), reduced to what this app's calls need:
|
|
@@ -274601,7 +276665,7 @@ function fromWire<T extends { id: string }>(record: Record<string, unknown>): T
|
|
|
274601
276665
|
for (const [key, value] of Object.entries(fields)) row[key] = fromWireValue(value);
|
|
274602
276666
|
return row as unknown as T;
|
|
274603
276667
|
}
|
|
274604
|
-
|
|
276668
|
+
${liveRecords ? HELD_RECORDS_SOURCE : ""}${wiredFilters ? FILTER_VALUE_SOURCE : ""}
|
|
274605
276669
|
export interface QueryParams {
|
|
274606
276670
|
where?: unknown;
|
|
274607
276671
|
sort?: string[];
|
|
@@ -274622,13 +276686,13 @@ export async function query<T extends { id: string }>(collection: string, params
|
|
|
274622
276686
|
skip: params.skip
|
|
274623
276687
|
}
|
|
274624
276688
|
});
|
|
274625
|
-
return response.results.map((record) => fromWire<T>(record));
|
|
276689
|
+
return response.results.map((record) => ${liveRecords ? "holdRecord(fromWire<T>(record))" : "fromWire<T>(record)"});
|
|
274626
276690
|
}
|
|
274627
276691
|
|
|
274628
276692
|
/** One record by Id \u2014 the runtime's own \`GET /classes/<collection>/<id>\` (ParseWireAdapter.fetch). */
|
|
274629
276693
|
export async function fetchOne<T extends { id: string }>(collection: string, id: string): Promise<T> {
|
|
274630
276694
|
const response = await request<Record<string, unknown>>(\`/classes/\${collection}/\${encodeURIComponent(id)}\`);
|
|
274631
|
-
return fromWire<T>(response);
|
|
276695
|
+
return ${liveRecords ? "holdRecord(fromWire<T>(response))" : "fromWire<T>(response)"};
|
|
274632
276696
|
}
|
|
274633
276697
|
|
|
274634
276698
|
export async function create<T extends { id: string }>(collection: string, data: Partial<T>): Promise<T> {
|
|
@@ -274636,20 +276700,10 @@ export async function create<T extends { id: string }>(collection: string, data:
|
|
|
274636
276700
|
method: 'POST',
|
|
274637
276701
|
body: data
|
|
274638
276702
|
});
|
|
274639
|
-
return fromWire<T>({ ...data, ...response });
|
|
274640
|
-
}
|
|
274641
|
-
|
|
274642
|
-
export async function update<T extends { id: string }>(collection: string, id: string, data: Partial<T>): Promise<T> {
|
|
274643
|
-
// The wire answers a PUT with the changed fields only (\`updatedAt\`), so the
|
|
274644
|
-
// caller's own data is the record the graph continues with.
|
|
274645
|
-
const response = await request<Record<string, unknown>>(\`/classes/\${collection}/\${encodeURIComponent(id)}\`, {
|
|
274646
|
-
method: 'PUT',
|
|
274647
|
-
body: data
|
|
274648
|
-
});
|
|
274649
|
-
const { objectId: _objectId, ...fields } = response;
|
|
274650
|
-
return { ...data, ...fields, id } as unknown as T;
|
|
276703
|
+
return ${liveRecords ? "holdRecord(fromWire<T>({ ...data, ...response }))" : "fromWire<T>({ ...data, ...response })"};
|
|
274651
276704
|
}
|
|
274652
276705
|
|
|
276706
|
+
${liveRecords ? HELD_UPDATE_SOURCE : PLAIN_UPDATE_SOURCE}
|
|
274653
276707
|
export async function remove(collection: string, id: string): Promise<void> {
|
|
274654
276708
|
await request<unknown>(\`/classes/\${collection}/\${encodeURIComponent(id)}\`, { method: 'DELETE' });
|
|
274655
276709
|
}
|
|
@@ -275764,6 +277818,11 @@ function byType() {
|
|
|
275764
277818
|
}
|
|
275765
277819
|
return cachedByType;
|
|
275766
277820
|
}
|
|
277821
|
+
var cachedShipped;
|
|
277822
|
+
function shippedTypeNames() {
|
|
277823
|
+
if (!cachedShipped) cachedShipped = new Set(shippedCatalog.nodes.map((n) => n.typeName));
|
|
277824
|
+
return cachedShipped;
|
|
277825
|
+
}
|
|
275767
277826
|
function allExamples() {
|
|
275768
277827
|
return shippedCatalog.examples ?? [];
|
|
275769
277828
|
}
|
|
@@ -276391,39 +278450,6 @@ function validateComponentPath(path43) {
|
|
|
276391
278450
|
}
|
|
276392
278451
|
|
|
276393
278452
|
// src/project/runOnChangePins.ts
|
|
276394
|
-
var projectWillBeCleanedUp = (version2) => projectPredatesRunOnValueChange(version2);
|
|
276395
|
-
var incomingOf = (connections, id) => JSON.stringify(
|
|
276396
|
-
(connections ?? []).filter((c) => c.toId === id).map((c) => `${c.fromId}.${c.fromProperty}>${c.toProperty}`).sort()
|
|
276397
|
-
);
|
|
276398
|
-
function pinAuthoredRunOnChange(key2, files, onDisk) {
|
|
276399
|
-
const nodes = files.nodes.nodes ?? [];
|
|
276400
|
-
const plan = planRunOnValueChangeMigration({
|
|
276401
|
-
components: [{ name: key2, graph: { roots: nodes, connections: files.connections.connections } }]
|
|
276402
|
-
});
|
|
276403
|
-
if (plan.writes.length === 0) return [];
|
|
276404
|
-
const before = /* @__PURE__ */ new Map();
|
|
276405
|
-
for (const n of onDisk?.nodes.nodes ?? []) before.set(n.id, n);
|
|
276406
|
-
const authored = (n) => {
|
|
276407
|
-
const old = before.get(n.id);
|
|
276408
|
-
if (!old) return true;
|
|
276409
|
-
if (JSON.stringify(old) !== JSON.stringify(n)) return true;
|
|
276410
|
-
return incomingOf(onDisk?.connections.connections, n.id) !== incomingOf(files.connections.connections, n.id);
|
|
276411
|
-
};
|
|
276412
|
-
const byNode = /* @__PURE__ */ new Map();
|
|
276413
|
-
for (const w of plan.writes) byNode.set(w.nodeId, [...byNode.get(w.nodeId) ?? [], w.parameter]);
|
|
276414
|
-
const written2 = [];
|
|
276415
|
-
for (const node of nodes) {
|
|
276416
|
-
const parameters = byNode.get(node.id);
|
|
276417
|
-
if (!parameters || !authored(node)) continue;
|
|
276418
|
-
const existing = node.parameters ?? {};
|
|
276419
|
-
const rebuilt = {};
|
|
276420
|
-
for (const p of parameters) rebuilt[p] = true;
|
|
276421
|
-
for (const name of Object.keys(existing)) if (!(name in rebuilt)) rebuilt[name] = existing[name];
|
|
276422
|
-
node.parameters = rebuilt;
|
|
276423
|
-
for (const p of parameters) written2.push({ node: node.id, parameter: p });
|
|
276424
|
-
}
|
|
276425
|
-
return written2;
|
|
276426
|
-
}
|
|
276427
278453
|
var TICKED_WHY = 'This project was saved before the "run on value change" setting existed. The editor upgrades it the next time it opens. That upgrade unticks every unset box on a node whose Run is wired. The door wrote these boxes as ticked, which is what an unset box means today. Each node still runs when these values change. Send the parameter as false to switch one off.';
|
|
276428
278454
|
function tickedPayload(boxes) {
|
|
276429
278455
|
return boxes && boxes.length > 0 ? { ticked: { boxes: [...boxes], why: TICKED_WHY } } : {};
|
|
@@ -277629,6 +279655,70 @@ function completionPayload(state) {
|
|
|
277629
279655
|
};
|
|
277630
279656
|
}
|
|
277631
279657
|
|
|
279658
|
+
// src/honesty.ts
|
|
279659
|
+
var COMPONENT_SCRIPT_NODES_SHOWN = 12;
|
|
279660
|
+
var PROJECT_COMPONENTS_SHOWN = 5;
|
|
279661
|
+
var PROJECT_SCRIPT_TYPES_SHOWN = 8;
|
|
279662
|
+
function honestyComponents(store2) {
|
|
279663
|
+
return portProjectFromStore(store2.format).components.map((c) => ({ name: c.name, nodes: c.nodes }));
|
|
279664
|
+
}
|
|
279665
|
+
function projectHonestyOf(store2) {
|
|
279666
|
+
return projectHonesty(honestyComponents(store2), { coreTypes: shippedTypeNames() });
|
|
279667
|
+
}
|
|
279668
|
+
function componentHonestyOf(name, nodes) {
|
|
279669
|
+
return componentHonesty({ name, nodes }, { coreTypes: shippedTypeNames() });
|
|
279670
|
+
}
|
|
279671
|
+
function projectHonestyPayload(h) {
|
|
279672
|
+
const byType2 = {};
|
|
279673
|
+
for (const p of h.scriptParts) byType2[p.type] = (byType2[p.type] ?? 0) + 1;
|
|
279674
|
+
return {
|
|
279675
|
+
line: honestyLine(h, 0),
|
|
279676
|
+
percent: h.percent,
|
|
279677
|
+
known: h.known,
|
|
279678
|
+
script: h.script,
|
|
279679
|
+
unspecced: h.unspecced,
|
|
279680
|
+
percentByLines: h.percentByLines,
|
|
279681
|
+
scriptLines: h.scriptLines,
|
|
279682
|
+
scriptByType: Object.fromEntries(
|
|
279683
|
+
Object.entries(byType2).sort((a8, b) => b[1] - a8[1] || a8[0].localeCompare(b[0])).slice(0, PROJECT_SCRIPT_TYPES_SHOWN)
|
|
279684
|
+
),
|
|
279685
|
+
mostScript: h.components.filter((c) => c.script > 0).sort((a8, b) => b.script - a8.script || a8.component.localeCompare(b.component)).slice(0, PROJECT_COMPONENTS_SHOWN).map((c) => ({ component: c.component, percent: c.percent, script: c.script })),
|
|
279686
|
+
about: HONESTY_SENTENCE
|
|
279687
|
+
};
|
|
279688
|
+
}
|
|
279689
|
+
function componentHonestyPayload(h) {
|
|
279690
|
+
const parts = [...h.scriptParts].sort((a8, b) => b.lines - a8.lines);
|
|
279691
|
+
return {
|
|
279692
|
+
line: honestyLine(h),
|
|
279693
|
+
percent: h.percent,
|
|
279694
|
+
known: h.known,
|
|
279695
|
+
script: h.script,
|
|
279696
|
+
unspecced: h.unspecced,
|
|
279697
|
+
scriptNodes: parts.slice(0, COMPONENT_SCRIPT_NODES_SHOWN).map((p) => ({ id: p.id, type: p.type, ...p.label ? { label: p.label } : {}, lines: p.lines })),
|
|
279698
|
+
...parts.length > COMPONENT_SCRIPT_NODES_SHOWN ? { moreScriptNodes: parts.length - COMPONENT_SCRIPT_NODES_SHOWN } : {}
|
|
279699
|
+
};
|
|
279700
|
+
}
|
|
279701
|
+
function writeHonestyPayload(store2, legacyName) {
|
|
279702
|
+
const project2 = projectHonestyOf(store2);
|
|
279703
|
+
const mine = project2.components.find((c) => c.component === legacyName);
|
|
279704
|
+
return {
|
|
279705
|
+
honesty: {
|
|
279706
|
+
component: mine ? `this component: ${honestyLine(mine)}` : "this component: not found after the write",
|
|
279707
|
+
project: `the project: ${honestyLine(project2, 0)}`
|
|
279708
|
+
}
|
|
279709
|
+
};
|
|
279710
|
+
}
|
|
279711
|
+
function honestySection(h) {
|
|
279712
|
+
const most = projectHonestyPayload(h).mostScript;
|
|
279713
|
+
const named = h.scriptParts.length ? [...h.scriptParts].sort((a8, b) => b.lines - a8.lines).slice(0, 3).map((p) => "`" + scriptPartName(p) + "`") : [];
|
|
279714
|
+
return [
|
|
279715
|
+
`HONESTY \u2014 ${honestyLine(h, 0)}` + (h.percentByLines !== null && h.script ? `; ${h.percentByLines}% weighed by script lines (${h.scriptLines} lines).` : "."),
|
|
279716
|
+
most.length ? `Most script: ${most.map((c) => `${c.component} ${c.script} (${c.percent === null ? "\u2014" : c.percent + "%"})`).join(", ")}.` : "No script nodes.",
|
|
279717
|
+
named.length ? `Largest script parts: ${named.join(", ")}.` : "",
|
|
279718
|
+
HONESTY_SENTENCE
|
|
279719
|
+
].filter(Boolean).join("\n");
|
|
279720
|
+
}
|
|
279721
|
+
|
|
277632
279722
|
// src/tools/util.ts
|
|
277633
279723
|
function jsonResult(payload) {
|
|
277634
279724
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
@@ -279108,7 +281198,9 @@ function registerAuthorTools(server, binding, plans, examples, ledger, disclosur
|
|
|
279108
281198
|
// it yet. Said on every visual write rather than only on pages: a
|
|
279109
281199
|
// section component is what the page is made of, and "I only changed
|
|
279110
281200
|
// a card" is how a page stops being the thing anybody renders.
|
|
279111
|
-
...drawsSomething(candidate) ? completionPayload(ledger.state(store2.projectDir)) : {}
|
|
281201
|
+
...drawsSomething(candidate) ? completionPayload(ledger.state(store2.projectDir)) : {},
|
|
281202
|
+
// P111 SEE-001 — the component's number with its script named, and the project's after this write
|
|
281203
|
+
...writeHonestyPayload(store2, legacyName)
|
|
279112
281204
|
};
|
|
279113
281205
|
return jsonResult(payload);
|
|
279114
281206
|
}
|
|
@@ -279202,7 +281294,9 @@ function registerAuthorTools(server, binding, plans, examples, ledger, disclosur
|
|
|
279202
281294
|
...backendRevealPayload(disclosure?.revealForNodes(candidate.nodes.nodes) ?? []),
|
|
279203
281295
|
...unchanged ? {} : cloudDeployPayload([stored.legacyName], store2, disclosure),
|
|
279204
281296
|
...visualRootsPayload(candidate, args.set?.visual_roots),
|
|
279205
|
-
...drawsSomething(candidate) ? completionPayload(ledger.state(store2.projectDir)) : {}
|
|
281297
|
+
...drawsSomething(candidate) ? completionPayload(ledger.state(store2.projectDir)) : {},
|
|
281298
|
+
// P111 SEE-001 — the component's number with its script named, and the project's after this write
|
|
281299
|
+
...writeHonestyPayload(store2, stored.legacyName)
|
|
279206
281300
|
};
|
|
279207
281301
|
return jsonResult(payload);
|
|
279208
281302
|
}
|
|
@@ -281914,7 +284008,7 @@ function registerBackendWriteTools(server, binding) {
|
|
|
281914
284008
|
}).optional().describe('Required for type "schedule"'),
|
|
281915
284009
|
webhook: external_exports.object({
|
|
281916
284010
|
slug: external_exports.string().describe("URL slug: POST /hooks/<backendId>/<slug>"),
|
|
281917
|
-
scheme: external_exports.enum(["hmac-sha256", "token"]).optional().describe("Secret scheme (default hmac-sha256)"),
|
|
284011
|
+
scheme: external_exports.enum(["hmac-sha256", "hmac-sha512", "token"]).optional().describe("Secret scheme (default hmac-sha256; hmac-sha512 is Paystack: secret = its key)"),
|
|
281918
284012
|
maxBodyBytes: external_exports.number().optional().describe("Body size limit (default 1MB)")
|
|
281919
284013
|
}).optional().describe('Required for type "webhook"'),
|
|
281920
284014
|
dbChange: external_exports.object({
|
|
@@ -287007,7 +289101,9 @@ function registerReadTools(server, binding, options) {
|
|
|
287007
289101
|
note: "Standing preferences this user wrote about themselves. They hold in every project, and they outrank your own habits and defaults. This project's own conventions (docs/CONVENTIONS.md) outrank them in turn: where the two disagree, follow the project and say that you did.",
|
|
287008
289102
|
preferences: userPreferences
|
|
287009
289103
|
}
|
|
287010
|
-
} : {}
|
|
289104
|
+
} : {},
|
|
289105
|
+
// P111 SEE-001 — the honesty number, always (R2). A result field: no resident-surface cost.
|
|
289106
|
+
honesty: projectHonestyPayload(projectHonestyOf(store2))
|
|
287011
289107
|
};
|
|
287012
289108
|
return jsonResult(payload);
|
|
287013
289109
|
})
|
|
@@ -287081,6 +289177,7 @@ function registerReadTools(server, binding, options) {
|
|
|
287081
289177
|
};
|
|
287082
289178
|
const drawn3 = drawnPortsFor(store2, stored.files.component.path ?? "/" + stored.key, stored.files.nodes.nodes);
|
|
287083
289179
|
if (Object.keys(drawn3).length) payload.drawnPorts = drawn3;
|
|
289180
|
+
payload.honesty = componentHonestyPayload(componentHonestyOf(stored.legacyName, stored.files.nodes.nodes));
|
|
287084
289181
|
return jsonResult(payload);
|
|
287085
289182
|
})
|
|
287086
289183
|
);
|
|
@@ -287210,6 +289307,9 @@ function registerRenderTools(server, binding, ledger, options = {}) {
|
|
|
287210
289307
|
// page nobody could reach.
|
|
287211
289308
|
...verdict ? [{ type: "text", text: verdictHeadline(verdict) }] : [],
|
|
287212
289309
|
{ type: "text", text: JSON.stringify(report3) },
|
|
289310
|
+
// P111 SEE-001 — the honesty number, the same one get_project_info and every write report (a result block:
|
|
289311
|
+
// no resident-surface cost). Read from the files the render read.
|
|
289312
|
+
{ type: "text", text: honestySection(projectHonestyOf(binding.require())) },
|
|
287213
289313
|
// FLD-011 AC1 — with `out_dir` the harness wrote files and returned no base64, so
|
|
287214
289314
|
// `screenshots` is empty and the loop below produces nothing. The manifest replaces it,
|
|
287215
289315
|
// because a caller told only "written to a folder" has to list a directory to find out
|
|
@@ -291275,7 +293375,7 @@ function refuseUnknownArguments(server) {
|
|
|
291275
293375
|
// src/server.ts
|
|
291276
293376
|
var PKG_VERSION3 = (
|
|
291277
293377
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
291278
|
-
true ? "0.4.
|
|
293378
|
+
true ? "0.4.2" : null.version
|
|
291279
293379
|
);
|
|
291280
293380
|
function createServer2(options) {
|
|
291281
293381
|
setClientProfile(options.profile);
|