@nodegx/preview 0.2.5 → 0.2.6

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.
@@ -18277,6 +18277,27 @@ var init_run_on_value_change = __esm({
18277
18277
  }
18278
18278
  });
18279
18279
 
18280
+ // ../noodl-runtime/src/tracecause.ts
18281
+ function currentTraceCause(context) {
18282
+ const c = context;
18283
+ return c && c.traceEnabled ? c._currentCause || 0 : 0;
18284
+ }
18285
+ function withTraceCause(context, cause, fn) {
18286
+ const c = context;
18287
+ if (!cause || !c || !c.traceEnabled) return fn();
18288
+ const previous = c._currentCause;
18289
+ c._currentCause = cause;
18290
+ try {
18291
+ return fn();
18292
+ } finally {
18293
+ c._currentCause = previous;
18294
+ }
18295
+ }
18296
+ var init_tracecause = __esm({
18297
+ "../noodl-runtime/src/tracecause.ts"() {
18298
+ }
18299
+ });
18300
+
18280
18301
  // ../noodl-runtime/src/outputproperty.ts
18281
18302
  var require_outputproperty = __commonJS({
18282
18303
  "../noodl-runtime/src/outputproperty.ts"(exports2, module2) {
@@ -18452,6 +18473,7 @@ var require_node2 = __commonJS({
18452
18473
  init_diagnostics();
18453
18474
  init_outcome();
18454
18475
  init_run_on_value_change();
18476
+ init_tracecause();
18455
18477
  var OutputProperty = require_outputproperty();
18456
18478
  var NAN_INPUT_KEY = "node/nan-input/";
18457
18479
  var SIGNAL_PULSE = Object.freeze({ __noodlSignalPulse: true });
@@ -18964,6 +18986,8 @@ var require_node2 = __commonJS({
18964
18986
  });
18965
18987
  Node17.prototype.beginOutcome = function(inputData) {
18966
18988
  const token = { reported: void 0 };
18989
+ const cause = currentTraceCause(this.context);
18990
+ if (cause) token.cause = cause;
18967
18991
  const scope = this.nodeScope;
18968
18992
  const runContext = scope && scope.runContext;
18969
18993
  if (runContext && runContext.beginStep) {
@@ -18999,6 +19023,10 @@ var require_node2 = __commonJS({
18999
19023
  return;
19000
19024
  }
19001
19025
  token.reported = outcome;
19026
+ const traceContext = this.context;
19027
+ const tokenCause = token.cause;
19028
+ const causeBefore = traceContext ? traceContext._currentCause : 0;
19029
+ if (tokenCause && traceContext && traceContext.traceEnabled) traceContext._currentCause = tokenCause;
19002
19030
  this._raisingForOutcome = true;
19003
19031
  try {
19004
19032
  if (outcome === "failure" && !(options && options.raise === false)) {
@@ -19028,6 +19056,7 @@ var require_node2 = __commonJS({
19028
19056
  }
19029
19057
  } finally {
19030
19058
  this._raisingForOutcome = false;
19059
+ if (tokenCause && traceContext && traceContext.traceEnabled) traceContext._currentCause = causeBefore;
19031
19060
  }
19032
19061
  const step = token.step;
19033
19062
  if (step !== void 0 && step !== null) {
@@ -19123,7 +19152,12 @@ var require_node2 = __commonJS({
19123
19152
  this.flagDirty();
19124
19153
  };
19125
19154
  Node17.prototype.scheduleAfterInputsHaveUpdated = function(callback) {
19126
- this._afterInputsHaveUpdatedCallbacks.push(callback);
19155
+ const cause = currentTraceCause(this.context);
19156
+ this._afterInputsHaveUpdatedCallbacks.push(
19157
+ cause ? function() {
19158
+ return withTraceCause(this.context, cause, () => callback.call(this));
19159
+ } : callback
19160
+ );
19127
19161
  this.flagDirty();
19128
19162
  };
19129
19163
  Node17.prototype.setNodeModel = function(nodeModel) {
@@ -19226,6 +19260,60 @@ var require_node2 = __commonJS({
19226
19260
  }
19227
19261
  });
19228
19262
 
19263
+ // ../nodegx-project-contract/renamed-ports.ts
19264
+ function renamedOutput(typeName2, port) {
19265
+ const renames = own.call(RENAMED_OUTPUTS, typeName2) ? RENAMED_OUTPUTS[typeName2] : void 0;
19266
+ return renames && own.call(renames, port) ? renames[port] : void 0;
19267
+ }
19268
+ var RENAMED_OUTPUTS, own;
19269
+ var init_renamed_ports = __esm({
19270
+ "../nodegx-project-contract/renamed-ports.ts"() {
19271
+ "use strict";
19272
+ RENAMED_OUTPUTS = {
19273
+ AddDbModelRelation: { relationAdded: "done" },
19274
+ Boolean: { stored: "completed" },
19275
+ CloudFunction2: { success: "done" },
19276
+ CollectionClear: { modified: "done" },
19277
+ CollectionInsert: { modified: "done" },
19278
+ CollectionNew: { created: "done" },
19279
+ CollectionRemove: { modified: "done" },
19280
+ Color: { stored: "completed" },
19281
+ DeleteDbModelProperties: { deleted: "done" },
19282
+ "Event Sender": { sent: "done" },
19283
+ NavigationClosePopup: { success: "done" },
19284
+ NewDbModelProperties: { created: "done" },
19285
+ NewModel: { created: "done" },
19286
+ Number: { stored: "completed" },
19287
+ "Open File Picker": { success: "done", cancelled: "unchanged" },
19288
+ PageStackNavigate: { navigated: "done" },
19289
+ PageStackNavigateBack: { success: "done" },
19290
+ PageStackNavigateToPath: { success: "done" },
19291
+ RemoveDbModelRelation: { relationRemoved: "done" },
19292
+ RouterNavigate: { navigated: "done" },
19293
+ RunTasks: { success: "done" },
19294
+ SetDbModelProperties: { stored: "done" },
19295
+ SetModelProperties: { stored: "done" },
19296
+ "Sign File URL": { success: "done" },
19297
+ String: { stored: "completed" },
19298
+ "Unique Id": { generated: "done" },
19299
+ "Upload File": { success: "done" },
19300
+ "net.noodl.HTTP": { success: "done" },
19301
+ "net.noodl.SetComponentObjectProperties": { stored: "done" },
19302
+ "net.noodl.SetParentComponentObjectProperties": { stored: "done" },
19303
+ "net.noodl.externallink": { success: "done" },
19304
+ "net.noodl.user.LogIn": { success: "done" },
19305
+ "net.noodl.user.LogOut": { success: "done" },
19306
+ "net.noodl.user.RequestMagicLink": { success: "done" },
19307
+ "net.noodl.user.SetUserProperties": { success: "done" },
19308
+ "net.noodl.user.SignInWith": { success: "done" },
19309
+ "net.noodl.user.SignUp": { success: "done" },
19310
+ "noodl.cloud.response": { sent: "done" },
19311
+ "noodl.cloud.sendemail": { sent: "done", failed: "failure" }
19312
+ };
19313
+ own = Object.prototype.hasOwnProperty;
19314
+ }
19315
+ });
19316
+
19229
19317
  // ../noodl-runtime/src/guid.ts
19230
19318
  var require_guid = __commonJS({
19231
19319
  "../noodl-runtime/src/guid.ts"(exports2, module2) {
@@ -19243,6 +19331,7 @@ var require_guid = __commonJS({
19243
19331
  var require_nodescope = __commonJS({
19244
19332
  "../noodl-runtime/src/nodescope.ts"(exports2, module2) {
19245
19333
  "use strict";
19334
+ init_renamed_ports();
19246
19335
  var guid4 = require_guid();
19247
19336
  function hasNode(map2, id) {
19248
19337
  return Object.prototype.hasOwnProperty.call(map2, id);
@@ -19275,11 +19364,15 @@ var require_nodescope = __commonJS({
19275
19364
  var sourceNode = this.getNodeWithId(connectionData.sourceId), targetNode = this.getNodeWithId(connectionData.targetId);
19276
19365
  targetNode.registerInputIfNeeded(connectionData.targetPort);
19277
19366
  sourceNode.registerOutputIfNeeded(connectionData.sourcePort);
19278
- targetNode.connectInput(connectionData.targetPort, sourceNode, connectionData.sourcePort);
19367
+ targetNode.connectInput(connectionData.targetPort, sourceNode, sourcePortOf(sourceNode, connectionData.sourcePort));
19279
19368
  } catch (e) {
19280
19369
  console.error(e.message);
19281
19370
  }
19282
19371
  };
19372
+ function sourcePortOf(sourceNode, port) {
19373
+ if (sourceNode.hasOutput(port)) return port;
19374
+ return renamedOutput(sourceNode.name, port) ?? port;
19375
+ }
19283
19376
  NodeScope.prototype.setNodeParameters = function(node, nodeModel) {
19284
19377
  const variant = this.context.variants.getVariant(nodeModel.type, nodeModel.variant);
19285
19378
  if (variant) {
@@ -19287,10 +19380,10 @@ var require_nodescope = __commonJS({
19287
19380
  } else {
19288
19381
  const parameters = nodeModel.parameters;
19289
19382
  var inputNames = Object.keys(parameters);
19290
- const RUN_ON_CHANGE_PREFIX3 = "runOnChange-";
19383
+ const RUN_ON_CHANGE_PREFIX4 = "runOnChange-";
19291
19384
  const metadata = this.context.nodeRegister.hasNode(node.name) ? this.context.nodeRegister.getNodeMetadata(node.name) : void 0;
19292
19385
  const priorityOf = function(name2) {
19293
- if (name2.indexOf(RUN_ON_CHANGE_PREFIX3) === 0) return Number.MAX_SAFE_INTEGER;
19386
+ if (name2.indexOf(RUN_ON_CHANGE_PREFIX4) === 0) return Number.MAX_SAFE_INTEGER;
19294
19387
  const input2 = metadata ? metadata.inputs[name2] : void 0;
19295
19388
  return input2 ? input2.inputPriority : 0;
19296
19389
  };
@@ -19464,7 +19557,9 @@ var require_nodescope = __commonJS({
19464
19557
  };
19465
19558
  NodeScope.prototype.removeConnection = function(connectionModel) {
19466
19559
  var targetNode = this.getNodeWithId(connectionModel.targetId);
19467
- targetNode.removeInputConnection(connectionModel.targetPort, connectionModel.sourceId, connectionModel.sourcePort);
19560
+ const sourceNode = this.hasNodeWithId(connectionModel.sourceId) ? this.getNodeWithId(connectionModel.sourceId) : void 0;
19561
+ const sourcePort = sourceNode ? sourcePortOf(sourceNode, connectionModel.sourcePort) : connectionModel.sourcePort;
19562
+ targetNode.removeInputConnection(connectionModel.targetPort, connectionModel.sourceId, sourcePort);
19468
19563
  };
19469
19564
  NodeScope.prototype.setComponentModel = async function(componentModel) {
19470
19565
  this.componentModel = componentModel;
@@ -27078,7 +27173,9 @@ var init_function_ports = __esm({
27078
27173
  INPUT_PATTERNS = [/Inputs\.([A-Za-z0-9_]+)/g, /Inputs\s*\[\s*(?:'([^'\n]*)'|"([^"\n]*)")\s*\]/g];
27079
27174
  SIGNAL_PATTERNS = [
27080
27175
  /Outputs\.([A-Za-z0-9_]+)\s*\(\s*\)/g,
27081
- /Outputs\s*\[\s*(?:'([^'\n]*)'|"([^"\n]*)")\s*\]\(\s*\)/g
27176
+ /Outputs\s*\[\s*(?:'([^'\n]*)'|"([^"\n]*)")\s*\]\(\s*\)/g,
27177
+ /Outputs\.([A-Za-z0-9_]+)\.send\s*\(\s*\)/g,
27178
+ /Outputs\s*\[\s*(?:'([^'\n]*)'|"([^"\n]*)")\s*\]\.send\s*\(\s*\)/g
27082
27179
  ];
27083
27180
  OUTPUT_PATTERNS = [/Outputs\.([A-Za-z0-9_]+)/g, /Outputs\s*\[\s*"([^"]*)"\s*\]/g];
27084
27181
  REGEX_AFTER = /* @__PURE__ */ new Set(["(", ",", "=", ":", "[", "!", "&", "|", "?", "{", "}", ";", "+", "-", "*", "%", "<", ">", "~", "^"]);
@@ -27095,6 +27192,7 @@ var require_simplejavascript = __commonJS({
27095
27192
  init_functionDiagnostics();
27096
27193
  init_scriptfiles();
27097
27194
  init_function_ports();
27195
+ init_tracecause();
27098
27196
  var Node17 = require_node2();
27099
27197
  var JavascriptNodeParser4 = require_javascriptnodeparser();
27100
27198
  var { logJavaScriptNodeError: logJavaScriptNodeError2, thrownMessage: thrownMessage3 } = (init_utils(), __toCommonJS(utils_exports));
@@ -27464,11 +27562,12 @@ var require_simplejavascript = __commonJS({
27464
27562
  }
27465
27563
  const inputs = this._internal.inputValues;
27466
27564
  const outputs = this._internal.outputValuesProxy;
27565
+ const runCause = currentTraceCause(this.context);
27467
27566
  placingSignalPorts(this).forEach((key) => {
27468
27567
  const _sendSignal = () => {
27469
27568
  this._internal.outputWrites.add(key.substring("out-".length));
27470
27569
  publishHeldWrites(this);
27471
- if (this.hasOutput(key)) this.sendSignalOnOutput(key);
27570
+ if (this.hasOutput(key)) withTraceCause(this.context, runCause, () => this.sendSignalOnOutput(key));
27472
27571
  };
27473
27572
  const signalValue = _sendSignal;
27474
27573
  signalValue.send = _sendSignal;
@@ -27493,7 +27592,7 @@ var require_simplejavascript = __commonJS({
27493
27592
  this._internal.lastError = "";
27494
27593
  this.flagOutputDirty("error");
27495
27594
  }
27496
- this.sendSignalOnOutput("success");
27595
+ withTraceCause(this.context, runCause, () => this.sendSignalOnOutput("success"));
27497
27596
  reportOutcomes(this, tokens, "done");
27498
27597
  }
27499
27598
  } catch (e) {
@@ -35613,6 +35712,466 @@ var init_TokenLifecycle = __esm({
35613
35712
  }
35614
35713
  });
35615
35714
 
35715
+ // ../noodl-runtime/src/api/backends/ParseAuthAdapter.ts
35716
+ function pageSays(response, phrase) {
35717
+ return typeof response === "string" && response.indexOf(phrase) !== -1;
35718
+ }
35719
+ function htmlPageError(error, invalidPhrase, invalidMessage, fallback) {
35720
+ if (typeof error !== "string" || error.indexOf("<") === -1) return error || fallback;
35721
+ return error.indexOf(invalidPhrase) !== -1 ? invalidMessage : fallback;
35722
+ }
35723
+ function sessionTokenOf(session) {
35724
+ return session ? session.sessionToken : void 0;
35725
+ }
35726
+ var guid, HANDOFF_PARAM, AUTH_ERROR_PARAM, SESSION_MOVED_MESSAGE, ParseAuthAdapter;
35727
+ var init_ParseAuthAdapter = __esm({
35728
+ "../noodl-runtime/src/api/backends/ParseAuthAdapter.ts"() {
35729
+ init_AuthEvents();
35730
+ init_SessionStore();
35731
+ init_TokenLifecycle();
35732
+ guid = require_guid();
35733
+ HANDOFF_PARAM = "nodegx_auth";
35734
+ AUTH_ERROR_PARAM = "nodegx_auth_error";
35735
+ SESSION_MOVED_MESSAGE = "Someone signed in or out while this was being checked, so its answer was not used. Check again.";
35736
+ ParseAuthAdapter = class extends AuthEvents {
35737
+ constructor(options) {
35738
+ super();
35739
+ /** BAK-004: the state of a sign-in returning from a provider or a magic link. */
35740
+ this.oauthReturn = { inProgress: false };
35741
+ /**
35742
+ * One store and one controller per backend, keyed by application id.
35743
+ *
35744
+ * Per handle rather than per adapter because the contract's whole point is
35745
+ * that a project may have more than one backend, and two backends have two
35746
+ * sessions — the phase decided explicitly that they are never shared.
35747
+ */
35748
+ this.stores = /* @__PURE__ */ new Map();
35749
+ this.controllers = /* @__PURE__ */ new Map();
35750
+ this.serializeObject = options.serializeObject;
35751
+ this.createStore = options.createStore || parseSessionStore;
35752
+ this.lifecycle = options.lifecycle || { kind: "eternal" };
35753
+ }
35754
+ // ── Session storage ──────────────────────────────────────────────────────
35755
+ sessionStore(handle) {
35756
+ const appId = handle.publicToken;
35757
+ const cacheKey = String(appId);
35758
+ let store = this.stores.get(cacheKey);
35759
+ if (store === void 0) {
35760
+ store = this.createStore(appId);
35761
+ this.stores.set(cacheKey, store);
35762
+ }
35763
+ return store;
35764
+ }
35765
+ /**
35766
+ * The lifecycle controller for this backend.
35767
+ *
35768
+ * On Parse this is an `eternal` controller: it arms no timer, subscribes to no
35769
+ * storage events, and its gate is a synchronous pass-through. It is on the
35770
+ * request path anyway, so that BCN-004 adds a `performRefresh` rather than a
35771
+ * call site.
35772
+ */
35773
+ lifecycleController(handle) {
35774
+ const cacheKey = String(handle.publicToken);
35775
+ let controller = this.controllers.get(cacheKey);
35776
+ if (controller === void 0) {
35777
+ controller = new TokenLifecycleController({
35778
+ lifecycle: this.lifecycle,
35779
+ store: this.sessionStore(handle),
35780
+ onSessionLost: (reason) => {
35781
+ this.emitAuthEvent("sessionChanged", void 0);
35782
+ this.emitAuthEvent("sessionLost", reason);
35783
+ },
35784
+ onSessionClearedElsewhere: () => {
35785
+ this.emitAuthEvent("sessionChanged", void 0);
35786
+ this.emitAuthEvent("loggedOut");
35787
+ }
35788
+ });
35789
+ controller.start();
35790
+ this.controllers.set(cacheKey, controller);
35791
+ }
35792
+ return controller;
35793
+ }
35794
+ /** The stored session, or `undefined`. Not a contract method. */
35795
+ getCurrentUser(handle) {
35796
+ return this.sessionStore(handle).read();
35797
+ }
35798
+ setSession(handle, session) {
35799
+ this.sessionStore(handle).write(session);
35800
+ this.lifecycleController(handle).sessionChanged();
35801
+ this.emitAuthEvent("sessionChanged", session);
35802
+ }
35803
+ clearSession(handle) {
35804
+ this.sessionStore(handle).clear();
35805
+ this.lifecycleController(handle).sessionChanged();
35806
+ this.emitAuthEvent("sessionChanged", void 0);
35807
+ }
35808
+ // ── The wire ─────────────────────────────────────────────────────────────
35809
+ /**
35810
+ * The Parse auth seam. Moved from `userservice.ts:198` with one substitution:
35811
+ * `this.endpoint`/`this.appId` became `handle.url`/`handle.publicToken`.
35812
+ *
35813
+ * It is deliberately *not* `ParseWireAdapter._makeRequest`. The two differ in
35814
+ * three ways that all matter: this one sends `X-Parse-Installation-Id`, never
35815
+ * sends a master key, and hands its error callback Parse's `code` (the `209`
35816
+ * the session check turns on). Merging them would be a rewrite in a task
35817
+ * whose value is that nothing was rewritten.
35818
+ */
35819
+ _makeRequest(handle, path8, options) {
35820
+ if (!handle.url) {
35821
+ if (options.error) {
35822
+ options.error({ error: "No active cloud service", status: 0 });
35823
+ }
35824
+ return;
35825
+ }
35826
+ const store = this.sessionStore(handle);
35827
+ this.lifecycleController(handle).withSession((session) => {
35828
+ const xhr = new XMLHttpRequest();
35829
+ xhr.onreadystatechange = function() {
35830
+ if (xhr.readyState === 4) {
35831
+ let json;
35832
+ try {
35833
+ json = JSON.parse(xhr.response);
35834
+ } catch (e) {
35835
+ }
35836
+ if (xhr.status === 200 || xhr.status === 201) {
35837
+ options.success(json || xhr.response);
35838
+ } else options.error(json || { error: xhr.responseText, status: xhr.status });
35839
+ }
35840
+ };
35841
+ xhr.open(options.method || "GET", handle.url + path8, true);
35842
+ xhr.setRequestHeader("X-Parse-Application-Id", handle.publicToken);
35843
+ const iidKey = parseInstallationIdKey(handle.publicToken);
35844
+ let _iid = store.readKey(iidKey);
35845
+ if (_iid === void 0) {
35846
+ _iid = guid();
35847
+ store.writeKey(iidKey, _iid);
35848
+ }
35849
+ xhr.setRequestHeader("X-Parse-Installation-Id", _iid);
35850
+ if (options.sessionToken) xhr.setRequestHeader("X-Parse-Session-Token", options.sessionToken);
35851
+ else if (session !== void 0) {
35852
+ xhr.setRequestHeader("X-Parse-Session-Token", session.sessionToken);
35853
+ }
35854
+ xhr.setRequestHeader("Content-Type", "application/json");
35855
+ xhr.send(JSON.stringify(options.content));
35856
+ });
35857
+ }
35858
+ // ── The ten ──────────────────────────────────────────────────────────────
35859
+ logIn(handle, options) {
35860
+ this._makeRequest(handle, "/login", {
35861
+ method: "POST",
35862
+ content: {
35863
+ username: options.username,
35864
+ password: options.password,
35865
+ _method: "GET"
35866
+ },
35867
+ success: (response) => {
35868
+ this.setSession(handle, response);
35869
+ options.success(response);
35870
+ this.emitAuthEvent("loggedIn", response);
35871
+ },
35872
+ error: (e) => {
35873
+ options.error(e.error);
35874
+ }
35875
+ });
35876
+ }
35877
+ logOut(handle, options) {
35878
+ this._makeRequest(handle, "/logout", {
35879
+ method: "POST",
35880
+ content: {},
35881
+ success: () => {
35882
+ this.clearSession(handle);
35883
+ options.success();
35884
+ this.emitAuthEvent("loggedOut");
35885
+ },
35886
+ error: (e) => {
35887
+ options.error(e.error);
35888
+ }
35889
+ });
35890
+ }
35891
+ signUp(handle, options) {
35892
+ const additionalUserProps = options.properties ? this.serializeObject({ ...options.properties }, "_User") : {};
35893
+ this._makeRequest(handle, "/users", {
35894
+ method: "POST",
35895
+ content: Object.assign({}, additionalUserProps, {
35896
+ username: options.username,
35897
+ password: options.password,
35898
+ email: options.email
35899
+ }),
35900
+ success: (response) => {
35901
+ const identity2 = { username: options.username };
35902
+ if (options.email !== void 0) identity2.email = options.email;
35903
+ const _cu = Object.assign(response, identity2, options.properties);
35904
+ if (_cu.emailVerified === void 0) _cu.emailVerified = false;
35905
+ this.setSession(handle, _cu);
35906
+ options.success(response);
35907
+ this.emitAuthEvent("loggedIn", _cu);
35908
+ },
35909
+ error: (e) => {
35910
+ options.error(e.error);
35911
+ }
35912
+ });
35913
+ }
35914
+ fetchCurrentUser(handle, options) {
35915
+ const asked = sessionTokenOf(this.getCurrentUser(handle));
35916
+ this._makeRequest(handle, "/users/me", {
35917
+ method: "GET",
35918
+ sessionToken: options.sessionToken,
35919
+ success: (response) => {
35920
+ if (sessionTokenOf(this.getCurrentUser(handle)) !== asked) {
35921
+ options.error(SESSION_MOVED_MESSAGE);
35922
+ return;
35923
+ }
35924
+ this.setSession(handle, response);
35925
+ this.emitAuthEvent("sessionGained", response);
35926
+ options.success(response);
35927
+ },
35928
+ error: (e) => {
35929
+ if (sessionTokenOf(this.getCurrentUser(handle)) !== asked) {
35930
+ options.error(SESSION_MOVED_MESSAGE);
35931
+ return;
35932
+ }
35933
+ if (e.code === 209) {
35934
+ this.sessionStore(handle).clear();
35935
+ this.emitAuthEvent("sessionLost");
35936
+ }
35937
+ options.error(e.error);
35938
+ }
35939
+ });
35940
+ }
35941
+ verifyEmail(handle, options) {
35942
+ this._makeRequest(
35943
+ handle,
35944
+ "/apps/" + handle.publicToken + "/verify_email?username=" + options.username + "&token=" + options.token,
35945
+ {
35946
+ method: "GET",
35947
+ // This endpoint answers with an HTML page rather than JSON, so the outcome has to be
35948
+ // read out of the page's text.
35949
+ success: (response) => {
35950
+ if (pageSays(response, "Successfully verified your email")) {
35951
+ options.success();
35952
+ } else if (pageSays(response, "Invalid Verification Link")) {
35953
+ options.error("Invalid verification token");
35954
+ } else {
35955
+ options.error("Failed to verify email");
35956
+ }
35957
+ },
35958
+ error: (e) => {
35959
+ options.error(htmlPageError(e.error, "Invalid Verification Link", "Invalid verification token", "Failed to verify email"));
35960
+ }
35961
+ }
35962
+ );
35963
+ }
35964
+ sendEmailVerification(handle, options) {
35965
+ this._makeRequest(handle, "/verificationEmailRequest", {
35966
+ method: "POST",
35967
+ content: { email: options.email },
35968
+ success: () => {
35969
+ options.success();
35970
+ },
35971
+ error: (e) => {
35972
+ options.error(e.error);
35973
+ }
35974
+ });
35975
+ }
35976
+ resetPassword(handle, options) {
35977
+ this._makeRequest(handle, "/apps/" + handle.publicToken + "/request_password_reset", {
35978
+ method: "POST",
35979
+ content: {
35980
+ username: options.username,
35981
+ token: options.token,
35982
+ new_password: options.newPassword
35983
+ },
35984
+ success: (response) => {
35985
+ if (pageSays(response, "Password successfully reset") || pageSays(response, "Successfully updated your password")) {
35986
+ options.success();
35987
+ } else if (pageSays(response, "Invalid Link")) {
35988
+ options.error("Invalid or expired reset link");
35989
+ } else {
35990
+ options.error("Failed to reset password");
35991
+ }
35992
+ },
35993
+ error: (e) => {
35994
+ options.error(htmlPageError(e.error, "Invalid Link", "Invalid or expired reset link", "Failed to reset password"));
35995
+ }
35996
+ });
35997
+ }
35998
+ requestPasswordReset(handle, options) {
35999
+ this._makeRequest(handle, "/requestPasswordReset", {
36000
+ method: "POST",
36001
+ content: { email: options.email },
36002
+ success: () => {
36003
+ options.success();
36004
+ },
36005
+ error: (e) => {
36006
+ options.error(e.error);
36007
+ }
36008
+ });
36009
+ }
36010
+ /**
36011
+ * Send the browser to a provider. This NAVIGATES AWAY: nothing after it runs,
36012
+ * and the result arrives on a later page load through {@link consumeAuthReturn}.
36013
+ *
36014
+ * `redirect` defaults to the current page (minus any leftover sign-in
36015
+ * parameters), so the user comes back where they were. The backend refuses
36016
+ * any target that is neither same-origin nor on its `redirectAllowList` — if
36017
+ * sign-in dead-ends with "origin not allowed", that list is the fix.
36018
+ */
36019
+ signInWithProvider(handle, options) {
36020
+ if (!handle.url) {
36021
+ if (options.error) options.error("No active cloud service");
36022
+ return;
36023
+ }
36024
+ if (!options.provider) {
36025
+ if (options.error) options.error("Sign In With: no provider was set.");
36026
+ return;
36027
+ }
36028
+ const redirect = options.redirect || this._currentUrlWithoutAuthParams();
36029
+ const url = `${handle.url}/oauth/${encodeURIComponent(options.provider)}/start?redirect=${encodeURIComponent(redirect)}`;
36030
+ window.location.href = url;
36031
+ }
36032
+ /**
36033
+ * Ask the backend to email a one-click sign-in link.
36034
+ *
36035
+ * Succeeds identically for a known and an unknown address — the endpoint is
36036
+ * anonymous and anti-enumerating by design, so "success" here means "the
36037
+ * request was accepted", never "an account exists". The node's help text says
36038
+ * so, because a UI that claims "check your inbox" only for real accounts
36039
+ * re-creates the oracle the endpoint removed.
36040
+ */
36041
+ requestMagicLink(handle, options) {
36042
+ this._makeRequest(handle, "/auth/magic-link", {
36043
+ method: "POST",
36044
+ content: { email: options.email, redirect: options.redirect || this._currentUrlWithoutAuthParams() },
36045
+ success: () => options.success(),
36046
+ error: (e) => options.error(e.error)
36047
+ });
36048
+ }
36049
+ // ── Not contract, and deliberately so ────────────────────────────────────
36050
+ /**
36051
+ * Write user-profile fields onto the signed-in user.
36052
+ *
36053
+ * Not one of the ten. It is the `Set User Properties` node's whole
36054
+ * implementation, and BCN-006's Desired State §3 makes it schema-driven across
36055
+ * five backends — which is step 6, not this pass. It moves with the wire
36056
+ * because leaving it behind would have left a second `_makeRequest` in
36057
+ * `userservice.ts`, and the entire claim of this move is that there is one.
36058
+ */
36059
+ setUserProperties(handle, options) {
36060
+ const _cu = this.getCurrentUser(handle);
36061
+ if (_cu === void 0) {
36062
+ options.error("Nobody is signed in.");
36063
+ return;
36064
+ }
36065
+ {
36066
+ const propsToSave = this.serializeObject({ ...options.properties }, "_User");
36067
+ const _content = Object.assign(
36068
+ {},
36069
+ { email: options.email, username: options.username },
36070
+ propsToSave
36071
+ );
36072
+ delete _content.emailVerified;
36073
+ delete _content.createdAt;
36074
+ delete _content.updatedAt;
36075
+ this._makeRequest(handle, "/users/" + _cu.objectId, {
36076
+ method: "PUT",
36077
+ content: _content,
36078
+ success: (response) => {
36079
+ Object.assign(_cu, _content);
36080
+ this.setSession(handle, _cu);
36081
+ options.success(response);
36082
+ },
36083
+ error: (e) => {
36084
+ options.error(e.error);
36085
+ }
36086
+ });
36087
+ }
36088
+ }
36089
+ /** The sign-in methods this backend offers — for rendering a set of buttons. */
36090
+ listAuthProviders(handle, options) {
36091
+ this._makeRequest(handle, "/auth/providers", {
36092
+ method: "GET",
36093
+ success: (response) => options.success(response),
36094
+ error: (e) => options.error(e.error)
36095
+ });
36096
+ }
36097
+ // ── BAK-004 — the provider return leg ────────────────────────────────────
36098
+ /**
36099
+ * Look for a sign-in result in the current URL and act on it.
36100
+ *
36101
+ * Returns true when this page load IS a sign-in return, so the caller can skip
36102
+ * its ordinary stale-session validation.
36103
+ *
36104
+ * **Per backend, not contract.** BCN-006 step 5 is four different redirect
36105
+ * shapes — Parse `authData`, Directus SSO, Supabase, PocketBase
36106
+ * `auth-with-oauth2` — and this is the first of the four. What they have in
36107
+ * common is only the *outcome*: a session, established. That is where the
36108
+ * contract's surface is, and it is `signInWithProvider` plus a `loggedIn`.
36109
+ */
36110
+ consumeAuthReturn(handle) {
36111
+ if (typeof window === "undefined" || !window.location) return false;
36112
+ let params;
36113
+ try {
36114
+ params = new URLSearchParams(window.location.search);
36115
+ } catch (e) {
36116
+ return false;
36117
+ }
36118
+ const code = params.get(HANDOFF_PARAM);
36119
+ const error = params.get(AUTH_ERROR_PARAM);
36120
+ if (!code && !error) return false;
36121
+ params.delete(HANDOFF_PARAM);
36122
+ params.delete(AUTH_ERROR_PARAM);
36123
+ this._stripAuthParamsFromUrl(params);
36124
+ if (error) {
36125
+ this.oauthReturn = { inProgress: false, succeeded: false, error };
36126
+ setTimeout(() => this.emitAuthEvent("oauthReturn", this.oauthReturn), 0);
36127
+ return true;
36128
+ }
36129
+ this.oauthReturn = { inProgress: true };
36130
+ this._makeRequest(handle, "/oauth/exchange", {
36131
+ method: "POST",
36132
+ content: { code },
36133
+ success: (response) => {
36134
+ this.setSession(handle, response);
36135
+ this.oauthReturn = {
36136
+ inProgress: false,
36137
+ succeeded: true,
36138
+ outcome: response.authOutcome,
36139
+ notice: response.authNotice || void 0
36140
+ };
36141
+ this.emitAuthEvent("oauthReturn", this.oauthReturn);
36142
+ this.emitAuthEvent("loggedIn", response);
36143
+ },
36144
+ error: (e) => {
36145
+ this.oauthReturn = {
36146
+ inProgress: false,
36147
+ succeeded: false,
36148
+ error: e && e.error || "Sign-in could not be completed."
36149
+ };
36150
+ this.emitAuthEvent("oauthReturn", this.oauthReturn);
36151
+ }
36152
+ });
36153
+ return true;
36154
+ }
36155
+ /** Rewrite the address bar without the sign-in parameters, keeping everything else. */
36156
+ _stripAuthParamsFromUrl(remaining) {
36157
+ if (!window.history || typeof window.history.replaceState !== "function") return;
36158
+ const query = remaining.toString();
36159
+ const cleaned = window.location.pathname + (query ? `?${query}` : "") + window.location.hash;
36160
+ try {
36161
+ window.history.replaceState(window.history.state, "", cleaned);
36162
+ } catch (e) {
36163
+ }
36164
+ }
36165
+ _currentUrlWithoutAuthParams() {
36166
+ const url = new URL(window.location.href);
36167
+ url.searchParams.delete(HANDOFF_PARAM);
36168
+ url.searchParams.delete(AUTH_ERROR_PARAM);
36169
+ return url.toString();
36170
+ }
36171
+ };
36172
+ }
36173
+ });
36174
+
35616
36175
  // ../noodl-runtime/src/api/backends/RestAuthAdapter.ts
35617
36176
  function restAuthProfileFor(type) {
35618
36177
  return PROFILES[type];
@@ -35704,6 +36263,7 @@ var init_RestAuthAdapter = __esm({
35704
36263
  "../noodl-runtime/src/api/backends/RestAuthAdapter.ts"() {
35705
36264
  init_src();
35706
36265
  init_AuthEvents();
36266
+ init_ParseAuthAdapter();
35707
36267
  init_SessionStore();
35708
36268
  init_TokenLifecycle();
35709
36269
  DEFAULT_POCKETBASE_USER_COLLECTION = "users";
@@ -36269,12 +36829,20 @@ var init_RestAuthAdapter = __esm({
36269
36829
  { method, path: this.path(profile, profile.me), token, body: method === "POST" ? {} : void 0 },
36270
36830
  {
36271
36831
  ok: (body) => {
36832
+ if (sessionTokenOf(this.getCurrentUser(handle)) !== sessionTokenOf(stored)) {
36833
+ options.error(SESSION_MOVED_MESSAGE);
36834
+ return;
36835
+ }
36272
36836
  const session = this.readSession(profile, body, Object.assign({}, stored, { sessionToken: token })) ?? Object.assign({}, stored, this.readUser(profile, body), { sessionToken: token });
36273
36837
  this.setSession(handle, session);
36274
36838
  this.emitAuthEvent("sessionGained", session);
36275
36839
  options.success(session);
36276
36840
  },
36277
36841
  fail: (error) => {
36842
+ if (sessionTokenOf(this.getCurrentUser(handle)) !== sessionTokenOf(stored)) {
36843
+ options.error(SESSION_MOVED_MESSAGE);
36844
+ return;
36845
+ }
36278
36846
  if (error.status === 401 || error.status === 403) {
36279
36847
  this.sessionStore(handle).clear();
36280
36848
  this.emitAuthEvent("sessionLost", error.error);
@@ -38775,8 +39343,9 @@ function schemaFor(collectionName2) {
38775
39343
  try {
38776
39344
  collection = CloudStore._collections[collectionName2];
38777
39345
  } catch (e) {
38778
- return void 0;
39346
+ collection = void 0;
38779
39347
  }
39348
+ if (!collection) collection = hostCollection(collectionName2);
38780
39349
  if (!collection) return void 0;
38781
39350
  if (collection.schema) return { collection: collectionName2, properties: collection.schema.properties };
38782
39351
  if (Array.isArray(collection.columns)) {
@@ -38790,6 +39359,15 @@ function schemaFor(collectionName2) {
38790
39359
  }
38791
39360
  return void 0;
38792
39361
  }
39362
+ function hostCollection(collectionName2) {
39363
+ if (typeof _noodl_cloud_runtime_version === "undefined" || typeof _noodl_cloudservices === "undefined") return void 0;
39364
+ if (!_noodl_cloudservices || typeof _noodl_cloudservices.collections !== "function") return void 0;
39365
+ try {
39366
+ return (_noodl_cloudservices.collections() || []).find((c) => c && c.name === collectionName2);
39367
+ } catch (e) {
39368
+ return void 0;
39369
+ }
39370
+ }
38793
39371
  function backendType() {
38794
39372
  try {
38795
39373
  const handle = CloudStore.instance && CloudStore.instance._handle && CloudStore.instance._handle();
@@ -70138,7 +70716,7 @@ function syncDataAttributesOnElement(node) {
70138
70716
  node._appliedDataAttributes = Object.keys(want);
70139
70717
  }
70140
70718
  function claimDataAttributeInputs(node) {
70141
- const own = node.registerInputIfNeeded;
70719
+ const own2 = node.registerInputIfNeeded;
70142
70720
  node.registerInputIfNeeded = function(name2) {
70143
70721
  if (!this.hasInput(name2) && DATA_ATTRIBUTE_NAME.test(name2)) {
70144
70722
  this.registerInput(name2, {
@@ -70150,7 +70728,7 @@ function claimDataAttributeInputs(node) {
70150
70728
  });
70151
70729
  return;
70152
70730
  }
70153
- return own ? own.call(this, name2) : void 0;
70731
+ return own2 ? own2.call(this, name2) : void 0;
70154
70732
  };
70155
70733
  }
70156
70734
  function scrollNodeIntoView(node, align) {
@@ -71700,7 +72278,7 @@ var init_Checkbox2 = __esm({
71700
72278
  });
71701
72279
 
71702
72280
  // ../noodl-viewer-react/src/guid.ts
71703
- function guid() {
72281
+ function guid2() {
71704
72282
  function s4() {
71705
72283
  return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
71706
72284
  }
@@ -71709,7 +72287,7 @@ function guid() {
71709
72287
  var guid_default;
71710
72288
  var init_guid = __esm({
71711
72289
  "../noodl-viewer-react/src/guid.ts"() {
71712
- guid_default = guid;
72290
+ guid_default = guid2;
71713
72291
  }
71714
72292
  });
71715
72293
 
@@ -71929,6 +72507,17 @@ var init_checkbox = __esm({
71929
72507
  });
71930
72508
 
71931
72509
  // ../noodl-viewer-react/src/components/controls/Select/Select.tsx
72510
+ function domValueOf(item) {
72511
+ if (item.Value === void 0 || item.Value === null || typeof item.Value === "boolean") {
72512
+ const label = item.Label;
72513
+ return (label === void 0 || label === null || typeof label === "boolean" ? "" : String(label)).replace(/[ \t\n\f\r]+/g, " ").trim();
72514
+ }
72515
+ return String(item.Value);
72516
+ }
72517
+ function offersValue(items, value2) {
72518
+ if (!items || typeof value2 !== "string") return false;
72519
+ return items.some((item) => item !== null && item !== void 0 && domValueOf(item) === value2);
72520
+ }
71932
72521
  function Select(props) {
71933
72522
  const [value2, setValue] = (0, import_react6.useState)(props.value);
71934
72523
  (0, import_react6.useEffect)(() => {
@@ -71942,7 +72531,7 @@ function Select(props) {
71942
72531
  style2 = Object.assign({}, props.textStyle, style2);
71943
72532
  style2.color = props.noodlNode.context.styles.resolveColor(style2.color);
71944
72533
  }
71945
- const selectedIndex = !props.items || value2 === void 0 ? -1 : props.items.findIndex((i2) => i2.Value === value2);
72534
+ const selectedIndex = !props.items || value2 === void 0 ? -1 : props.items.findIndex((i2) => i2 !== null && i2 !== void 0 && domValueOf(i2) === value2);
71946
72535
  const { height, ...otherStyles } = style2;
71947
72536
  function _renderIcon() {
71948
72537
  if (props.iconSourceType === "image" && props.iconImageSource !== void 0)
@@ -72050,7 +72639,7 @@ function Select(props) {
72050
72639
  {
72051
72640
  ...inputProps,
72052
72641
  disabled: !props.enabled,
72053
- value: options.find((i2) => i2.props.value === value2) ? value2 : void 0,
72642
+ value: offersValue(props.items, value2) ? value2 : "",
72054
72643
  ...utils_default.controlEvents(props),
72055
72644
  onChange: (e) => {
72056
72645
  setValue(e.target.value);
@@ -72223,12 +72812,14 @@ var init_options = __esm({
72223
72812
  this.props.id = "input-" + guid_default();
72224
72813
  this.props.items = DEFAULT_ITEMS.map((item) => ({ ...item }));
72225
72814
  this.props.value = this._internal.value = DEFAULT_ITEMS[0].Value;
72815
+ this._internal.valueIsSeed = true;
72226
72816
  this.props.valueWrites = 0;
72227
72817
  this.props.valueChanged = (value2) => {
72228
72818
  const changed = this._internal.value !== value2;
72229
72819
  this.props.value = this._internal.value = value2;
72230
72820
  this.props.valueWrites++;
72231
72821
  if (changed) {
72822
+ this._internal.valueIsSeed = false;
72232
72823
  this.flagOutputDirty("value");
72233
72824
  this.sendSignalOnOutput("onChange");
72234
72825
  }
@@ -72281,6 +72872,16 @@ var init_options = __esm({
72281
72872
  if (newValue && typeof newValue.on === "function") newValue.on("change", this._itemsChanged);
72282
72873
  }
72283
72874
  this.props.items = optionItems(this._internal.items);
72875
+ if (this._internal.valueIsSeed) {
72876
+ const offered = this.props.items;
72877
+ const seed = this._internal.value;
72878
+ if (!Array.isArray(offered) || !offered.some((item) => item && item.Value === seed)) {
72879
+ this._internal.valueIsSeed = false;
72880
+ this.props.value = this._internal.value = "";
72881
+ this.props.valueWrites++;
72882
+ this.flagOutputDirty("value");
72883
+ }
72884
+ }
72284
72885
  this.forceUpdate();
72285
72886
  }
72286
72887
  },
@@ -72299,6 +72900,7 @@ var init_options = __esm({
72299
72900
  }
72300
72901
  const changed = value2 !== this._internal.value;
72301
72902
  this.props.value = this._internal.value = value2;
72903
+ this._internal.valueIsSeed = false;
72302
72904
  this.props.valueWrites++;
72303
72905
  if (changed) {
72304
72906
  this.forceUpdate();
@@ -78605,11 +79207,11 @@ var init_foreach = __esm({
78605
79207
  const internal2 = this._internal;
78606
79208
  const children = internal2.target.getChildren();
78607
79209
  let at = children.indexOf(this) + 1;
78608
- let own = 0;
79210
+ let own2 = 0;
78609
79211
  for (let i2 = at; i2 < children.length; i2++) {
78610
79212
  if (internal2.itemNodes.indexOf(children[i2]) === -1) continue;
78611
- if (own === listIndex) return i2;
78612
- own++;
79213
+ if (own2 === listIndex) return i2;
79214
+ own2++;
78613
79215
  at = i2 + 1;
78614
79216
  }
78615
79217
  return at;
@@ -90999,8 +91601,8 @@ var init_timer = __esm({
90999
91601
  const a = this._internal._animation;
91000
91602
  if (a._isRunning !== true || this._internal.paused) return;
91001
91603
  const left = a.durationLeft();
91002
- const own = this._internal.resumed || { duration: a.duration, delay: a.delay };
91003
- this._internal.paused = { left: typeof left === "number" && left >= 0 ? left : a.duration, ...own };
91604
+ const own2 = this._internal.resumed || { duration: a.duration, delay: a.delay };
91605
+ this._internal.paused = { left: typeof left === "number" && left >= 0 ? left : a.duration, ...own2 };
91004
91606
  a.stop();
91005
91607
  },
91006
91608
  _onPageShown: function() {
@@ -95191,6 +95793,7 @@ __export(cloudfunction2_exports, {
95191
95793
  default: () => cloudfunction2_default
95192
95794
  });
95193
95795
  function _makeRequest(path8, options) {
95796
+ options = holdOptionsForWrite(options);
95194
95797
  const xhr = new XMLHttpRequest();
95195
95798
  xhr.onreadystatechange = function() {
95196
95799
  if (xhr.readyState === 4) {
@@ -95235,6 +95838,7 @@ var init_cloudfunction2 = __esm({
95235
95838
  "../noodl-viewer-react/src/nodes/std-library/data/cloudfunction2.ts"() {
95236
95839
  import_runtime10 = __toESM(require_noodl_runtime());
95237
95840
  init_SessionStore();
95841
+ init_pressguard();
95238
95842
  init_outcome();
95239
95843
  CALL_ERROR_CODE = "cloud-function/call-failed";
95240
95844
  CloudFunctionNode = {
@@ -97873,453 +98477,6 @@ var init_router_navigate = __esm({
97873
98477
  }
97874
98478
  });
97875
98479
 
97876
- // ../noodl-runtime/src/api/backends/ParseAuthAdapter.ts
97877
- function pageSays(response, phrase) {
97878
- return typeof response === "string" && response.indexOf(phrase) !== -1;
97879
- }
97880
- function htmlPageError(error, invalidPhrase, invalidMessage, fallback) {
97881
- if (typeof error !== "string" || error.indexOf("<") === -1) return error || fallback;
97882
- return error.indexOf(invalidPhrase) !== -1 ? invalidMessage : fallback;
97883
- }
97884
- var guid2, HANDOFF_PARAM, AUTH_ERROR_PARAM, ParseAuthAdapter;
97885
- var init_ParseAuthAdapter = __esm({
97886
- "../noodl-runtime/src/api/backends/ParseAuthAdapter.ts"() {
97887
- init_AuthEvents();
97888
- init_SessionStore();
97889
- init_TokenLifecycle();
97890
- guid2 = require_guid();
97891
- HANDOFF_PARAM = "nodegx_auth";
97892
- AUTH_ERROR_PARAM = "nodegx_auth_error";
97893
- ParseAuthAdapter = class extends AuthEvents {
97894
- constructor(options) {
97895
- super();
97896
- /** BAK-004: the state of a sign-in returning from a provider or a magic link. */
97897
- this.oauthReturn = { inProgress: false };
97898
- /**
97899
- * One store and one controller per backend, keyed by application id.
97900
- *
97901
- * Per handle rather than per adapter because the contract's whole point is
97902
- * that a project may have more than one backend, and two backends have two
97903
- * sessions — the phase decided explicitly that they are never shared.
97904
- */
97905
- this.stores = /* @__PURE__ */ new Map();
97906
- this.controllers = /* @__PURE__ */ new Map();
97907
- this.serializeObject = options.serializeObject;
97908
- this.createStore = options.createStore || parseSessionStore;
97909
- this.lifecycle = options.lifecycle || { kind: "eternal" };
97910
- }
97911
- // ── Session storage ──────────────────────────────────────────────────────
97912
- sessionStore(handle) {
97913
- const appId = handle.publicToken;
97914
- const cacheKey = String(appId);
97915
- let store = this.stores.get(cacheKey);
97916
- if (store === void 0) {
97917
- store = this.createStore(appId);
97918
- this.stores.set(cacheKey, store);
97919
- }
97920
- return store;
97921
- }
97922
- /**
97923
- * The lifecycle controller for this backend.
97924
- *
97925
- * On Parse this is an `eternal` controller: it arms no timer, subscribes to no
97926
- * storage events, and its gate is a synchronous pass-through. It is on the
97927
- * request path anyway, so that BCN-004 adds a `performRefresh` rather than a
97928
- * call site.
97929
- */
97930
- lifecycleController(handle) {
97931
- const cacheKey = String(handle.publicToken);
97932
- let controller = this.controllers.get(cacheKey);
97933
- if (controller === void 0) {
97934
- controller = new TokenLifecycleController({
97935
- lifecycle: this.lifecycle,
97936
- store: this.sessionStore(handle),
97937
- onSessionLost: (reason) => {
97938
- this.emitAuthEvent("sessionChanged", void 0);
97939
- this.emitAuthEvent("sessionLost", reason);
97940
- },
97941
- onSessionClearedElsewhere: () => {
97942
- this.emitAuthEvent("sessionChanged", void 0);
97943
- this.emitAuthEvent("loggedOut");
97944
- }
97945
- });
97946
- controller.start();
97947
- this.controllers.set(cacheKey, controller);
97948
- }
97949
- return controller;
97950
- }
97951
- /** The stored session, or `undefined`. Not a contract method. */
97952
- getCurrentUser(handle) {
97953
- return this.sessionStore(handle).read();
97954
- }
97955
- setSession(handle, session) {
97956
- this.sessionStore(handle).write(session);
97957
- this.lifecycleController(handle).sessionChanged();
97958
- this.emitAuthEvent("sessionChanged", session);
97959
- }
97960
- clearSession(handle) {
97961
- this.sessionStore(handle).clear();
97962
- this.lifecycleController(handle).sessionChanged();
97963
- this.emitAuthEvent("sessionChanged", void 0);
97964
- }
97965
- // ── The wire ─────────────────────────────────────────────────────────────
97966
- /**
97967
- * The Parse auth seam. Moved from `userservice.ts:198` with one substitution:
97968
- * `this.endpoint`/`this.appId` became `handle.url`/`handle.publicToken`.
97969
- *
97970
- * It is deliberately *not* `ParseWireAdapter._makeRequest`. The two differ in
97971
- * three ways that all matter: this one sends `X-Parse-Installation-Id`, never
97972
- * sends a master key, and hands its error callback Parse's `code` (the `209`
97973
- * the session check turns on). Merging them would be a rewrite in a task
97974
- * whose value is that nothing was rewritten.
97975
- */
97976
- _makeRequest(handle, path8, options) {
97977
- if (!handle.url) {
97978
- if (options.error) {
97979
- options.error({ error: "No active cloud service", status: 0 });
97980
- }
97981
- return;
97982
- }
97983
- const store = this.sessionStore(handle);
97984
- this.lifecycleController(handle).withSession((session) => {
97985
- const xhr = new XMLHttpRequest();
97986
- xhr.onreadystatechange = function() {
97987
- if (xhr.readyState === 4) {
97988
- let json;
97989
- try {
97990
- json = JSON.parse(xhr.response);
97991
- } catch (e) {
97992
- }
97993
- if (xhr.status === 200 || xhr.status === 201) {
97994
- options.success(json || xhr.response);
97995
- } else options.error(json || { error: xhr.responseText, status: xhr.status });
97996
- }
97997
- };
97998
- xhr.open(options.method || "GET", handle.url + path8, true);
97999
- xhr.setRequestHeader("X-Parse-Application-Id", handle.publicToken);
98000
- const iidKey = parseInstallationIdKey(handle.publicToken);
98001
- let _iid = store.readKey(iidKey);
98002
- if (_iid === void 0) {
98003
- _iid = guid2();
98004
- store.writeKey(iidKey, _iid);
98005
- }
98006
- xhr.setRequestHeader("X-Parse-Installation-Id", _iid);
98007
- if (options.sessionToken) xhr.setRequestHeader("X-Parse-Session-Token", options.sessionToken);
98008
- else if (session !== void 0) {
98009
- xhr.setRequestHeader("X-Parse-Session-Token", session.sessionToken);
98010
- }
98011
- xhr.setRequestHeader("Content-Type", "application/json");
98012
- xhr.send(JSON.stringify(options.content));
98013
- });
98014
- }
98015
- // ── The ten ──────────────────────────────────────────────────────────────
98016
- logIn(handle, options) {
98017
- this._makeRequest(handle, "/login", {
98018
- method: "POST",
98019
- content: {
98020
- username: options.username,
98021
- password: options.password,
98022
- _method: "GET"
98023
- },
98024
- success: (response) => {
98025
- this.setSession(handle, response);
98026
- options.success(response);
98027
- this.emitAuthEvent("loggedIn", response);
98028
- },
98029
- error: (e) => {
98030
- options.error(e.error);
98031
- }
98032
- });
98033
- }
98034
- logOut(handle, options) {
98035
- this._makeRequest(handle, "/logout", {
98036
- method: "POST",
98037
- content: {},
98038
- success: () => {
98039
- this.clearSession(handle);
98040
- options.success();
98041
- this.emitAuthEvent("loggedOut");
98042
- },
98043
- error: (e) => {
98044
- options.error(e.error);
98045
- }
98046
- });
98047
- }
98048
- signUp(handle, options) {
98049
- const additionalUserProps = options.properties ? this.serializeObject({ ...options.properties }, "_User") : {};
98050
- this._makeRequest(handle, "/users", {
98051
- method: "POST",
98052
- content: Object.assign({}, additionalUserProps, {
98053
- username: options.username,
98054
- password: options.password,
98055
- email: options.email
98056
- }),
98057
- success: (response) => {
98058
- const identity2 = { username: options.username };
98059
- if (options.email !== void 0) identity2.email = options.email;
98060
- const _cu = Object.assign(response, identity2, options.properties);
98061
- if (_cu.emailVerified === void 0) _cu.emailVerified = false;
98062
- this.setSession(handle, _cu);
98063
- options.success(response);
98064
- this.emitAuthEvent("loggedIn", _cu);
98065
- },
98066
- error: (e) => {
98067
- options.error(e.error);
98068
- }
98069
- });
98070
- }
98071
- fetchCurrentUser(handle, options) {
98072
- this._makeRequest(handle, "/users/me", {
98073
- method: "GET",
98074
- sessionToken: options.sessionToken,
98075
- success: (response) => {
98076
- this.setSession(handle, response);
98077
- this.emitAuthEvent("sessionGained", response);
98078
- options.success(response);
98079
- },
98080
- error: (e) => {
98081
- if (e.code === 209) {
98082
- this.sessionStore(handle).clear();
98083
- this.emitAuthEvent("sessionLost");
98084
- }
98085
- options.error(e.error);
98086
- }
98087
- });
98088
- }
98089
- verifyEmail(handle, options) {
98090
- this._makeRequest(
98091
- handle,
98092
- "/apps/" + handle.publicToken + "/verify_email?username=" + options.username + "&token=" + options.token,
98093
- {
98094
- method: "GET",
98095
- // This endpoint answers with an HTML page rather than JSON, so the outcome has to be
98096
- // read out of the page's text.
98097
- success: (response) => {
98098
- if (pageSays(response, "Successfully verified your email")) {
98099
- options.success();
98100
- } else if (pageSays(response, "Invalid Verification Link")) {
98101
- options.error("Invalid verification token");
98102
- } else {
98103
- options.error("Failed to verify email");
98104
- }
98105
- },
98106
- error: (e) => {
98107
- options.error(htmlPageError(e.error, "Invalid Verification Link", "Invalid verification token", "Failed to verify email"));
98108
- }
98109
- }
98110
- );
98111
- }
98112
- sendEmailVerification(handle, options) {
98113
- this._makeRequest(handle, "/verificationEmailRequest", {
98114
- method: "POST",
98115
- content: { email: options.email },
98116
- success: () => {
98117
- options.success();
98118
- },
98119
- error: (e) => {
98120
- options.error(e.error);
98121
- }
98122
- });
98123
- }
98124
- resetPassword(handle, options) {
98125
- this._makeRequest(handle, "/apps/" + handle.publicToken + "/request_password_reset", {
98126
- method: "POST",
98127
- content: {
98128
- username: options.username,
98129
- token: options.token,
98130
- new_password: options.newPassword
98131
- },
98132
- success: (response) => {
98133
- if (pageSays(response, "Password successfully reset") || pageSays(response, "Successfully updated your password")) {
98134
- options.success();
98135
- } else if (pageSays(response, "Invalid Link")) {
98136
- options.error("Invalid or expired reset link");
98137
- } else {
98138
- options.error("Failed to reset password");
98139
- }
98140
- },
98141
- error: (e) => {
98142
- options.error(htmlPageError(e.error, "Invalid Link", "Invalid or expired reset link", "Failed to reset password"));
98143
- }
98144
- });
98145
- }
98146
- requestPasswordReset(handle, options) {
98147
- this._makeRequest(handle, "/requestPasswordReset", {
98148
- method: "POST",
98149
- content: { email: options.email },
98150
- success: () => {
98151
- options.success();
98152
- },
98153
- error: (e) => {
98154
- options.error(e.error);
98155
- }
98156
- });
98157
- }
98158
- /**
98159
- * Send the browser to a provider. This NAVIGATES AWAY: nothing after it runs,
98160
- * and the result arrives on a later page load through {@link consumeAuthReturn}.
98161
- *
98162
- * `redirect` defaults to the current page (minus any leftover sign-in
98163
- * parameters), so the user comes back where they were. The backend refuses
98164
- * any target that is neither same-origin nor on its `redirectAllowList` — if
98165
- * sign-in dead-ends with "origin not allowed", that list is the fix.
98166
- */
98167
- signInWithProvider(handle, options) {
98168
- if (!handle.url) {
98169
- if (options.error) options.error("No active cloud service");
98170
- return;
98171
- }
98172
- if (!options.provider) {
98173
- if (options.error) options.error("Sign In With: no provider was set.");
98174
- return;
98175
- }
98176
- const redirect = options.redirect || this._currentUrlWithoutAuthParams();
98177
- const url = `${handle.url}/oauth/${encodeURIComponent(options.provider)}/start?redirect=${encodeURIComponent(redirect)}`;
98178
- window.location.href = url;
98179
- }
98180
- /**
98181
- * Ask the backend to email a one-click sign-in link.
98182
- *
98183
- * Succeeds identically for a known and an unknown address — the endpoint is
98184
- * anonymous and anti-enumerating by design, so "success" here means "the
98185
- * request was accepted", never "an account exists". The node's help text says
98186
- * so, because a UI that claims "check your inbox" only for real accounts
98187
- * re-creates the oracle the endpoint removed.
98188
- */
98189
- requestMagicLink(handle, options) {
98190
- this._makeRequest(handle, "/auth/magic-link", {
98191
- method: "POST",
98192
- content: { email: options.email, redirect: options.redirect || this._currentUrlWithoutAuthParams() },
98193
- success: () => options.success(),
98194
- error: (e) => options.error(e.error)
98195
- });
98196
- }
98197
- // ── Not contract, and deliberately so ────────────────────────────────────
98198
- /**
98199
- * Write user-profile fields onto the signed-in user.
98200
- *
98201
- * Not one of the ten. It is the `Set User Properties` node's whole
98202
- * implementation, and BCN-006's Desired State §3 makes it schema-driven across
98203
- * five backends — which is step 6, not this pass. It moves with the wire
98204
- * because leaving it behind would have left a second `_makeRequest` in
98205
- * `userservice.ts`, and the entire claim of this move is that there is one.
98206
- */
98207
- setUserProperties(handle, options) {
98208
- const _cu = this.getCurrentUser(handle);
98209
- if (_cu === void 0) {
98210
- options.error("Nobody is signed in.");
98211
- return;
98212
- }
98213
- {
98214
- const propsToSave = this.serializeObject({ ...options.properties }, "_User");
98215
- const _content = Object.assign(
98216
- {},
98217
- { email: options.email, username: options.username },
98218
- propsToSave
98219
- );
98220
- delete _content.emailVerified;
98221
- delete _content.createdAt;
98222
- delete _content.updatedAt;
98223
- this._makeRequest(handle, "/users/" + _cu.objectId, {
98224
- method: "PUT",
98225
- content: _content,
98226
- success: (response) => {
98227
- Object.assign(_cu, _content);
98228
- this.setSession(handle, _cu);
98229
- options.success(response);
98230
- },
98231
- error: (e) => {
98232
- options.error(e.error);
98233
- }
98234
- });
98235
- }
98236
- }
98237
- /** The sign-in methods this backend offers — for rendering a set of buttons. */
98238
- listAuthProviders(handle, options) {
98239
- this._makeRequest(handle, "/auth/providers", {
98240
- method: "GET",
98241
- success: (response) => options.success(response),
98242
- error: (e) => options.error(e.error)
98243
- });
98244
- }
98245
- // ── BAK-004 — the provider return leg ────────────────────────────────────
98246
- /**
98247
- * Look for a sign-in result in the current URL and act on it.
98248
- *
98249
- * Returns true when this page load IS a sign-in return, so the caller can skip
98250
- * its ordinary stale-session validation.
98251
- *
98252
- * **Per backend, not contract.** BCN-006 step 5 is four different redirect
98253
- * shapes — Parse `authData`, Directus SSO, Supabase, PocketBase
98254
- * `auth-with-oauth2` — and this is the first of the four. What they have in
98255
- * common is only the *outcome*: a session, established. That is where the
98256
- * contract's surface is, and it is `signInWithProvider` plus a `loggedIn`.
98257
- */
98258
- consumeAuthReturn(handle) {
98259
- if (typeof window === "undefined" || !window.location) return false;
98260
- let params;
98261
- try {
98262
- params = new URLSearchParams(window.location.search);
98263
- } catch (e) {
98264
- return false;
98265
- }
98266
- const code = params.get(HANDOFF_PARAM);
98267
- const error = params.get(AUTH_ERROR_PARAM);
98268
- if (!code && !error) return false;
98269
- params.delete(HANDOFF_PARAM);
98270
- params.delete(AUTH_ERROR_PARAM);
98271
- this._stripAuthParamsFromUrl(params);
98272
- if (error) {
98273
- this.oauthReturn = { inProgress: false, succeeded: false, error };
98274
- setTimeout(() => this.emitAuthEvent("oauthReturn", this.oauthReturn), 0);
98275
- return true;
98276
- }
98277
- this.oauthReturn = { inProgress: true };
98278
- this._makeRequest(handle, "/oauth/exchange", {
98279
- method: "POST",
98280
- content: { code },
98281
- success: (response) => {
98282
- this.setSession(handle, response);
98283
- this.oauthReturn = {
98284
- inProgress: false,
98285
- succeeded: true,
98286
- outcome: response.authOutcome,
98287
- notice: response.authNotice || void 0
98288
- };
98289
- this.emitAuthEvent("oauthReturn", this.oauthReturn);
98290
- this.emitAuthEvent("loggedIn", response);
98291
- },
98292
- error: (e) => {
98293
- this.oauthReturn = {
98294
- inProgress: false,
98295
- succeeded: false,
98296
- error: e && e.error || "Sign-in could not be completed."
98297
- };
98298
- this.emitAuthEvent("oauthReturn", this.oauthReturn);
98299
- }
98300
- });
98301
- return true;
98302
- }
98303
- /** Rewrite the address bar without the sign-in parameters, keeping everything else. */
98304
- _stripAuthParamsFromUrl(remaining) {
98305
- if (!window.history || typeof window.history.replaceState !== "function") return;
98306
- const query = remaining.toString();
98307
- const cleaned = window.location.pathname + (query ? `?${query}` : "") + window.location.hash;
98308
- try {
98309
- window.history.replaceState(window.history.state, "", cleaned);
98310
- } catch (e) {
98311
- }
98312
- }
98313
- _currentUrlWithoutAuthParams() {
98314
- const url = new URL(window.location.href);
98315
- url.searchParams.delete(HANDOFF_PARAM);
98316
- url.searchParams.delete(AUTH_ERROR_PARAM);
98317
- return url.toString();
98318
- }
98319
- };
98320
- }
98321
- });
98322
-
98323
98480
  // ../noodl-viewer-react/src/nodes/std-library/user/userservice.ts
98324
98481
  var import_events2, import_runtime14, import_cloudstore2, UserService, _instance, userservice_default;
98325
98482
  var init_userservice = __esm({
@@ -98865,44 +99022,20 @@ var signup_exports = {};
98865
99022
  __export(signup_exports, {
98866
99023
  default: () => signup_default
98867
99024
  });
98868
- function updatePorts6(nodeId, _parameters, editorConnection, systemCollections) {
98869
- const ports = [];
98870
- if (systemCollections) {
98871
- const c = systemCollections.find((c2) => c2.name === "_User");
98872
- if (c && c.schema && c.schema.properties) {
98873
- const props = c.schema.properties;
98874
- const _ignoreKeys = ["authData", "password", "username", "createdAt", "updatedAt", "emailVerified", "email", "ACL"];
98875
- for (const key in props) {
98876
- if (_ignoreKeys.indexOf(key) !== -1) continue;
98877
- const p = props[key];
98878
- if (ports.find((_p) => _p.name === key)) continue;
98879
- if (p.type === "Relation") {
98880
- } else {
98881
- const _typeMap = {
98882
- String: "string",
98883
- Boolean: "boolean",
98884
- Number: "number",
98885
- Date: "date"
98886
- };
98887
- ports.push({
98888
- type: {
98889
- name: _typeMap[p.type] ? _typeMap[p.type] : "*"
98890
- },
98891
- plug: "input",
98892
- group: "Properties",
98893
- name: "prop-" + key,
98894
- displayName: key
98895
- });
98896
- }
98897
- }
98898
- }
98899
- }
98900
- editorConnection.sendDynamicPorts(nodeId, ports);
99025
+ function updatePorts6(nodeId, editorConnection, graphModel) {
99026
+ const ctx = userSchemaContext(graphModel, {});
99027
+ const ignore = isParseWireContext(ctx) ? USER_INPUT_IGNORE_PARSE_BROWSER : USER_INPUT_IGNORE_REST;
99028
+ sendSchemaPorts(editorConnection, nodeId, userPropertyPorts(ctx, { plug: "input", ignore }), {
99029
+ staticPorts: staticPortNames(SignUpNodeDefinition)
99030
+ });
98901
99031
  }
98902
99032
  var SIGN_UP_ERROR_CODE, SignUpNodeDefinition, SignUpModule, signup_default;
98903
99033
  var init_signup = __esm({
98904
99034
  "../noodl-viewer-react/src/nodes/std-library/user/signup.ts"() {
98905
99035
  "use strict";
99036
+ init_record_ports();
99037
+ init_schema_ports();
99038
+ init_user_ports();
98906
99039
  init_outcome();
98907
99040
  init_userservice();
98908
99041
  SIGN_UP_ERROR_CODE = "user/sign-up-failed";
@@ -99040,23 +99173,11 @@ var init_signup = __esm({
99040
99173
  return;
99041
99174
  }
99042
99175
  function _managePortsForNode(node) {
99043
- updatePorts6(
99044
- node.id,
99045
- node.parameters,
99046
- editorConnection,
99047
- graphModel.getMetaData("systemCollections")
99048
- );
99049
- node.on("parameterUpdated", function() {
99050
- updatePorts6(
99051
- node.id,
99052
- node.parameters,
99053
- editorConnection,
99054
- graphModel.getMetaData("systemCollections")
99055
- );
99056
- });
99057
- graphModel.on("metadataChanged.systemCollections", function(data) {
99058
- updatePorts6(node.id, node.parameters, editorConnection, data);
99059
- });
99176
+ const rebuild2 = () => updatePorts6(node.id, editorConnection, graphModel);
99177
+ rebuild2();
99178
+ graphModel.on("metadataChanged.systemCollections", rebuild2);
99179
+ graphModel.on("metadataChanged.dbCollections", rebuild2);
99180
+ graphModel.on("metadataChanged.backendServices", rebuild2);
99060
99181
  }
99061
99182
  graphModel.on("editorImportComplete", () => {
99062
99183
  graphModel.on("nodeAdded.net.noodl.user.SignUp", function(node) {
@@ -100237,6 +100358,7 @@ __export(cloudfunction_exports, {
100237
100358
  default: () => cloudfunction_default
100238
100359
  });
100239
100360
  function _makeRequest2(path8, options) {
100361
+ options = holdOptionsForWrite(options);
100240
100362
  const xhr = new XMLHttpRequest();
100241
100363
  xhr.onreadystatechange = function() {
100242
100364
  if (xhr.readyState === 4) {
@@ -100265,6 +100387,7 @@ var init_cloudfunction = __esm({
100265
100387
  import_runtime15 = __toESM(require_noodl_runtime());
100266
100388
  import_cloudstore3 = __toESM(require_cloudstore());
100267
100389
  init_SessionStore();
100390
+ init_pressguard();
100268
100391
  CloudFunctionNode2 = {
100269
100392
  name: "Cloud Function",
100270
100393
  category: "Cloud Services",
@@ -112336,7 +112459,7 @@ var require_package = __commonJS({
112336
112459
  "package.json"(exports2, module2) {
112337
112460
  module2.exports = {
112338
112461
  name: "@nodegx/preview",
112339
- version: "0.2.5",
112462
+ version: "0.2.6",
112340
112463
  description: "Standalone live-preview harness: renders a NodeGX (OpenNoodl) v2 project in a browser with no editor process, and reloads it when the files on disk change.",
112341
112464
  license: "GPL-3.0-only",
112342
112465
  homepage: "https://nodegx.io",
@@ -167046,11 +167169,12 @@ var node_catalog_default = {
167046
167169
  ],
167047
167170
  items: [
167048
167171
  "nothing handed (undefined): the list it has stays",
167172
+ "the value is still the placed node's seed and the list does not offer it: the list is kept, and the value is cleared to '' and sent",
167049
167173
  "otherwise: the list is kept as handed and drawn at the next paint; the value stays, so a list handed again keeps the pick"
167050
167174
  ],
167051
167175
  value: [
167052
167176
  "null: ignored",
167053
- "the value it already holds, as text: nothing",
167177
+ "the value it already holds, as text: nothing (it is no longer the seed)",
167054
167178
  "a change: kept as text, Value follows, and the list selects it at the next paint if it names an option \u2014 Changed does not fire (only a person's pick fires it)"
167055
167179
  ],
167056
167180
  mounted: [
@@ -179431,7 +179555,7 @@ var node_catalog_default = {
179431
179555
  group: "Request",
179432
179556
  plug: "output",
179433
179557
  type: {
179434
- name: "boolean"
179558
+ name: "string"
179435
179559
  },
179436
179560
  isSignal: false,
179437
179561
  description: "Id of the user the session token resolved to, and blank for an unauthenticated request"
@@ -180657,8 +180781,8 @@ var CatalogIndex = class _CatalogIndex {
180657
180781
  suggestPort(typeName2, plug, unknown, kind) {
180658
180782
  const names = this.portNames(typeName2, plug);
180659
180783
  const candidates = kind ? names.filter((name2) => {
180660
- const own = this.portKind(typeName2, plug, name2);
180661
- return own === void 0 || own === kind;
180784
+ const own2 = this.portKind(typeName2, plug, name2);
180785
+ return own2 === void 0 || own2 === kind;
180662
180786
  }) : names;
180663
180787
  return nearest(unknown, candidates);
180664
180788
  }
@@ -191036,7 +191160,15 @@ function projectModelFromLegacy(legacy) {
191036
191160
  var fs3 = __toESM(require("fs"));
191037
191161
  var path4 = __toESM(require("path"));
191038
191162
  init_scriptfiles();
191039
- var readJson = (file) => JSON.parse(fs3.readFileSync(file, "utf8"));
191163
+ var readJson = (file) => {
191164
+ const text2 = fs3.readFileSync(file, "utf8");
191165
+ try {
191166
+ return JSON.parse(text2);
191167
+ } catch (e) {
191168
+ const why = text2.trim() === "" ? "the file is empty, so it is not valid JSON" : e instanceof Error ? e.message : String(e);
191169
+ throw Object.assign(new Error(`${file.split(path4.sep).join("/")}: ${why}`), { projectFile: file });
191170
+ }
191171
+ };
191040
191172
  function resolveTarget(target) {
191041
191173
  const abs = path4.resolve(target);
191042
191174
  if (!fs3.existsSync(abs)) throw new Error(`No such file or directory: ${abs}`);
@@ -191179,6 +191311,9 @@ function flatten3(roots) {
191179
191311
  for (const root2 of roots) visit(root2, void 0);
191180
191312
  return out;
191181
191313
  }
191314
+ function recordedVisualRoots(list) {
191315
+ return Array.isArray(list) && list.length > 0 ? { visualRoots: list.filter((id) => typeof id === "string") } : {};
191316
+ }
191182
191317
  function fromLegacyProject(project) {
191183
191318
  const components = [];
191184
191319
  for (const comp of project.components ?? []) {
@@ -191192,7 +191327,8 @@ function fromLegacyProject(project) {
191192
191327
  components.push({
191193
191328
  name: comp.name,
191194
191329
  nodes: flatten3(graph.roots ?? []),
191195
- connections
191330
+ connections,
191331
+ ...recordedVisualRoots(graph.visualRoots)
191196
191332
  });
191197
191333
  }
191198
191334
  return {
@@ -192036,6 +192172,7 @@ function cloudPortMeaning(type, plug) {
192036
192172
 
192037
192173
  // ../noodl-editor/src/editor/src/validation/functionScriptUses.ts
192038
192174
  init_scriptfiles();
192175
+ var FUNCTION_NODE_TYPE = "JavaScriptFunction";
192039
192176
 
192040
192177
  // ../noodl-editor/src/editor/src/validation/functionPorts.ts
192041
192178
  var SCRIPT_NODE_API_MEMBERS = [
@@ -192054,6 +192191,10 @@ var SCRIPT_DECLARATION = new RegExp(
192054
192191
  )
192055
192192
  );
192056
192193
 
192194
+ // ../noodl-editor/src/editor/src/validation/publicWriteDoor.ts
192195
+ var RECORD_WRITE_SCRIPT_MEMBERS = ["create", "save", "delete", "increment", "addRelation", "removeRelation"];
192196
+ var SCRIPT_WRITE_CALL = new RegExp(`\\bRecords\\s*\\.\\s*(${RECORD_WRITE_SCRIPT_MEMBERS.join("|")})\\s*\\(`);
192197
+
192057
192198
  // ../noodl-editor/src/editor/src/validation/typedWordNotTranslated.ts
192058
192199
  var HAS_LETTER = new RegExp("\\p{L}", "u");
192059
192200
 
@@ -192250,7 +192391,11 @@ var AUTHORED_BLOCKING_WARNINGS = /* @__PURE__ */ new Set([
192250
192391
  // and **no `completed` wires at all**. `completed` is a rare port — authors
192251
192392
  // reach for `done` — so the blast radius of making this blocking is small, and
192252
192393
  // that is a measurement rather than a hope.
192253
- "completed-commits-unchecked" /* CompletedCommitsUnchecked */
192394
+ "completed-commits-unchecked" /* CompletedCommitsUnchecked */,
192395
+ // Bug P121-TWO003-SCRIPTCOMPILE — the runtime's own compile fails, so every Run fails. No benign
192396
+ // reading for a graph an agent just wrote; the write said nothing before, and the first person
192397
+ // to run the function got "That could not be done."
192398
+ "function-script-not-compiled" /* FunctionScriptNotCompiled */
192254
192399
  ]);
192255
192400
 
192256
192401
  // ../noodl-editor/src/editor/src/validation/diagnosticExamples.ts
@@ -192379,6 +192524,17 @@ var DIAGNOSTIC_EXAMPLES = [
192379
192524
  code: "function-trusts-a-field-the-caller-wrote" /* FunctionTrustsAFieldTheCallerWrote */,
192380
192525
  examples: ["cloud-share-my-records-with-someone"],
192381
192526
  why: "a cloud function that returns the records of the shares' owners, never a field the page wrote"
192527
+ },
192528
+ // P112 OWN-026 FIX-06 B — a group joined only through its owner's invitation, written only by a function.
192529
+ {
192530
+ code: "role-named-by-a-field-the-caller-wrote" /* RoleNamedByAFieldTheCallerWrote */,
192531
+ examples: ["cloud-members-can-edit-my-records"],
192532
+ why: "a cloud function that names the role from the invitation's owner, so an invitation only lets someone into its creator's group"
192533
+ },
192534
+ {
192535
+ code: "function-guards-an-open-collection" /* FunctionGuardsAnOpenCollection */,
192536
+ examples: ["cloud-members-can-edit-my-records"],
192537
+ why: `the records only a function creates, with the collection's create rule "nobody": the function runs as the system`
192382
192538
  }
192383
192539
  ];
192384
192540
 
@@ -192569,6 +192725,9 @@ var unknownNodeType = {
192569
192725
  }
192570
192726
  };
192571
192727
 
192728
+ // ../noodl-editor/src/editor/src/validation/rules/nonexistentPort.ts
192729
+ init_renamed_ports();
192730
+
192572
192731
  // ../noodl-editor/src/editor/src/views/panels/propertyeditor/propertyPanelTiers.ts
192573
192732
  var ADVANCED_CSS_GROUPS = {
192574
192733
  // ── plumbing ────────────────────────────────────────────────────────────────
@@ -192647,18 +192806,23 @@ function otherEndKind(catalog, nodeById, conn, plug) {
192647
192806
  }
192648
192807
  return portKindOfTypeName(other.instancePortTypes?.[port]);
192649
192808
  }
192809
+ function renamedPort(type, port, plug) {
192810
+ const to = plug === "output" ? renamedOutput(type, port) : void 0;
192811
+ return to ? { to, sentence: ` It was renamed "${to}" in August 2026: wire "${to}".` } : void 0;
192812
+ }
192650
192813
  function missingDrawnPort(catalog, nodeById, conn, component, node, port, plug, drawn) {
192651
192814
  const names = [...drawn];
192652
192815
  const tail = (name2) => name2.slice(name2.indexOf("-") + 1).toLowerCase();
192653
192816
  const suffixed = names.filter((name2) => tail(name2) === port.toLowerCase() || tail(name2) === tail(port));
192654
- const suggestion = suffixed.length > 0 ? nearest(port, suffixed) ?? suffixed[0] : nearest(port, names);
192817
+ const renamed = renamedPort(node.type, port, plug);
192818
+ const suggestion = renamed && drawn.has(renamed.to) ? renamed.to : suffixed.length > 0 ? nearest(port, suffixed) ?? suffixed[0] : nearest(port, names);
192655
192819
  const wireKind = otherEndKind(catalog, nodeById, conn, plug);
192656
192820
  const fromProject = names.filter((name2) => !catalog.hasPort(node.type, plug, name2)).sort();
192657
192821
  const alternatives = [.../* @__PURE__ */ new Set([...fromProject, ...availableAlternatives(catalog, node.type, plug, wireKind).filter((name2) => drawn.has(name2))])].slice(0, MAX_ALTERNATIVES3);
192658
192822
  return {
192659
192823
  code: "nonexistent-port" /* NonexistentPort */,
192660
192824
  severity: "error",
192661
- message: `${node.type} has no ${plug} named "${port}" with its settings in this project.`,
192825
+ message: `${node.type} has no ${plug} named "${port}" with its settings in this project.` + (renamed && drawn.has(renamed.to) ? renamed.sentence : ""),
192662
192826
  location: {
192663
192827
  component,
192664
192828
  nodeId: node.id,
@@ -192738,12 +192902,13 @@ var nonexistentPort = {
192738
192902
  }
192739
192903
  const wireKind = otherEndKind(catalog, nodeById, conn, plug);
192740
192904
  const prefixed = missingPrefix ? missingPrefix + port : void 0;
192741
- const suggestion = catalog.suggestPort(node.type, plug, port, wireKind) ?? prefixed;
192905
+ const renamed = renamedPort(node.type, port, plug);
192906
+ const suggestion = renamed?.to ?? catalog.suggestPort(node.type, plug, port, wireKind) ?? prefixed;
192742
192907
  const alternatives = availableAlternatives(catalog, node.type, plug, wireKind);
192743
192908
  out.push({
192744
192909
  code: "nonexistent-port" /* NonexistentPort */,
192745
192910
  severity: "error",
192746
- message: `${node.type} has no ${plug} named "${port}".` + (prefixed ? ` Its other ${plug}s exist only as "${missingPrefix}<name>", so a name without the prefix is never made and nothing goes through this wire. If "${port}" is ${cloudPortMeaning(node.type, plug)}, wire "${prefixed}".` : ""),
192911
+ message: `${node.type} has no ${plug} named "${port}".` + (renamed?.sentence ?? "") + (prefixed ? ` Its other ${plug}s exist only as "${missingPrefix}<name>", so a name without the prefix is never made and nothing goes through this wire. If "${port}" is ${cloudPortMeaning(node.type, plug)}, wire "${prefixed}".` : ""),
192747
192912
  location: {
192748
192913
  component: component.name,
192749
192914
  nodeId: node.id,
@@ -192946,6 +193111,7 @@ var detachedPageContent = {
192946
193111
  };
192947
193112
 
192948
193113
  // ../noodl-editor/src/editor/src/validation/rules/invalidHierarchy.ts
193114
+ var RECORDED_VISUAL_ROOT = { allowAsChild: true, category: "Visual" };
192949
193115
  function hierarchyTyper(ctx) {
192950
193116
  const byName = /* @__PURE__ */ new Map();
192951
193117
  for (const c of ctx.project.components) {
@@ -192965,8 +193131,9 @@ function hierarchyTyper(ctx) {
192965
193131
  const childIds = new Set(component.nodes.flatMap((n) => n.children));
192966
193132
  const roots = component.nodes.filter((n) => n.parent === void 0 && !childIds.has(n.id));
192967
193133
  const known = (n) => typeOf(n.type);
193134
+ const rootType = (n) => known(n) ?? (component.visualRoots?.includes(n.id) ? RECORDED_VISUAL_ROOT : void 0);
192968
193135
  const derived = componentHierarchyType(
192969
- roots.map(known).filter((t) => !!t),
193136
+ roots.map(rootType).filter((t) => !!t),
192970
193137
  component.nodes.map(known).filter((t) => !!t)
192971
193138
  );
192972
193139
  inFlight.delete(typeName2);
@@ -193837,6 +194004,123 @@ var settingsDiagnostic = {
193837
194004
  }
193838
194005
  };
193839
194006
 
194007
+ // ../noodl-editor/src/editor/src/validation/rules/functionScriptNotCompiled.ts
194008
+ var CODE_PREFIX = "const Script = (typeof Node !== 'undefined')?Node:undefined;\n";
194009
+ var PARAMS2 = ["Inputs", "Outputs", "Noodl", "Component", "Scripts"];
194010
+ function functionScriptCompileError(script) {
194011
+ try {
194012
+ const AsyncFunction = Object.getPrototypeOf(async function() {
194013
+ }).constructor;
194014
+ new AsyncFunction(...PARAMS2, CODE_PREFIX + script);
194015
+ return void 0;
194016
+ } catch (e) {
194017
+ const message = e instanceof Error ? e.message : String(e);
194018
+ const line = syntaxErrorLine(script);
194019
+ return line === void 0 ? { message } : { message, line };
194020
+ }
194021
+ }
194022
+ function syntaxErrorLine(script) {
194023
+ const file = "nodegx-function-script";
194024
+ try {
194025
+ const vm = typeof require === "function" ? require("vm") : void 0;
194026
+ if (!vm?.Script) return void 0;
194027
+ new vm.Script(`(async function (${PARAMS2.join(", ")}) {
194028
+ ${script}
194029
+ })`, { filename: file });
194030
+ } catch (e) {
194031
+ const stack = e && typeof e === "object" ? e.stack : void 0;
194032
+ const match = typeof stack === "string" ? new RegExp(`${file}:(\\d+)`).exec(stack) : null;
194033
+ if (match) return Math.max(1, Number(match[1]) - 1);
194034
+ }
194035
+ return void 0;
194036
+ }
194037
+ var functionScriptNotCompiled = {
194038
+ code: "function-script-not-compiled" /* FunctionScriptNotCompiled */,
194039
+ description: "A Function's script does not compile, so the Function can never run.",
194040
+ defaultEnabled: true,
194041
+ run(ctx) {
194042
+ const out = [];
194043
+ for (const { component } of ctx.components) {
194044
+ for (const node of component.nodes) {
194045
+ if (node.type !== FUNCTION_NODE_TYPE) continue;
194046
+ const script = node.parameters?.functionScript;
194047
+ if (typeof script !== "string" || script.trim() === "") continue;
194048
+ const error = functionScriptCompileError(script);
194049
+ if (!error) continue;
194050
+ const name2 = node.label ? `"${node.label}"` : "This Function";
194051
+ out.push({
194052
+ code: "function-script-not-compiled" /* FunctionScriptNotCompiled */,
194053
+ severity: "warning",
194054
+ message: `${name2}'s script does not compile${error.line !== void 0 ? ` (line ${error.line})` : ""}: ${error.message}. It can never run: every Run fails with function/script-not-compiled. Fix the script; a regular expression or URL written inside a template literal loses its backslashes, so \`\\/\\/\` arrives as \`//\`, which starts a comment.`,
194055
+ location: {
194056
+ component: component.name,
194057
+ nodeId: node.id,
194058
+ nodeType: node.type,
194059
+ nodeLabel: node.label,
194060
+ port: "functionScript"
194061
+ }
194062
+ });
194063
+ }
194064
+ }
194065
+ return out;
194066
+ }
194067
+ };
194068
+
194069
+ // ../noodl-editor/src/editor/src/validation/rules/functionNeverReruns.ts
194070
+ var RUN_ON_CHANGE_PREFIX3 = "runOnChange-";
194071
+ var functionNeverReruns = {
194072
+ code: "function-never-reruns" /* FunctionNeverReruns */,
194073
+ description: "Nothing re-runs a node when its inputs change: its control signal is unwired and every wired input is set not to run on change.",
194074
+ defaultEnabled: true,
194075
+ run(ctx) {
194076
+ const out = [];
194077
+ const { catalog } = ctx;
194078
+ for (const { component } of ctx.components) {
194079
+ const incoming = /* @__PURE__ */ new Map();
194080
+ for (const c of component.connections) {
194081
+ const list = incoming.get(c.toId);
194082
+ if (list) list.push(c);
194083
+ else incoming.set(c.toId, [c]);
194084
+ }
194085
+ for (const node of component.nodes) {
194086
+ const isFunction2 = node.type === FUNCTION_NODE_TYPE;
194087
+ if (!isFunction2 && !catalog.isRunOnValueChangeFamily(node.type)) continue;
194088
+ const inbound = incoming.get(node.id) ?? [];
194089
+ if (inbound.length === 0) continue;
194090
+ if (inbound.some((c) => catalog.getPort(node.type, "input", c.toProperty)?.isSignal)) continue;
194091
+ const governed = [
194092
+ ...new Set(
194093
+ inbound.map((c) => c.toProperty).filter(
194094
+ (port) => isFunction2 ? port.startsWith("in-") : !!catalog.getPort(node.type, "input", RUN_ON_CHANGE_PREFIX3 + port)
194095
+ )
194096
+ )
194097
+ ];
194098
+ if (governed.length === 0) continue;
194099
+ const params = node.parameters ?? {};
194100
+ if (!governed.every((port) => params[RUN_ON_CHANGE_PREFIX3 + port] === false)) continue;
194101
+ const name2 = node.label ? `"${node.label}"` : isFunction2 ? "This Function" : "This node";
194102
+ const shown = governed.map((p) => `"${isFunction2 ? p.slice("in-".length) : p}"`).join(", ");
194103
+ out.push({
194104
+ code: "function-never-reruns" /* FunctionNeverReruns */,
194105
+ severity: "warning",
194106
+ message: `Nothing re-runs ${name2} when its inputs change: Run is not wired, and every wired input (${shown}) has Run On Value Change unticked. ` + (isFunction2 ? "It runs once when it loads, and a new value on any of them never runs it again. " : "") + "Wire Run to what should start it, or tick Run On Value Change on the input whose new value should run it.",
194107
+ location: {
194108
+ component: component.name,
194109
+ nodeId: node.id,
194110
+ nodeType: node.type,
194111
+ nodeLabel: node.label,
194112
+ port: RUN_ON_CHANGE_PREFIX3 + governed[0],
194113
+ plug: "input"
194114
+ },
194115
+ suggestion: `${RUN_ON_CHANGE_PREFIX3}${governed[0]}: true`,
194116
+ alternatives: governed.map((p) => `${RUN_ON_CHANGE_PREFIX3}${p}: true`)
194117
+ });
194118
+ }
194119
+ }
194120
+ return out;
194121
+ }
194122
+ };
194123
+
193840
194124
  // ../noodl-editor/src/editor/src/validation/rules/index.ts
193841
194125
  var ALL_RULES = [
193842
194126
  duplicateNodeId,
@@ -193888,6 +194172,11 @@ var ALL_RULES = [
193888
194172
  // the first fires on `prep` and never on the `RunTasks` carrying the defect,
193889
194173
  // so this covers a hole in it rather than repeating it.
193890
194174
  completedCommitsUnchecked,
194175
+ // P121 bugs SCRIPTCOMPILE / NEVERRUNS — a Function that can never run, and one that never
194176
+ // re-runs: breakage of the graph's behaviour, so beside the outcome rules and before the
194177
+ // arrangement ones.
194178
+ functionScriptNotCompiled,
194179
+ functionNeverReruns,
193891
194180
  repeatedSiblingSubtree,
193892
194181
  // DEF-025 — beside `repeatedSiblingSubtree` because both read the visual
193893
194182
  // tree's arrangement rather than wires: that one asks whether siblings
@@ -194805,7 +195094,7 @@ var PreviewServer = class {
194805
195094
  if (!bundle) return this.notFound(res);
194806
195095
  return this.sendDynamic(res, bundle, "application/json; charset=utf-8");
194807
195096
  }
194808
- if (pathname === "/nodegx-config.json") return this.notFound(res);
195097
+ if (pathname === "/nodegx-config.json") return this.sendDynamic(res, "{}", "application/json; charset=utf-8");
194809
195098
  const asset = pathname.slice(1);
194810
195099
  if (DEPLOY_ASSETS.includes(asset)) {
194811
195100
  return this.sendFile(req, res, deployAssetPath(asset));