@neocompose/cli 0.73.2 → 0.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1124,16 +1124,16 @@ function memberKindSupportsDistribution(kind) {
1124
1124
  return memberKindSupportsStorage(kind);
1125
1125
  }
1126
1126
 
1127
- // node_modules/uuid/dist-node/regex.js
1127
+ // ../node_modules/uuid/dist-node/regex.js
1128
1128
  var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
1129
1129
 
1130
- // node_modules/uuid/dist-node/validate.js
1130
+ // ../node_modules/uuid/dist-node/validate.js
1131
1131
  function validate(uuid) {
1132
1132
  return typeof uuid === "string" && regex_default.test(uuid);
1133
1133
  }
1134
1134
  var validate_default = validate;
1135
1135
 
1136
- // node_modules/uuid/dist-node/parse.js
1136
+ // ../node_modules/uuid/dist-node/parse.js
1137
1137
  function parse(uuid) {
1138
1138
  if (!validate_default(uuid)) {
1139
1139
  throw TypeError("Invalid UUID");
@@ -1143,7 +1143,7 @@ function parse(uuid) {
1143
1143
  }
1144
1144
  var parse_default = parse;
1145
1145
 
1146
- // node_modules/uuid/dist-node/stringify.js
1146
+ // ../node_modules/uuid/dist-node/stringify.js
1147
1147
  var byteToHex = [];
1148
1148
  for (let i = 0; i < 256; ++i) {
1149
1149
  byteToHex.push((i + 256).toString(16).slice(1));
@@ -1152,13 +1152,13 @@ function unsafeStringify(arr, offset = 0) {
1152
1152
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
1153
1153
  }
1154
1154
 
1155
- // node_modules/uuid/dist-node/rng.js
1155
+ // ../node_modules/uuid/dist-node/rng.js
1156
1156
  var rnds8 = new Uint8Array(16);
1157
1157
  function rng() {
1158
1158
  return crypto.getRandomValues(rnds8);
1159
1159
  }
1160
1160
 
1161
- // node_modules/uuid/dist-node/v35.js
1161
+ // ../node_modules/uuid/dist-node/v35.js
1162
1162
  function stringToBytes(str) {
1163
1163
  str = unescape(encodeURIComponent(str));
1164
1164
  const bytes = new Uint8Array(str.length);
@@ -1197,7 +1197,7 @@ function v35(version, hash, value, namespace, buf, offset) {
1197
1197
  return unsafeStringify(bytes);
1198
1198
  }
1199
1199
 
1200
- // node_modules/uuid/dist-node/v4.js
1200
+ // ../node_modules/uuid/dist-node/v4.js
1201
1201
  function v4(options, buf, offset) {
1202
1202
  if (!buf && !options && crypto.randomUUID) {
1203
1203
  return crypto.randomUUID();
@@ -1226,7 +1226,7 @@ function _v4(options, buf, offset) {
1226
1226
  }
1227
1227
  var v4_default = v4;
1228
1228
 
1229
- // node_modules/uuid/dist-node/sha1.js
1229
+ // ../node_modules/uuid/dist-node/sha1.js
1230
1230
  import { createHash } from "node:crypto";
1231
1231
  function sha1(bytes) {
1232
1232
  if (Array.isArray(bytes)) {
@@ -1238,7 +1238,7 @@ function sha1(bytes) {
1238
1238
  }
1239
1239
  var sha1_default = sha1;
1240
1240
 
1241
- // node_modules/uuid/dist-node/v5.js
1241
+ // ../node_modules/uuid/dist-node/v5.js
1242
1242
  function v5(value, namespace, buf, offset) {
1243
1243
  return v35(80, sha1_default, value, namespace, buf, offset);
1244
1244
  }
@@ -3629,24 +3629,30 @@ function isNSInstructionTry(value) {
3629
3629
  }
3630
3630
  return true;
3631
3631
  }
3632
- function isNSInstructionChangeListener(value) {
3633
- const instruction = value;
3634
- if (instruction?.type !== "addChangeListener" /* addChangeListener */ && instruction?.type !== "removeChangeListener" /* removeChangeListener */)
3635
- return false;
3636
- const target = instruction.target;
3632
+ function isNSChangeListenerTarget(value) {
3633
+ const target = value;
3637
3634
  if (!target || typeof target.memberId !== "string" || target.memberId.length === 0)
3638
3635
  return false;
3639
3636
  if (!isNSPointer(target.owner) || !isNSTypeInfo(target.typeInfo))
3640
3637
  return false;
3641
- if (!Object.values(NSWritability).includes(target.writability)) return false;
3642
- return isNSPointer(instruction.listener);
3638
+ return target.writability !== void 0 && Object.values(NSWritability).includes(target.writability);
3639
+ }
3640
+ function isNSInstructionChangeListener(value) {
3641
+ const instruction = value;
3642
+ if (instruction?.type !== "addChangeListener" /* addChangeListener */ && instruction?.type !== "removeChangeListener" /* removeChangeListener */)
3643
+ return false;
3644
+ return isNSChangeListenerTarget(instruction.target) && isNSPointer(instruction.listener);
3645
+ }
3646
+ function isNSInstructionClearChangeListeners(value) {
3647
+ const instruction = value;
3648
+ return instruction?.type === "clearChangeListeners" /* clearChangeListeners */ && isNSChangeListenerTarget(instruction.target);
3643
3649
  }
3644
3650
  function isNSInstructionDebug(value) {
3645
3651
  const v = value;
3646
3652
  return v?.type === "debug" /* debug */ && ["log", "warning", "error", "assert"].includes(v.severity ?? "") && isNSPointer(v.message) && isNSTypeInfo(v.messageType) && (v.severity === "assert" ? isNSPointer(v.condition) : v.condition === void 0);
3647
3653
  }
3648
3654
  function isNSInstruction(value) {
3649
- return isNSInstructionDebug(value) || isNSInstructionVariable(value) || isNSInstructionIfBranch(value) || isNSInstructionReturn(value) || isNSInstructionThrow(value) || isNSInstructionAssign(value) || isNSInstructionCollectionCall(value) || isNSInstructionFunctionCall(value) || isNSInstructionConditionLoop(value) || isNSInstructionFor(value) || isNSInstructionForEach(value) || isNSInstructionBreak(value) || isNSInstructionContinue(value) || isNSInstructionSwitch(value) || isNSInstructionTry(value) || isNSInstructionChangeListener(value) || isNSInstructionAddActionListener(value) || isNSInstructionRemoveActionListener(value);
3655
+ return isNSInstructionDebug(value) || isNSInstructionVariable(value) || isNSInstructionIfBranch(value) || isNSInstructionReturn(value) || isNSInstructionThrow(value) || isNSInstructionAssign(value) || isNSInstructionCollectionCall(value) || isNSInstructionFunctionCall(value) || isNSInstructionConditionLoop(value) || isNSInstructionFor(value) || isNSInstructionForEach(value) || isNSInstructionBreak(value) || isNSInstructionContinue(value) || isNSInstructionSwitch(value) || isNSInstructionTry(value) || isNSInstructionChangeListener(value) || isNSInstructionClearChangeListeners(value) || isNSInstructionAddActionListener(value) || isNSInstructionRemoveActionListener(value);
3650
3656
  }
3651
3657
  function isNSInstructions(value) {
3652
3658
  return Array.isArray(value) && value.every(isNSInstruction);
@@ -13204,6 +13210,9 @@ function listenerWritesByOwner(state) {
13204
13210
  }
13205
13211
  return index.owners;
13206
13212
  }
13213
+ function changeListenerWriteEdit(write) {
13214
+ return write.operation === "clear" ? { kind: "replace", targets: [] } : { kind: write.operation, target: write.listener };
13215
+ }
13207
13216
 
13208
13217
  // ../src/runtime/neoscript/typed-aggregate-row-references.ts
13209
13218
  function visitTypedAggregateRowReferences(value, rawType, visitor) {
@@ -14859,6 +14868,144 @@ function checkedCoordinate(value) {
14859
14868
  return value;
14860
14869
  }
14861
14870
 
14871
+ // ../src/runtime/neoscript/getter-subscriptions.ts
14872
+ var NeoScriptGetterSubscription = class {
14873
+ constructor(rowId, getterId, classId) {
14874
+ this.rowId = rowId;
14875
+ this.getterId = getterId;
14876
+ this.classId = classId;
14877
+ }
14878
+ rowId;
14879
+ getterId;
14880
+ classId;
14881
+ targets = [];
14882
+ reads = new InitializerReadSet();
14883
+ queued = false;
14884
+ /** Queued only to arm: its first read calls no handler. */
14885
+ arming = true;
14886
+ /** Its handlers are running. */
14887
+ handling = false;
14888
+ };
14889
+ var NO_SUBSCRIPTIONS = [];
14890
+ var staticReadKey = (memberId) => `static:${memberId}`;
14891
+ function* readKeys(reads) {
14892
+ yield* reads.valueIds;
14893
+ yield* reads.containerIds;
14894
+ yield* reads.containerEntryReadIds;
14895
+ for (const id of reads.staticMemberIds) yield staticReadKey(id);
14896
+ }
14897
+ var NeoScriptGetterSubscriptions = class _NeoScriptGetterSubscriptions {
14898
+ byRow = /* @__PURE__ */ new Map();
14899
+ byRead = /* @__PURE__ */ new Map();
14900
+ /** Reads no row id names, such as corpus or world queries, hear every write. */
14901
+ readingEverything = /* @__PURE__ */ new Set();
14902
+ pending = [];
14903
+ get empty() {
14904
+ return this.byRow.size === 0;
14905
+ }
14906
+ get(rowId, getterId) {
14907
+ return this.byRow.get(rowId)?.get(getterId);
14908
+ }
14909
+ add(subscription) {
14910
+ this.register(subscription);
14911
+ this.queue(subscription);
14912
+ }
14913
+ drop(subscription) {
14914
+ const getters = this.byRow.get(subscription.rowId);
14915
+ if (getters?.get(subscription.getterId) !== subscription) return;
14916
+ getters.delete(subscription.getterId);
14917
+ if (getters.size === 0) this.byRow.delete(subscription.rowId);
14918
+ this.unindex(subscription);
14919
+ }
14920
+ noteValueWrite(rowId) {
14921
+ this.noteRead(rowId);
14922
+ }
14923
+ noteStaticBinding(memberId) {
14924
+ this.noteRead(staticReadKey(memberId));
14925
+ }
14926
+ /** Replace what the subscription's last read touched. */
14927
+ setReads(subscription, reads) {
14928
+ this.unindex(subscription);
14929
+ subscription.reads = reads;
14930
+ if (this.get(subscription.rowId, subscription.getterId) !== subscription)
14931
+ return;
14932
+ if (reads.corpusReadReasons.size > 0 || reads.worldQueries.size > 0)
14933
+ this.readingEverything.add(subscription);
14934
+ for (const key of readKeys(reads)) {
14935
+ let subscriptions = this.byRead.get(key);
14936
+ if (subscriptions === void 0) {
14937
+ subscriptions = /* @__PURE__ */ new Set();
14938
+ this.byRead.set(key, subscriptions);
14939
+ }
14940
+ subscriptions.add(subscription);
14941
+ }
14942
+ }
14943
+ takePending() {
14944
+ if (this.pending.length === 0) return NO_SUBSCRIPTIONS;
14945
+ const pending = this.pending;
14946
+ this.pending = [];
14947
+ return pending;
14948
+ }
14949
+ /** A throw skipped `drained` from `next` on. They stay queued, so none goes deaf. */
14950
+ requeue(drained, next) {
14951
+ for (let index = next; index < drained.length; index += 1)
14952
+ this.pending.push(drained[index]);
14953
+ }
14954
+ /** A copy for a fresh runtime over the same rows, armed exactly as these are. */
14955
+ clone() {
14956
+ const cloned = new _NeoScriptGetterSubscriptions();
14957
+ const copies = /* @__PURE__ */ new Map();
14958
+ for (const getters of this.byRow.values()) {
14959
+ for (const source of getters.values()) {
14960
+ const copy = new NeoScriptGetterSubscription(
14961
+ source.rowId,
14962
+ source.getterId,
14963
+ source.classId
14964
+ );
14965
+ copy.targets.push(...source.targets);
14966
+ copy.arming = source.arming;
14967
+ copies.set(source, copy);
14968
+ cloned.register(copy);
14969
+ cloned.setReads(copy, source.reads);
14970
+ }
14971
+ }
14972
+ for (const source of this.pending) {
14973
+ const copy = copies.get(source);
14974
+ if (copy !== void 0) cloned.queue(copy);
14975
+ }
14976
+ return cloned;
14977
+ }
14978
+ register(subscription) {
14979
+ let getters = this.byRow.get(subscription.rowId);
14980
+ if (getters === void 0) {
14981
+ getters = /* @__PURE__ */ new Map();
14982
+ this.byRow.set(subscription.rowId, getters);
14983
+ }
14984
+ getters.set(subscription.getterId, subscription);
14985
+ }
14986
+ queue(subscription) {
14987
+ if (subscription.queued) return;
14988
+ subscription.queued = true;
14989
+ this.pending.push(subscription);
14990
+ }
14991
+ noteRead(key) {
14992
+ const subscriptions = this.byRead.get(key);
14993
+ if (subscriptions !== void 0)
14994
+ for (const subscription of subscriptions) this.queue(subscription);
14995
+ for (const subscription of this.readingEverything)
14996
+ if (!subscription.handling) this.queue(subscription);
14997
+ }
14998
+ unindex(subscription) {
14999
+ this.readingEverything.delete(subscription);
15000
+ for (const key of readKeys(subscription.reads)) {
15001
+ const subscriptions = this.byRead.get(key);
15002
+ if (subscriptions === void 0) continue;
15003
+ subscriptions.delete(subscription);
15004
+ if (subscriptions.size === 0) this.byRead.delete(key);
15005
+ }
15006
+ }
15007
+ };
15008
+
14862
15009
  // ../src/models/decimal/decimal-math.ts
14863
15010
  var DecimalOverflowError = class extends Error {
14864
15011
  };
@@ -15446,6 +15593,7 @@ function evalMemberById(vm, id) {
15446
15593
  return vm.members.find((a) => a.id === id) ?? null;
15447
15594
  }
15448
15595
  function evalValueById(ctx, id, runtimeValues, overlayValues) {
15596
+ ctx.__getterReads?.add(id);
15449
15597
  if (ctx.__removedValueIds?.has(id)) {
15450
15598
  ctx.valueDependencies?.add(id);
15451
15599
  return null;
@@ -16120,8 +16268,13 @@ function bindThisValue(ctx, thisValue) {
16120
16268
  const rowId = thisValue === ctx.thisValue ? ctx.thisValueRowId : trackedRowForValueReference(thisValue, ctx)?.id;
16121
16269
  return { ...ctx, thisValue, thisValueRowId: rowId };
16122
16270
  }
16271
+ function observeValueWrite(ctx, row) {
16272
+ ctx.beforeValueWrite?.(row);
16273
+ ctx.__getterSubscriptions?.noteValueWrite(row.id);
16274
+ }
16123
16275
  function assertStoredReplayMutableValue(value, ctx, label, key) {
16124
- if (ctx.storedConstructionReplay !== true && !ctx.assertValueWritable && !ctx.beforeValueWrite)
16276
+ const observesWrites = ctx.beforeValueWrite !== void 0 || ctx.__getterSubscriptions?.empty === false;
16277
+ if (ctx.storedConstructionReplay !== true && !ctx.assertValueWritable && !observesWrites)
16125
16278
  return;
16126
16279
  let rowId = findKnownRowIdByValueReference(value, ctx);
16127
16280
  if (rowId === null) return;
@@ -16140,7 +16293,7 @@ function assertStoredReplayMutableValue(value, ctx, label, key) {
16140
16293
  );
16141
16294
  if (ctx.__runtimeSessionValues?.has(rowId) !== true)
16142
16295
  ctx.assertValueWritable?.(rowId);
16143
- if (row) ctx.beforeValueWrite?.(row);
16296
+ if (row) observeValueWrite(ctx, row);
16144
16297
  if (ctx.__runtimeSessionValues?.has(rowId) === true) return;
16145
16298
  if (ctx.storedConstructionReplay !== true) return;
16146
16299
  throw new NSGetterRuntimeError(
@@ -16649,10 +16802,14 @@ function readNeoScriptRuntimeRow(ctx, valueId) {
16649
16802
  ctx.__valueOverlay
16650
16803
  );
16651
16804
  }
16805
+ function getterSubscriptionsFor(ctx) {
16806
+ return ctx.getterDispatch === "afterEntryStatement" /* AfterEntryStatement */ ? new NeoScriptGetterSubscriptions() : void 0;
16807
+ }
16652
16808
  function withEvaluationRuntime(ctx, writes) {
16653
16809
  if (ctx.__runtimeSessionValues !== void 0 && ctx.__executionState !== void 0 && ctx.__valueOverlay !== void 0) {
16654
16810
  ctx.__unorderedListProjections ??= createUnorderedListProjectionCache();
16655
16811
  ctx.__runtimeClassOverrides ??= /* @__PURE__ */ new Map();
16812
+ ctx.__getterSubscriptions ??= getterSubscriptionsFor(ctx);
16656
16813
  return ctx;
16657
16814
  }
16658
16815
  const overlay = ctx.__valueOverlay ?? (ctx.storedConstructionReplay === true ? /* @__PURE__ */ new Map() : buildValueOverlay(ctx.vm));
@@ -16677,6 +16834,7 @@ function withEvaluationRuntime(ctx, writes) {
16677
16834
  trigger: remap(ctx.dialogueContext.trigger)
16678
16835
  },
16679
16836
  __runtimeSessionValues: runtimeSessionValues,
16837
+ __getterSubscriptions: ctx.__getterSubscriptions ?? getterSubscriptionsFor(ctx),
16680
16838
  __copiedChangeListeners: ctx.__copiedChangeListeners ?? /* @__PURE__ */ new Map(),
16681
16839
  __runtimeClassOverrides: ctx.__runtimeClassOverrides ?? /* @__PURE__ */ new Map(),
16682
16840
  __saveStaticBindings: ctx.__saveStaticBindings ?? staticBindingMap(ctx.saveStaticBindings),
@@ -16883,33 +17041,41 @@ function finalizeConstructorAllocations(ctx, returnValue, ownsInvocationState, r
16883
17041
  const listenerEdges = /* @__PURE__ */ new Map();
16884
17042
  for (const write of state.writes) {
16885
17043
  if (!retainListenerReceivers || write.kind !== "changeListener") continue;
17044
+ const slotKey = JSON.stringify([write.ownerId, write.memberId]);
17045
+ if (write.operation === "clear") {
17046
+ listenerEdges.delete(slotKey);
17047
+ continue;
17048
+ }
16886
17049
  const key = JSON.stringify([
16887
- write.ownerId,
16888
- write.memberId,
16889
17050
  write.listener.memberId,
16890
17051
  write.listener.valueId
16891
17052
  ]);
16892
- if (write.operation === "remove") listenerEdges.delete(key);
16893
- else listenerEdges.set(key, write);
17053
+ let edges = listenerEdges.get(slotKey);
17054
+ if (write.operation === "remove") edges?.delete(key);
17055
+ else {
17056
+ if (edges === void 0) listenerEdges.set(slotKey, edges = /* @__PURE__ */ new Map());
17057
+ edges.set(key, write);
17058
+ }
16894
17059
  }
16895
17060
  const externalListenerReceivers = /* @__PURE__ */ new Set();
16896
- for (const write of listenerEdges.values()) {
16897
- if (write.listener.valueId === null) continue;
16898
- const receiverGroup = groupByRowId.get(write.listener.valueId);
16899
- if (receiverGroup === void 0) continue;
16900
- const ownerGroup = groupByRowId.get(write.ownerId);
16901
- if (ownerGroup === void 0) {
16902
- if (rowForCleanup(write.ownerId) !== null)
16903
- externalListenerReceivers.add(receiverGroup);
16904
- } else {
16905
- let dependencies = state.constructorGroupDependencies.get(ownerGroup);
16906
- if (dependencies === void 0) {
16907
- dependencies = /* @__PURE__ */ new Set();
16908
- state.constructorGroupDependencies.set(ownerGroup, dependencies);
17061
+ for (const edges of listenerEdges.values())
17062
+ for (const write of edges.values()) {
17063
+ if (write.listener.valueId === null) continue;
17064
+ const receiverGroup = groupByRowId.get(write.listener.valueId);
17065
+ if (receiverGroup === void 0) continue;
17066
+ const ownerGroup = groupByRowId.get(write.ownerId);
17067
+ if (ownerGroup === void 0) {
17068
+ if (rowForCleanup(write.ownerId) !== null)
17069
+ externalListenerReceivers.add(receiverGroup);
17070
+ } else {
17071
+ let dependencies = state.constructorGroupDependencies.get(ownerGroup);
17072
+ if (dependencies === void 0) {
17073
+ dependencies = /* @__PURE__ */ new Set();
17074
+ state.constructorGroupDependencies.set(ownerGroup, dependencies);
17075
+ }
17076
+ dependencies.add(receiverGroup);
16909
17077
  }
16910
- dependencies.add(receiverGroup);
16911
17078
  }
16912
- }
16913
17079
  const markConstructorGroupEscaped = (rootId) => {
16914
17080
  if (escapedRoots.has(rootId)) return;
16915
17081
  escapedRoots.add(rootId);
@@ -17106,7 +17272,7 @@ function finalizeConstructorAllocations(ctx, returnValue, ownsInvocationState, r
17106
17272
  const survivingWrites = state.writes.filter((write) => {
17107
17273
  if (write.kind === "changeListener") {
17108
17274
  if (collectedIds.has(write.ownerId)) return false;
17109
- return write.listener.valueId === null || !collectedIds.has(write.listener.valueId);
17275
+ return write.operation === "clear" || write.listener.valueId === null || !collectedIds.has(write.listener.valueId);
17110
17276
  }
17111
17277
  const destination = write.resolvedDestination;
17112
17278
  const destinationId = destination !== void 0 && "containerValueId" in destination ? destination.containerValueId : void 0;
@@ -17680,6 +17846,7 @@ function validateTryInstruction(instruction) {
17680
17846
  function evalBody(body, scope, ctx, options, preparedFrame) {
17681
17847
  const state = ctx.__executionState;
17682
17848
  if (!state) return evalInstructions(body.instructions, scope, ctx, options);
17849
+ const hostBody = state.frames.length === 0 && ctx.__getterSubscriptions !== void 0;
17683
17850
  state.frames.push(
17684
17851
  preparedFrame ?? {
17685
17852
  name: body.source?.name ?? "<body>",
@@ -17688,7 +17855,14 @@ function evalBody(body, scope, ctx, options, preparedFrame) {
17688
17855
  }
17689
17856
  );
17690
17857
  try {
17691
- return evalInstructions(body.instructions, scope, ctx, options);
17858
+ if (!hostBody)
17859
+ return evalInstructions(body.instructions, scope, ctx, options);
17860
+ const result = evalInstructions(body.instructions, scope, ctx, {
17861
+ ...options,
17862
+ dispatchesGetterChanges: true
17863
+ });
17864
+ dispatchGetterSubscriptions(ctx);
17865
+ return result;
17692
17866
  } catch (error) {
17693
17867
  attachNeoScriptTrace(error, state);
17694
17868
  throw error;
@@ -17852,7 +18026,8 @@ function evalInstructions(instructions, scope, ctx, options) {
17852
18026
  break;
17853
18027
  }
17854
18028
  case "addChangeListener" /* addChangeListener */:
17855
- case "removeChangeListener" /* removeChangeListener */: {
18029
+ case "removeChangeListener" /* removeChangeListener */:
18030
+ case "clearChangeListeners" /* clearChangeListeners */: {
17856
18031
  evalChangeListenerInstruction(ins, scope, ctx, options);
17857
18032
  break;
17858
18033
  }
@@ -18067,6 +18242,8 @@ function evalInstructions(instructions, scope, ctx, options) {
18067
18242
  return { kind: "continue" };
18068
18243
  }
18069
18244
  void touchUnknown(ins);
18245
+ if (options.dispatchesGetterChanges === true)
18246
+ dispatchGetterSubscriptions(ctx);
18070
18247
  } catch (error) {
18071
18248
  positionNeoScriptFrame(ctx, ins.source);
18072
18249
  if (ctx.__executionState)
@@ -18141,6 +18318,30 @@ function resolveNeoScriptChangeListenerSlot(ownerId, memberId, ctx) {
18141
18318
  typeInfo: changeListenerObservedType(raw, member, env, ctx)
18142
18319
  };
18143
18320
  }
18321
+ function resolveNeoScriptGetterListenerSlot(ownerId, memberId, ctx) {
18322
+ const owner = evalValueById(
18323
+ ctx,
18324
+ ownerId,
18325
+ ctx.__runtimeSessionValues,
18326
+ ctx.__valueOverlay
18327
+ );
18328
+ if (owner === null || owner.value === null) return null;
18329
+ const entry = changeListenerMemberOnOwner(owner, memberId, ctx);
18330
+ if (entry === void 0) return null;
18331
+ const member = evalMemberById(ctx.vm, entry.memberId);
18332
+ if (member === null || !isMemberNSProperty(member)) return null;
18333
+ if (isStaticMember(member)) return null;
18334
+ const env = runtimeGenericEnvForValueRow(owner, ctx, /* @__PURE__ */ new Set());
18335
+ const typeInfo = substituteRuntimeTypeInfo(member.returnTypeInfo, env, ctx);
18336
+ if (typeInfo.type === NS_TYPE_VOID) return null;
18337
+ return {
18338
+ owner,
18339
+ member,
18340
+ memberId: evaluatorResolutionCache(ctx).canonicalMemberId(member.id),
18341
+ schemaKey: entry.schemaKey,
18342
+ typeInfo
18343
+ };
18344
+ }
18144
18345
  function changeListenerObservedType(raw, member, env, ctx) {
18145
18346
  const runtime = runtimeTypeInfoForMember(raw, env, ctx, /* @__PURE__ */ new Set());
18146
18347
  if (!isMemberLookup(member)) return runtime;
@@ -18163,6 +18364,18 @@ function changeListenerObservedType(raw, member, env, ctx) {
18163
18364
  required: isRequiredMember(member)
18164
18365
  };
18165
18366
  }
18367
+ function readNeoScriptChangeListenerValue(slot, ctx) {
18368
+ const result = dispatchSchemaMember(
18369
+ readRowValue(slot.owner, ctx),
18370
+ slot.schemaKey,
18371
+ ctx
18372
+ );
18373
+ if (result.kind !== "ok")
18374
+ throw new NSGetterRuntimeError(
18375
+ `OnChanged cannot read '${slot.owner.id}/${slot.memberId}'.`
18376
+ );
18377
+ return result.value;
18378
+ }
18166
18379
  function resolveNeoScriptChangeListenerHandler(slot, target, ctx) {
18167
18380
  const raw = evalMemberById(ctx.vm, target.memberId);
18168
18381
  if (raw === null) return null;
@@ -18224,15 +18437,29 @@ function evalChangeListenerInstruction(ins, scope, ctx, options) {
18224
18437
  throw new NSGetterRuntimeError(
18225
18438
  "OnChanged requires an existing logical owner."
18226
18439
  );
18227
- const slot = resolveNeoScriptChangeListenerSlot(
18440
+ const stored = resolveNeoScriptChangeListenerSlot(
18228
18441
  owner.id,
18229
18442
  ins.target.memberId,
18230
18443
  ctx
18231
18444
  );
18445
+ const getter = stored === null ? resolveNeoScriptGetterListenerSlot(owner.id, ins.target.memberId, ctx) : null;
18446
+ const slot = stored ?? getter;
18232
18447
  if (slot === null)
18233
18448
  throw new NSGetterRuntimeError(
18234
- `OnChanged member '${ins.target.memberId}' is not an observable stored member of owner '${owner.id}'.`
18449
+ `OnChanged member '${ins.target.memberId}' is not an observable member or getter of owner '${owner.id}'.`
18235
18450
  );
18451
+ if (ins.type === "clearChangeListeners" /* clearChangeListeners */) {
18452
+ if (getter !== null) clearGetterSubscription(getter, ctx);
18453
+ else
18454
+ options.writes.push({
18455
+ kind: "changeListener",
18456
+ target: ins.target,
18457
+ ownerId: owner.id,
18458
+ memberId: slot.memberId,
18459
+ operation: "clear"
18460
+ });
18461
+ return;
18462
+ }
18236
18463
  const listener = evalPointer(ins.listener, scope, ctx);
18237
18464
  if (!isMemberDelegateTarget(listener))
18238
18465
  throw new NSGetterRuntimeError(
@@ -18243,6 +18470,15 @@ function evalChangeListenerInstruction(ins, scope, ctx, options) {
18243
18470
  throw new NSGetterRuntimeError(
18244
18471
  `OnChanged handler '${listener.memberId}' cannot accept the value of '${slot.member.name}' on '${owner.id}'.`
18245
18472
  );
18473
+ if (getter !== null) {
18474
+ editGetterSubscription(
18475
+ getter,
18476
+ handler.target,
18477
+ ins.type === "addChangeListener" /* addChangeListener */,
18478
+ ctx
18479
+ );
18480
+ return;
18481
+ }
18246
18482
  options.writes.push({
18247
18483
  kind: "changeListener",
18248
18484
  target: ins.target,
@@ -18252,6 +18488,106 @@ function evalChangeListenerInstruction(ins, scope, ctx, options) {
18252
18488
  listener: handler.target
18253
18489
  });
18254
18490
  }
18491
+ function editGetterSubscription(slot, target, add, ctx) {
18492
+ if ((ctx.__executionState?.constructionStack.length ?? 0) > 0)
18493
+ throw new NSGetterRuntimeError(
18494
+ "A getter subscription lives only for the play session, so a constructor can't make one. Subscribe from a function."
18495
+ );
18496
+ const subscriptions = ctx.__getterSubscriptions;
18497
+ if (subscriptions === void 0) return;
18498
+ let subscription = subscriptions.get(slot.owner.id, slot.memberId);
18499
+ const found = subscription?.targets.findIndex(
18500
+ (existing) => existing.memberId === target.memberId && existing.valueId === target.valueId
18501
+ ) ?? -1;
18502
+ if (!add) {
18503
+ if (subscription === void 0 || found < 0) return;
18504
+ subscription.targets.splice(found, 1);
18505
+ if (subscription.targets.length === 0) subscriptions.drop(subscription);
18506
+ return;
18507
+ }
18508
+ if (found >= 0) return;
18509
+ if (subscription === void 0) {
18510
+ subscription = new NeoScriptGetterSubscription(
18511
+ slot.owner.id,
18512
+ slot.memberId,
18513
+ slot.owner.classId
18514
+ );
18515
+ subscriptions.add(subscription);
18516
+ }
18517
+ subscription.targets.push(target);
18518
+ }
18519
+ function clearGetterSubscription(slot, ctx) {
18520
+ if ((ctx.__executionState?.constructionStack.length ?? 0) > 0)
18521
+ throw new NSGetterRuntimeError(
18522
+ "A getter subscription lives only for the play session, so a constructor can't clear one. Clear from a function."
18523
+ );
18524
+ const subscriptions = ctx.__getterSubscriptions;
18525
+ const subscription = subscriptions?.get(slot.owner.id, slot.memberId);
18526
+ if (subscriptions === void 0 || subscription === void 0) return;
18527
+ subscription.targets.length = 0;
18528
+ subscriptions.drop(subscription);
18529
+ }
18530
+ function dispatchGetterSubscriptions(ctx) {
18531
+ const subscriptions = ctx.__getterSubscriptions;
18532
+ if (subscriptions === void 0) return;
18533
+ const draining = subscriptions.takePending();
18534
+ let next = 0;
18535
+ try {
18536
+ while (next < draining.length)
18537
+ dispatchGetterSubscription(draining[next++], subscriptions, ctx);
18538
+ } finally {
18539
+ subscriptions.requeue(draining, next);
18540
+ }
18541
+ }
18542
+ function dispatchGetterSubscription(subscription, subscriptions, ctx) {
18543
+ subscription.queued = false;
18544
+ const notify = !subscription.arming;
18545
+ subscription.arming = false;
18546
+ if (subscription.targets.length === 0) return;
18547
+ const slot = resolveNeoScriptGetterListenerSlot(
18548
+ subscription.rowId,
18549
+ subscription.getterId,
18550
+ ctx
18551
+ );
18552
+ if (slot === null || slot.owner.classId !== subscription.classId) {
18553
+ subscriptions.drop(subscription);
18554
+ return;
18555
+ }
18556
+ const reads = new InitializerReadSet();
18557
+ let value;
18558
+ try {
18559
+ value = readNeoScriptChangeListenerValue(slot, {
18560
+ ...ctx,
18561
+ __getterReads: reads.valueIds,
18562
+ containerDependencies: reads.containerIds,
18563
+ staticMemberDependencies: reads.staticMemberIds,
18564
+ worldQueryDependencies: reads.worldQueries,
18565
+ onUnattributableValueRead: reads.recordUnattributableValueRead
18566
+ });
18567
+ } finally {
18568
+ subscriptions.setReads(subscription, reads);
18569
+ }
18570
+ if (!notify) return;
18571
+ const owner = readRowValue(slot.owner, ctx);
18572
+ subscription.handling = true;
18573
+ try {
18574
+ for (const target of [...subscription.targets]) {
18575
+ const handler = resolveNeoScriptChangeListenerHandler(slot, target, ctx);
18576
+ if (handler === null) continue;
18577
+ invokeDelegateValue(
18578
+ handler.invocation,
18579
+ [value],
18580
+ ctx,
18581
+ void 0,
18582
+ owner,
18583
+ "discarded",
18584
+ () => dispatchGetterSubscriptions(ctx)
18585
+ );
18586
+ }
18587
+ } finally {
18588
+ subscription.handling = false;
18589
+ }
18590
+ }
18255
18591
  function evalActionListenerInstruction(ins, scope, ctx, options) {
18256
18592
  const operator = ins.type === "addActionListener" /* addActionListener */ ? "+=" : "-=";
18257
18593
  if (ins.target.pointer.type === "variable" /* variable */) {
@@ -18428,6 +18764,7 @@ function applyOverlayStaticAssignment(memberId, value, ctx) {
18428
18764
  );
18429
18765
  }
18430
18766
  const bindings = writableStaticBindingMap(member, ctx);
18767
+ ctx.__getterSubscriptions?.noteStaticBinding(member.id);
18431
18768
  if (value === null || value === void 0) {
18432
18769
  if (isRequiredMember(member)) {
18433
18770
  throw new NSGetterRuntimeError(
@@ -18466,7 +18803,7 @@ function applyOverlayStaticAssignment(memberId, value, ctx) {
18466
18803
  `Static member '${member.name}' is bound to missing value '${currentId}'.`
18467
18804
  );
18468
18805
  }
18469
- ctx.beforeValueWrite?.(row2);
18806
+ observeValueWrite(ctx, row2);
18470
18807
  row2.value = value;
18471
18808
  row2.updatedAt = Date.now();
18472
18809
  invalidateEvaluatorIndexes(ctx);
@@ -25281,14 +25618,14 @@ function evalVariantApply(info, scope, ctx) {
25281
25618
  });
25282
25619
  for (const valueId of resolution.clearedValueIds) {
25283
25620
  const previous = readNeoScriptRuntimeRow(ctx, valueId);
25284
- if (previous) ctx.beforeValueWrite?.(previous);
25621
+ if (previous) observeValueWrite(ctx, previous);
25285
25622
  deleteRuntimeSessionValue(ctx, valueId);
25286
25623
  ctx.__valueOverlay?.delete(valueId);
25287
25624
  }
25288
25625
  for (const row of resolution.rows) {
25289
25626
  if (row.id === rootRow.id) continue;
25290
25627
  const previous = readNeoScriptRuntimeRow(ctx, row.id);
25291
- if (previous) ctx.beforeValueWrite?.(previous);
25628
+ if (previous) observeValueWrite(ctx, previous);
25292
25629
  ctx.__valueOverlay?.set(row.id, deepClonePlainData(row));
25293
25630
  }
25294
25631
  if (typeof receiver !== "object" || receiver === null || Array.isArray(receiver) || typeof resolution.root.value !== "object" || resolution.root.value === null || Array.isArray(resolution.root.value)) {
@@ -26506,7 +26843,7 @@ function cloneClassValueGraph(receiver, expectedClassId, ctx) {
26506
26843
  const edit = editChangeListenerSet(
26507
26844
  entry?.[write.memberId],
26508
26845
  [],
26509
- { kind: write.operation, target: write.listener },
26846
+ changeListenerWriteEdit(write),
26510
26847
  { inheritedComplete: false }
26511
26848
  );
26512
26849
  if (!edit.changed) continue;
@@ -33731,6 +34068,15 @@ function inspectListenerAnnotation(annotation2, parameters) {
33731
34068
  return;
33732
34069
  }
33733
34070
  const parameter3 = parameters.byName.get(name);
34071
+ if (!parameter3 && parameters.getters.has(name)) {
34072
+ errors.push(
34073
+ new CompileError(
34074
+ `'${name}' is a getter. Getter listeners can't be authored. Subscribe with '+=' from a function.`,
34075
+ expression.pos
34076
+ )
34077
+ );
34078
+ return;
34079
+ }
33734
34080
  if (!parameter3) {
33735
34081
  errors.push(
33736
34082
  new CompileError(
@@ -33774,7 +34120,10 @@ function createListenerAnnotationProvider(project, context = {}) {
33774
34120
  if (cached) return cached;
33775
34121
  const parameters = [];
33776
34122
  const byName = /* @__PURE__ */ new Map();
34123
+ const getters = /* @__PURE__ */ new Set();
33777
34124
  for (const symbol of declaration.members) {
34125
+ if (symbol.kind === "property" && symbol.computed === true && symbol.static !== true)
34126
+ getters.add(symbol.name);
33778
34127
  if (!isChangeListenerSymbol(symbol)) continue;
33779
34128
  if (memberAccessFailure({ project, context, owner: declaration, symbol }))
33780
34129
  continue;
@@ -33798,7 +34147,8 @@ function createListenerAnnotationProvider(project, context = {}) {
33798
34147
  const result = {
33799
34148
  namedOnly: true,
33800
34149
  parameters,
33801
- byName
34150
+ byName,
34151
+ getters
33802
34152
  };
33803
34153
  cache.set(key, result);
33804
34154
  return result;
@@ -34020,6 +34370,7 @@ var WRITE_TARGET_DESCRIPTIONS = {
34020
34370
  projectValue: "a stored project value",
34021
34371
  unknown: "something other than `this` or a local"
34022
34372
  };
34373
+ var ON_CHANGED_OPERATIONS_ERROR = "OnChanged supports only '+=', '-=', and Clear().";
34023
34374
  var FALLTHROUGH_LABELS = {
34024
34375
  function: "NSFunction",
34025
34376
  getter: "getter",
@@ -35362,6 +35713,12 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
35362
35713
  this.resolveExpression(statement.expr, scope);
35363
35714
  const debug = this.resolveDebugStatement(statement.expr, scope);
35364
35715
  if (debug) return debug;
35716
+ const listenerClear = this.resolveChangeListenerClear(
35717
+ statement.expr,
35718
+ scope,
35719
+ statement.pos
35720
+ );
35721
+ if (listenerClear) return listenerClear;
35365
35722
  const actionClear = this.resolveActionClear(
35366
35723
  statement.expr,
35367
35724
  scope,
@@ -35722,8 +36079,20 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
35722
36079
  const observed = this.resolveExpression(statement.target.receiver, scope);
35723
36080
  const observedType = neoScriptMemberLookupType(observed.type);
35724
36081
  const declared = observedType.kind === "named" && this.project.typeById.get(observedType.typeId)?.members.some((member) => member.name === "OnChanged");
35725
- if (!declared)
35726
- return this.resolveChangeSubscription(statement, observed, scope);
36082
+ if (!declared) {
36083
+ if (statement.op !== "+=" && statement.op !== "-=")
36084
+ throw new CompileError(ON_CHANGED_OPERATIONS_ERROR, statement.pos);
36085
+ return this.resolveChangeListener(
36086
+ statement.target,
36087
+ observed,
36088
+ scope,
36089
+ statement.pos,
36090
+ {
36091
+ kind: statement.op === "+=" ? "addChangeListener" /* AddChangeListener */ : "removeChangeListener" /* RemoveChangeListener */,
36092
+ handler: statement.value
36093
+ }
36094
+ );
36095
+ }
35727
36096
  }
35728
36097
  const local = statement.target.kind === "ident" ? scope.lookup(statement.target.name) : null;
35729
36098
  if (local?.readonlyBinding) {
@@ -35907,112 +36276,180 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
35907
36276
  pos
35908
36277
  );
35909
36278
  }
35910
- resolveChangeSubscription(statement, observed, scope) {
35911
- if (statement.op !== "+=" && statement.op !== "-=") {
35912
- throw new CompileError(
35913
- "OnChanged supports only '+=' and '-=' subscriptions.",
35914
- statement.pos
35915
- );
35916
- }
35917
- if (statement.target.kind !== "member") {
36279
+ /** `X.M.OnChanged.Clear()` empties M's listener set on X (P70, P106). */
36280
+ resolveChangeListenerClear(expression, scope, pos) {
36281
+ if (expression.kind !== "call" || expression.callee.kind !== "member" || expression.callee.name !== "Clear" || expression.callee.receiver.kind !== "member" || expression.callee.receiver.name !== "OnChanged")
36282
+ return null;
36283
+ const onChanged = expression.callee.receiver;
36284
+ const observed = this.resolveExpression(onChanged.receiver, scope);
36285
+ const observedType = neoScriptMemberLookupType(observed.type);
36286
+ if (observedType.kind === "named" && this.project.typeById.get(observedType.typeId)?.members.some((member) => member.name === "OnChanged"))
36287
+ return null;
36288
+ if (expression.callee.optional)
35918
36289
  throw new CompileError(
35919
- "OnChanged requires a declared member slot.",
35920
- statement.pos
36290
+ "OnChanged is never null, so optional-chained Clear is not supported.",
36291
+ pos
35921
36292
  );
35922
- }
35923
- if (statement.target.optional) {
36293
+ requireArgCount("Clear", expression.args, 0, pos);
36294
+ return this.resolveChangeListener(onChanged, observed, scope, pos, {
36295
+ kind: "clearChangeListeners" /* ClearChangeListeners */
36296
+ });
36297
+ }
36298
+ resolveChangeListener(onChanged, observed, scope, pos, edit) {
36299
+ if (onChanged.optional) {
35924
36300
  throw new CompileError(
35925
36301
  "OnChanged cannot use an optional subscription target.",
35926
- statement.pos
36302
+ pos
35927
36303
  );
35928
36304
  }
35929
36305
  if (observed.optionalChainType !== void 0) {
35930
36306
  throw new CompileError(
35931
36307
  "OnChanged cannot subscribe through optional chaining; narrow the receiver first.",
35932
- statement.pos
36308
+ pos
35933
36309
  );
35934
36310
  }
35935
36311
  const member = observed.symbol;
35936
36312
  if (!member)
35937
36313
  throw new CompileError(
35938
36314
  "OnChanged requires a declared member, not a local variable.",
35939
- statement.pos
36315
+ pos
35940
36316
  );
35941
- if (observed.pointer.type !== "keyOf" /* KeyOf */)
36317
+ if (observed.pointer.type === "staticMember" /* StaticMember */ || observed.pointer.type === "callGetter" /* CallGetter */ && observed.pointer.receiver.kind === "static") {
36318
+ const access = onChanged.receiver;
36319
+ const name = access.kind === "member" && access.receiver.kind === "ident" ? `${access.receiver.name}.${member.name}` : member.name;
35942
36320
  throw new CompileError(
35943
- "OnChanged requires a stored instance member, not a static member.",
35944
- statement.pos
36321
+ `OnChanged requires an instance member. '${name}' is static.`,
36322
+ pos
36323
+ );
36324
+ }
36325
+ if (observed.pointer.type === "callGetter" /* CallGetter */)
36326
+ return this.resolveGetterChangeListener(
36327
+ onChanged.receiver,
36328
+ observed,
36329
+ observed.pointer,
36330
+ member,
36331
+ scope,
36332
+ pos,
36333
+ edit
35945
36334
  );
36335
+ if (observed.pointer.type !== "keyOf" /* KeyOf */)
36336
+ throw new CompileError("OnChanged requires a declared member slot.", pos);
35946
36337
  if (!observed.pointer.memberId)
35947
36338
  throw new CompileError(
35948
36339
  "OnChanged cannot observe a collection entry.",
35949
- statement.pos
36340
+ pos
35950
36341
  );
35951
36342
  if (member.computed) {
35952
36343
  throw new CompileError(
35953
36344
  "OnChanged cannot observe a computed member.",
35954
- statement.pos
36345
+ pos
35955
36346
  );
35956
36347
  }
35957
36348
  if (member.readonlyDeclaration) {
35958
36349
  throw new CompileError(
35959
36350
  "OnChanged cannot observe a readonly declaration.",
35960
- statement.pos
36351
+ pos
35961
36352
  );
35962
36353
  }
35963
36354
  if (observed.type.kind === "delegate" || observed.type.kind === "action") {
35964
36355
  throw new CompileError(
35965
36356
  "OnChanged cannot observe a callable member.",
35966
- statement.pos
36357
+ pos
35967
36358
  );
35968
36359
  }
35969
36360
  const writability = observed.userData ? "runtime" /* Runtime */ : observed.writability;
35970
36361
  if (writability !== "save" /* Save */ && writability !== "session" /* Session */ && writability !== "runtime" /* Runtime */ && writability !== "local" /* Local */) {
35971
36362
  throw new CompileError(
35972
36363
  "OnChanged requires a member whose storage is writable at gameplay runtime.",
35973
- statement.pos
36364
+ pos
35974
36365
  );
35975
36366
  }
35976
36367
  this.validateConstructorWriteTarget(
35977
36368
  observed.pointer,
35978
- "subscribe to",
35979
- statement.pos,
36369
+ edit.kind === "clearChangeListeners" /* ClearChangeListeners */ ? "clear listeners on" : "subscribe to",
36370
+ pos,
35980
36371
  scope,
35981
36372
  false
35982
36373
  );
35983
- if (!statement.value) {
36374
+ const storageType = this.assignmentStorageType(
36375
+ onChanged.receiver,
36376
+ observed,
36377
+ null,
36378
+ scope
36379
+ );
36380
+ return this.changeListenerInstruction(
36381
+ edit,
36382
+ {
36383
+ owner: observed.pointer.keyOf.pointer,
36384
+ memberId: member.canonicalMemberId ?? observed.pointer.memberId,
36385
+ typeInfo: toWireType(storageType, this.project),
36386
+ writability
36387
+ },
36388
+ storageType,
36389
+ scope,
36390
+ pos
36391
+ );
36392
+ }
36393
+ /**
36394
+ * P106. A getter subscription lives only in the running client's memory:
36395
+ * nothing persists it, so construction replay would silently drop one.
36396
+ */
36397
+ resolveGetterChangeListener(getterRead, observed, pointer, member, scope, pos, edit) {
36398
+ if (pointer.dispatch === "base") {
35984
36399
  throw new CompileError(
35985
- "OnChanged subscription requires a handler member.",
35986
- statement.pos
36400
+ `OnChanged can't observe a base getter. Subscribe to '${member.name}' on the instance.`,
36401
+ pos
35987
36402
  );
35988
36403
  }
35989
- const storageType = this.assignmentStorageType(
35990
- statement.target.receiver,
36404
+ if (pointer.receiver.kind !== "instance") {
36405
+ throw new CompileError(
36406
+ `OnChanged requires an instance receiver for getter '${member.name}'.`,
36407
+ pos
36408
+ );
36409
+ }
36410
+ const kind = this.context.kind;
36411
+ if (isNeoScriptConstructionDocumentKind(kind) || kind === "migration") {
36412
+ throw new CompileError(
36413
+ edit.kind === "clearChangeListeners" /* ClearChangeListeners */ ? `A getter subscription lives only for the play session, so ${dialogueBodyKindLabel(kind)} can't clear one. Clear from a function.` : `A getter subscription lives only for the play session, so ${dialogueBodyKindLabel(kind)} can't make one. Subscribe from a function.`,
36414
+ pos
36415
+ );
36416
+ }
36417
+ const getterType = this.assignmentStorageType(
36418
+ getterRead,
35991
36419
  observed,
35992
36420
  null,
35993
36421
  scope
35994
36422
  );
35995
- const expected = {
35996
- kind: "action",
35997
- parameterTypes: [storageType]
35998
- };
36423
+ return this.changeListenerInstruction(
36424
+ edit,
36425
+ {
36426
+ owner: pointer.receiver.pointer,
36427
+ memberId: member.canonicalMemberId ?? pointer.memberId,
36428
+ typeInfo: toWireType(getterType, this.project),
36429
+ writability: "session" /* Session */
36430
+ },
36431
+ getterType,
36432
+ scope,
36433
+ pos
36434
+ );
36435
+ }
36436
+ changeListenerInstruction(edit, target, observedType, scope, pos) {
36437
+ if (edit.kind === "clearChangeListeners" /* ClearChangeListeners */)
36438
+ return { type: edit.kind, target };
36439
+ if (!edit.handler) {
36440
+ throw new CompileError(
36441
+ "OnChanged subscription requires a handler member.",
36442
+ pos
36443
+ );
36444
+ }
35999
36445
  const listener = this.resolveActionListener(
36000
- statement.value,
36001
- expected,
36446
+ edit.handler,
36447
+ { kind: "action", parameterTypes: [observedType] },
36002
36448
  scope,
36003
36449
  true,
36004
36450
  true
36005
36451
  );
36006
- return {
36007
- type: statement.op === "+=" ? "addChangeListener" /* AddChangeListener */ : "removeChangeListener" /* RemoveChangeListener */,
36008
- target: {
36009
- owner: observed.pointer.keyOf.pointer,
36010
- memberId: member.canonicalMemberId ?? observed.pointer.memberId,
36011
- typeInfo: toWireType(storageType, this.project),
36012
- writability
36013
- },
36014
- listener
36015
- };
36452
+ return { type: edit.kind, target, listener };
36016
36453
  }
36017
36454
  /**
36018
36455
  * P62 §3.2. `+=` / `-=` on an NSAction member are subscription operators,
@@ -38130,6 +38567,8 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
38130
38567
  entryUserData
38131
38568
  };
38132
38569
  }
38570
+ if (name === "OnChanged")
38571
+ throw new CompileError(ON_CHANGED_OPERATIONS_ERROR, pos);
38133
38572
  throw new CompileError(
38134
38573
  `Cannot read member '${name}' on ${this.describe(receiver.type)}`,
38135
38574
  pos
@@ -51186,7 +51625,7 @@ var ACTION_LISTENER_TARGET_KINDS = /* @__PURE__ */ new Set([
51186
51625
  26 /* NSAction */
51187
51626
  ]);
51188
51627
 
51189
- // node_modules/valibot/dist/index.mjs
51628
+ // ../node_modules/valibot/dist/index.mjs
51190
51629
  var store$4;
51191
51630
  var DEFAULT_CONFIG = {
51192
51631
  lang: void 0,
@@ -51274,18 +51713,6 @@ function _standardSchema(schema) {
51274
51713
  };
51275
51714
  return schema;
51276
51715
  }
51277
- var ValiError = class extends Error {
51278
- /**
51279
- * Creates a Valibot error with useful information.
51280
- *
51281
- * @param issues The error issues.
51282
- */
51283
- constructor(issues) {
51284
- super(issues[0].message);
51285
- this.name = "ValiError";
51286
- this.issues = issues;
51287
- }
51288
- };
51289
51716
  var EMOJI_REGEX = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
51290
51717
  // @__NO_SIDE_EFFECTS__
51291
51718
  function check(requirement, message$1) {
@@ -51956,11 +52383,6 @@ function variant(key, options, message$1) {
51956
52383
  }
51957
52384
  });
51958
52385
  }
51959
- function parse2(schema, input, config$1) {
51960
- const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig(config$1));
51961
- if (dataset.issues) throw new ValiError(dataset.issues);
51962
- return dataset.value;
51963
- }
51964
52386
  // @__NO_SIDE_EFFECTS__
51965
52387
  function pipe(...pipe$1) {
51966
52388
  return _standardSchema({
@@ -52584,6 +53006,43 @@ var schemaMemberSchema = variant("kind", [
52584
53006
  })
52585
53007
  ]);
52586
53008
 
53009
+ // node_modules/valibot/dist/index.mjs
53010
+ var store$42;
53011
+ var DEFAULT_CONFIG2 = {
53012
+ lang: void 0,
53013
+ message: void 0,
53014
+ abortEarly: void 0,
53015
+ abortPipeEarly: void 0
53016
+ };
53017
+ // @__NO_SIDE_EFFECTS__
53018
+ function getGlobalConfig2(config$1) {
53019
+ if (!config$1 && !store$42) return DEFAULT_CONFIG2;
53020
+ return {
53021
+ lang: config$1?.lang ?? store$42?.lang,
53022
+ message: config$1?.message,
53023
+ abortEarly: config$1?.abortEarly ?? store$42?.abortEarly,
53024
+ abortPipeEarly: config$1?.abortPipeEarly ?? store$42?.abortPipeEarly
53025
+ };
53026
+ }
53027
+ var ValiError = class extends Error {
53028
+ /**
53029
+ * Creates a Valibot error with useful information.
53030
+ *
53031
+ * @param issues The error issues.
53032
+ */
53033
+ constructor(issues) {
53034
+ super(issues[0].message);
53035
+ this.name = "ValiError";
53036
+ this.issues = issues;
53037
+ }
53038
+ };
53039
+ var EMOJI_REGEX2 = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
53040
+ function parse2(schema, input, config$1) {
53041
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig2(config$1));
53042
+ if (dataset.issues) throw new ValiError(dataset.issues);
53043
+ return dataset.value;
53044
+ }
53045
+
52587
53046
  // ../packages/neoscript-language/src/docs-text.ts
52588
53047
  var DOCS_TEXT_MAX_UTF8_BYTES2 = 16384;
52589
53048
  function docsTextUtf8ByteLength(value) {
@@ -55288,7 +55747,7 @@ var SPECIALIZED_RELATION_KINDS = /* @__PURE__ */ new Set([
55288
55747
 
55289
55748
  // ../packages/manifest/schema-contract/registry.mjs
55290
55749
  var PROJECT_SCHEMA_CONTRACT = Object.freeze({
55291
- cliVersion: "0.73.2",
55750
+ cliVersion: "0.75.0",
55292
55751
  projectFileUploadBatchSize: 32,
55293
55752
  documentRecords: {
55294
55753
  member: {
@@ -57010,7 +57469,7 @@ var PRESIGN_BATCH_SIZE = Math.min(
57010
57469
  PROJECT_FILE_UPLOAD_BATCH_SIZE
57011
57470
  );
57012
57471
 
57013
- // node_modules/@formatjs/icu-messageformat-parser/index.js
57472
+ // ../node_modules/@formatjs/icu-messageformat-parser/index.js
57014
57473
  var SPACE_SEPARATOR_REGEX = /[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/;
57015
57474
  var IDENTIFIER_PREFIX_REGEX = /([^\t-\r -\/:-@\[-\^`\{-~\x85\xA0-\xA7\xA9\xAB\xAC\xAE\xB0\xB1\xB6\xBB\xBF\xD7\xF7\u1680\u2000-\u200A\u2010-\u2029\u202F-\u203E\u2041-\u2053\u2055-\u205F\u2190-\u245F\u2500-\u2775\u2794-\u2BFF\u2E00-\u2E7F\u3000-\u3003\u3008-\u3020\u3030\uFD3E\uFD3F\uFE45\uFE46]*)/g;
57016
57475
  var SPACE_SEPARATOR_START_REGEX = new RegExp(`^${SPACE_SEPARATOR_REGEX.source}*`);
@@ -57270,7 +57729,8 @@ function makeEvaluatorContext(rawDocument, interceptor) {
57270
57729
  rootValue: base.rootValue,
57271
57730
  dialogueContext: null,
57272
57731
  callInterceptor: interceptor,
57273
- nativeFunctionErrorCheckBehavior: "strict"
57732
+ nativeFunctionErrorCheckBehavior: "strict",
57733
+ getterDispatch: "afterEntryStatement" /* AfterEntryStatement */
57274
57734
  });
57275
57735
  }
57276
57736
  function createSpecCompiler(document2) {
@@ -57844,6 +58304,7 @@ function copyMocks(source, target) {
57844
58304
  function cloneTestEnvironment(source) {
57845
58305
  const cloned = createTestEnvironment(snapshotEnvironmentDocument(source));
57846
58306
  copyMocks(source, cloned);
58307
+ cloned.context.__getterSubscriptions = source.context.__getterSubscriptions?.clone();
57847
58308
  return cloned;
57848
58309
  }
57849
58310
  function runCallback(environment, delegate, deadlineMs) {