@neocompose/cli 0.73.1 → 0.74.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
  }
@@ -14859,6 +14859,144 @@ function checkedCoordinate(value) {
14859
14859
  return value;
14860
14860
  }
14861
14861
 
14862
+ // ../src/runtime/neoscript/getter-subscriptions.ts
14863
+ var NeoScriptGetterSubscription = class {
14864
+ constructor(rowId, getterId, classId) {
14865
+ this.rowId = rowId;
14866
+ this.getterId = getterId;
14867
+ this.classId = classId;
14868
+ }
14869
+ rowId;
14870
+ getterId;
14871
+ classId;
14872
+ targets = [];
14873
+ reads = new InitializerReadSet();
14874
+ queued = false;
14875
+ /** Queued only to arm: its first read calls no handler. */
14876
+ arming = true;
14877
+ /** Its handlers are running. */
14878
+ handling = false;
14879
+ };
14880
+ var NO_SUBSCRIPTIONS = [];
14881
+ var staticReadKey = (memberId) => `static:${memberId}`;
14882
+ function* readKeys(reads) {
14883
+ yield* reads.valueIds;
14884
+ yield* reads.containerIds;
14885
+ yield* reads.containerEntryReadIds;
14886
+ for (const id of reads.staticMemberIds) yield staticReadKey(id);
14887
+ }
14888
+ var NeoScriptGetterSubscriptions = class _NeoScriptGetterSubscriptions {
14889
+ byRow = /* @__PURE__ */ new Map();
14890
+ byRead = /* @__PURE__ */ new Map();
14891
+ /** Reads no row id names, such as corpus or world queries, hear every write. */
14892
+ readingEverything = /* @__PURE__ */ new Set();
14893
+ pending = [];
14894
+ get empty() {
14895
+ return this.byRow.size === 0;
14896
+ }
14897
+ get(rowId, getterId) {
14898
+ return this.byRow.get(rowId)?.get(getterId);
14899
+ }
14900
+ add(subscription) {
14901
+ this.register(subscription);
14902
+ this.queue(subscription);
14903
+ }
14904
+ drop(subscription) {
14905
+ const getters = this.byRow.get(subscription.rowId);
14906
+ if (getters?.get(subscription.getterId) !== subscription) return;
14907
+ getters.delete(subscription.getterId);
14908
+ if (getters.size === 0) this.byRow.delete(subscription.rowId);
14909
+ this.unindex(subscription);
14910
+ }
14911
+ noteValueWrite(rowId) {
14912
+ this.noteRead(rowId);
14913
+ }
14914
+ noteStaticBinding(memberId) {
14915
+ this.noteRead(staticReadKey(memberId));
14916
+ }
14917
+ /** Replace what the subscription's last read touched. */
14918
+ setReads(subscription, reads) {
14919
+ this.unindex(subscription);
14920
+ subscription.reads = reads;
14921
+ if (this.get(subscription.rowId, subscription.getterId) !== subscription)
14922
+ return;
14923
+ if (reads.corpusReadReasons.size > 0 || reads.worldQueries.size > 0)
14924
+ this.readingEverything.add(subscription);
14925
+ for (const key of readKeys(reads)) {
14926
+ let subscriptions = this.byRead.get(key);
14927
+ if (subscriptions === void 0) {
14928
+ subscriptions = /* @__PURE__ */ new Set();
14929
+ this.byRead.set(key, subscriptions);
14930
+ }
14931
+ subscriptions.add(subscription);
14932
+ }
14933
+ }
14934
+ takePending() {
14935
+ if (this.pending.length === 0) return NO_SUBSCRIPTIONS;
14936
+ const pending = this.pending;
14937
+ this.pending = [];
14938
+ return pending;
14939
+ }
14940
+ /** A throw skipped `drained` from `next` on. They stay queued, so none goes deaf. */
14941
+ requeue(drained, next) {
14942
+ for (let index = next; index < drained.length; index += 1)
14943
+ this.pending.push(drained[index]);
14944
+ }
14945
+ /** A copy for a fresh runtime over the same rows, armed exactly as these are. */
14946
+ clone() {
14947
+ const cloned = new _NeoScriptGetterSubscriptions();
14948
+ const copies = /* @__PURE__ */ new Map();
14949
+ for (const getters of this.byRow.values()) {
14950
+ for (const source of getters.values()) {
14951
+ const copy = new NeoScriptGetterSubscription(
14952
+ source.rowId,
14953
+ source.getterId,
14954
+ source.classId
14955
+ );
14956
+ copy.targets.push(...source.targets);
14957
+ copy.arming = source.arming;
14958
+ copies.set(source, copy);
14959
+ cloned.register(copy);
14960
+ cloned.setReads(copy, source.reads);
14961
+ }
14962
+ }
14963
+ for (const source of this.pending) {
14964
+ const copy = copies.get(source);
14965
+ if (copy !== void 0) cloned.queue(copy);
14966
+ }
14967
+ return cloned;
14968
+ }
14969
+ register(subscription) {
14970
+ let getters = this.byRow.get(subscription.rowId);
14971
+ if (getters === void 0) {
14972
+ getters = /* @__PURE__ */ new Map();
14973
+ this.byRow.set(subscription.rowId, getters);
14974
+ }
14975
+ getters.set(subscription.getterId, subscription);
14976
+ }
14977
+ queue(subscription) {
14978
+ if (subscription.queued) return;
14979
+ subscription.queued = true;
14980
+ this.pending.push(subscription);
14981
+ }
14982
+ noteRead(key) {
14983
+ const subscriptions = this.byRead.get(key);
14984
+ if (subscriptions !== void 0)
14985
+ for (const subscription of subscriptions) this.queue(subscription);
14986
+ for (const subscription of this.readingEverything)
14987
+ if (!subscription.handling) this.queue(subscription);
14988
+ }
14989
+ unindex(subscription) {
14990
+ this.readingEverything.delete(subscription);
14991
+ for (const key of readKeys(subscription.reads)) {
14992
+ const subscriptions = this.byRead.get(key);
14993
+ if (subscriptions === void 0) continue;
14994
+ subscriptions.delete(subscription);
14995
+ if (subscriptions.size === 0) this.byRead.delete(key);
14996
+ }
14997
+ }
14998
+ };
14999
+
14862
15000
  // ../src/models/decimal/decimal-math.ts
14863
15001
  var DecimalOverflowError = class extends Error {
14864
15002
  };
@@ -15446,6 +15584,7 @@ function evalMemberById(vm, id) {
15446
15584
  return vm.members.find((a) => a.id === id) ?? null;
15447
15585
  }
15448
15586
  function evalValueById(ctx, id, runtimeValues, overlayValues) {
15587
+ ctx.__getterReads?.add(id);
15449
15588
  if (ctx.__removedValueIds?.has(id)) {
15450
15589
  ctx.valueDependencies?.add(id);
15451
15590
  return null;
@@ -16120,8 +16259,13 @@ function bindThisValue(ctx, thisValue) {
16120
16259
  const rowId = thisValue === ctx.thisValue ? ctx.thisValueRowId : trackedRowForValueReference(thisValue, ctx)?.id;
16121
16260
  return { ...ctx, thisValue, thisValueRowId: rowId };
16122
16261
  }
16262
+ function observeValueWrite(ctx, row) {
16263
+ ctx.beforeValueWrite?.(row);
16264
+ ctx.__getterSubscriptions?.noteValueWrite(row.id);
16265
+ }
16123
16266
  function assertStoredReplayMutableValue(value, ctx, label, key) {
16124
- if (ctx.storedConstructionReplay !== true && !ctx.assertValueWritable && !ctx.beforeValueWrite)
16267
+ const observesWrites = ctx.beforeValueWrite !== void 0 || ctx.__getterSubscriptions?.empty === false;
16268
+ if (ctx.storedConstructionReplay !== true && !ctx.assertValueWritable && !observesWrites)
16125
16269
  return;
16126
16270
  let rowId = findKnownRowIdByValueReference(value, ctx);
16127
16271
  if (rowId === null) return;
@@ -16140,7 +16284,7 @@ function assertStoredReplayMutableValue(value, ctx, label, key) {
16140
16284
  );
16141
16285
  if (ctx.__runtimeSessionValues?.has(rowId) !== true)
16142
16286
  ctx.assertValueWritable?.(rowId);
16143
- if (row) ctx.beforeValueWrite?.(row);
16287
+ if (row) observeValueWrite(ctx, row);
16144
16288
  if (ctx.__runtimeSessionValues?.has(rowId) === true) return;
16145
16289
  if (ctx.storedConstructionReplay !== true) return;
16146
16290
  throw new NSGetterRuntimeError(
@@ -16649,10 +16793,14 @@ function readNeoScriptRuntimeRow(ctx, valueId) {
16649
16793
  ctx.__valueOverlay
16650
16794
  );
16651
16795
  }
16796
+ function getterSubscriptionsFor(ctx) {
16797
+ return ctx.getterDispatch === "afterEntryStatement" /* AfterEntryStatement */ ? new NeoScriptGetterSubscriptions() : void 0;
16798
+ }
16652
16799
  function withEvaluationRuntime(ctx, writes) {
16653
16800
  if (ctx.__runtimeSessionValues !== void 0 && ctx.__executionState !== void 0 && ctx.__valueOverlay !== void 0) {
16654
16801
  ctx.__unorderedListProjections ??= createUnorderedListProjectionCache();
16655
16802
  ctx.__runtimeClassOverrides ??= /* @__PURE__ */ new Map();
16803
+ ctx.__getterSubscriptions ??= getterSubscriptionsFor(ctx);
16656
16804
  return ctx;
16657
16805
  }
16658
16806
  const overlay = ctx.__valueOverlay ?? (ctx.storedConstructionReplay === true ? /* @__PURE__ */ new Map() : buildValueOverlay(ctx.vm));
@@ -16677,6 +16825,7 @@ function withEvaluationRuntime(ctx, writes) {
16677
16825
  trigger: remap(ctx.dialogueContext.trigger)
16678
16826
  },
16679
16827
  __runtimeSessionValues: runtimeSessionValues,
16828
+ __getterSubscriptions: ctx.__getterSubscriptions ?? getterSubscriptionsFor(ctx),
16680
16829
  __copiedChangeListeners: ctx.__copiedChangeListeners ?? /* @__PURE__ */ new Map(),
16681
16830
  __runtimeClassOverrides: ctx.__runtimeClassOverrides ?? /* @__PURE__ */ new Map(),
16682
16831
  __saveStaticBindings: ctx.__saveStaticBindings ?? staticBindingMap(ctx.saveStaticBindings),
@@ -17680,6 +17829,7 @@ function validateTryInstruction(instruction) {
17680
17829
  function evalBody(body, scope, ctx, options, preparedFrame) {
17681
17830
  const state = ctx.__executionState;
17682
17831
  if (!state) return evalInstructions(body.instructions, scope, ctx, options);
17832
+ const hostBody = state.frames.length === 0 && ctx.__getterSubscriptions !== void 0;
17683
17833
  state.frames.push(
17684
17834
  preparedFrame ?? {
17685
17835
  name: body.source?.name ?? "<body>",
@@ -17688,7 +17838,14 @@ function evalBody(body, scope, ctx, options, preparedFrame) {
17688
17838
  }
17689
17839
  );
17690
17840
  try {
17691
- return evalInstructions(body.instructions, scope, ctx, options);
17841
+ if (!hostBody)
17842
+ return evalInstructions(body.instructions, scope, ctx, options);
17843
+ const result = evalInstructions(body.instructions, scope, ctx, {
17844
+ ...options,
17845
+ dispatchesGetterChanges: true
17846
+ });
17847
+ dispatchGetterSubscriptions(ctx);
17848
+ return result;
17692
17849
  } catch (error) {
17693
17850
  attachNeoScriptTrace(error, state);
17694
17851
  throw error;
@@ -18067,6 +18224,8 @@ function evalInstructions(instructions, scope, ctx, options) {
18067
18224
  return { kind: "continue" };
18068
18225
  }
18069
18226
  void touchUnknown(ins);
18227
+ if (options.dispatchesGetterChanges === true)
18228
+ dispatchGetterSubscriptions(ctx);
18070
18229
  } catch (error) {
18071
18230
  positionNeoScriptFrame(ctx, ins.source);
18072
18231
  if (ctx.__executionState)
@@ -18141,6 +18300,30 @@ function resolveNeoScriptChangeListenerSlot(ownerId, memberId, ctx) {
18141
18300
  typeInfo: changeListenerObservedType(raw, member, env, ctx)
18142
18301
  };
18143
18302
  }
18303
+ function resolveNeoScriptGetterListenerSlot(ownerId, memberId, ctx) {
18304
+ const owner = evalValueById(
18305
+ ctx,
18306
+ ownerId,
18307
+ ctx.__runtimeSessionValues,
18308
+ ctx.__valueOverlay
18309
+ );
18310
+ if (owner === null || owner.value === null) return null;
18311
+ const entry = changeListenerMemberOnOwner(owner, memberId, ctx);
18312
+ if (entry === void 0) return null;
18313
+ const member = evalMemberById(ctx.vm, entry.memberId);
18314
+ if (member === null || !isMemberNSProperty(member)) return null;
18315
+ if (isStaticMember(member)) return null;
18316
+ const env = runtimeGenericEnvForValueRow(owner, ctx, /* @__PURE__ */ new Set());
18317
+ const typeInfo = substituteRuntimeTypeInfo(member.returnTypeInfo, env, ctx);
18318
+ if (typeInfo.type === NS_TYPE_VOID) return null;
18319
+ return {
18320
+ owner,
18321
+ member,
18322
+ memberId: evaluatorResolutionCache(ctx).canonicalMemberId(member.id),
18323
+ schemaKey: entry.schemaKey,
18324
+ typeInfo
18325
+ };
18326
+ }
18144
18327
  function changeListenerObservedType(raw, member, env, ctx) {
18145
18328
  const runtime = runtimeTypeInfoForMember(raw, env, ctx, /* @__PURE__ */ new Set());
18146
18329
  if (!isMemberLookup(member)) return runtime;
@@ -18163,6 +18346,18 @@ function changeListenerObservedType(raw, member, env, ctx) {
18163
18346
  required: isRequiredMember(member)
18164
18347
  };
18165
18348
  }
18349
+ function readNeoScriptChangeListenerValue(slot, ctx) {
18350
+ const result = dispatchSchemaMember(
18351
+ readRowValue(slot.owner, ctx),
18352
+ slot.schemaKey,
18353
+ ctx
18354
+ );
18355
+ if (result.kind !== "ok")
18356
+ throw new NSGetterRuntimeError(
18357
+ `OnChanged cannot read '${slot.owner.id}/${slot.memberId}'.`
18358
+ );
18359
+ return result.value;
18360
+ }
18166
18361
  function resolveNeoScriptChangeListenerHandler(slot, target, ctx) {
18167
18362
  const raw = evalMemberById(ctx.vm, target.memberId);
18168
18363
  if (raw === null) return null;
@@ -18224,14 +18419,16 @@ function evalChangeListenerInstruction(ins, scope, ctx, options) {
18224
18419
  throw new NSGetterRuntimeError(
18225
18420
  "OnChanged requires an existing logical owner."
18226
18421
  );
18227
- const slot = resolveNeoScriptChangeListenerSlot(
18422
+ const stored = resolveNeoScriptChangeListenerSlot(
18228
18423
  owner.id,
18229
18424
  ins.target.memberId,
18230
18425
  ctx
18231
18426
  );
18427
+ const getter = stored === null ? resolveNeoScriptGetterListenerSlot(owner.id, ins.target.memberId, ctx) : null;
18428
+ const slot = stored ?? getter;
18232
18429
  if (slot === null)
18233
18430
  throw new NSGetterRuntimeError(
18234
- `OnChanged member '${ins.target.memberId}' is not an observable stored member of owner '${owner.id}'.`
18431
+ `OnChanged member '${ins.target.memberId}' is not an observable member or getter of owner '${owner.id}'.`
18235
18432
  );
18236
18433
  const listener = evalPointer(ins.listener, scope, ctx);
18237
18434
  if (!isMemberDelegateTarget(listener))
@@ -18243,6 +18440,15 @@ function evalChangeListenerInstruction(ins, scope, ctx, options) {
18243
18440
  throw new NSGetterRuntimeError(
18244
18441
  `OnChanged handler '${listener.memberId}' cannot accept the value of '${slot.member.name}' on '${owner.id}'.`
18245
18442
  );
18443
+ if (getter !== null) {
18444
+ editGetterSubscription(
18445
+ getter,
18446
+ handler.target,
18447
+ ins.type === "addChangeListener" /* addChangeListener */,
18448
+ ctx
18449
+ );
18450
+ return;
18451
+ }
18246
18452
  options.writes.push({
18247
18453
  kind: "changeListener",
18248
18454
  target: ins.target,
@@ -18252,6 +18458,95 @@ function evalChangeListenerInstruction(ins, scope, ctx, options) {
18252
18458
  listener: handler.target
18253
18459
  });
18254
18460
  }
18461
+ function editGetterSubscription(slot, target, add, ctx) {
18462
+ if ((ctx.__executionState?.constructionStack.length ?? 0) > 0)
18463
+ throw new NSGetterRuntimeError(
18464
+ "A getter subscription lives only for the play session, so a constructor can't make one. Subscribe from a function."
18465
+ );
18466
+ const subscriptions = ctx.__getterSubscriptions;
18467
+ if (subscriptions === void 0) return;
18468
+ let subscription = subscriptions.get(slot.owner.id, slot.memberId);
18469
+ const found = subscription?.targets.findIndex(
18470
+ (existing) => existing.memberId === target.memberId && existing.valueId === target.valueId
18471
+ ) ?? -1;
18472
+ if (!add) {
18473
+ if (subscription === void 0 || found < 0) return;
18474
+ subscription.targets.splice(found, 1);
18475
+ if (subscription.targets.length === 0) subscriptions.drop(subscription);
18476
+ return;
18477
+ }
18478
+ if (found >= 0) return;
18479
+ if (subscription === void 0) {
18480
+ subscription = new NeoScriptGetterSubscription(
18481
+ slot.owner.id,
18482
+ slot.memberId,
18483
+ slot.owner.classId
18484
+ );
18485
+ subscriptions.add(subscription);
18486
+ }
18487
+ subscription.targets.push(target);
18488
+ }
18489
+ function dispatchGetterSubscriptions(ctx) {
18490
+ const subscriptions = ctx.__getterSubscriptions;
18491
+ if (subscriptions === void 0) return;
18492
+ const draining = subscriptions.takePending();
18493
+ let next = 0;
18494
+ try {
18495
+ while (next < draining.length)
18496
+ dispatchGetterSubscription(draining[next++], subscriptions, ctx);
18497
+ } finally {
18498
+ subscriptions.requeue(draining, next);
18499
+ }
18500
+ }
18501
+ function dispatchGetterSubscription(subscription, subscriptions, ctx) {
18502
+ subscription.queued = false;
18503
+ const notify = !subscription.arming;
18504
+ subscription.arming = false;
18505
+ if (subscription.targets.length === 0) return;
18506
+ const slot = resolveNeoScriptGetterListenerSlot(
18507
+ subscription.rowId,
18508
+ subscription.getterId,
18509
+ ctx
18510
+ );
18511
+ if (slot === null || slot.owner.classId !== subscription.classId) {
18512
+ subscriptions.drop(subscription);
18513
+ return;
18514
+ }
18515
+ const reads = new InitializerReadSet();
18516
+ let value;
18517
+ try {
18518
+ value = readNeoScriptChangeListenerValue(slot, {
18519
+ ...ctx,
18520
+ __getterReads: reads.valueIds,
18521
+ containerDependencies: reads.containerIds,
18522
+ staticMemberDependencies: reads.staticMemberIds,
18523
+ worldQueryDependencies: reads.worldQueries,
18524
+ onUnattributableValueRead: reads.recordUnattributableValueRead
18525
+ });
18526
+ } finally {
18527
+ subscriptions.setReads(subscription, reads);
18528
+ }
18529
+ if (!notify) return;
18530
+ const owner = readRowValue(slot.owner, ctx);
18531
+ subscription.handling = true;
18532
+ try {
18533
+ for (const target of [...subscription.targets]) {
18534
+ const handler = resolveNeoScriptChangeListenerHandler(slot, target, ctx);
18535
+ if (handler === null) continue;
18536
+ invokeDelegateValue(
18537
+ handler.invocation,
18538
+ [value],
18539
+ ctx,
18540
+ void 0,
18541
+ owner,
18542
+ "discarded",
18543
+ () => dispatchGetterSubscriptions(ctx)
18544
+ );
18545
+ }
18546
+ } finally {
18547
+ subscription.handling = false;
18548
+ }
18549
+ }
18255
18550
  function evalActionListenerInstruction(ins, scope, ctx, options) {
18256
18551
  const operator = ins.type === "addActionListener" /* addActionListener */ ? "+=" : "-=";
18257
18552
  if (ins.target.pointer.type === "variable" /* variable */) {
@@ -18428,6 +18723,7 @@ function applyOverlayStaticAssignment(memberId, value, ctx) {
18428
18723
  );
18429
18724
  }
18430
18725
  const bindings = writableStaticBindingMap(member, ctx);
18726
+ ctx.__getterSubscriptions?.noteStaticBinding(member.id);
18431
18727
  if (value === null || value === void 0) {
18432
18728
  if (isRequiredMember(member)) {
18433
18729
  throw new NSGetterRuntimeError(
@@ -18466,7 +18762,7 @@ function applyOverlayStaticAssignment(memberId, value, ctx) {
18466
18762
  `Static member '${member.name}' is bound to missing value '${currentId}'.`
18467
18763
  );
18468
18764
  }
18469
- ctx.beforeValueWrite?.(row2);
18765
+ observeValueWrite(ctx, row2);
18470
18766
  row2.value = value;
18471
18767
  row2.updatedAt = Date.now();
18472
18768
  invalidateEvaluatorIndexes(ctx);
@@ -25281,14 +25577,14 @@ function evalVariantApply(info, scope, ctx) {
25281
25577
  });
25282
25578
  for (const valueId of resolution.clearedValueIds) {
25283
25579
  const previous = readNeoScriptRuntimeRow(ctx, valueId);
25284
- if (previous) ctx.beforeValueWrite?.(previous);
25580
+ if (previous) observeValueWrite(ctx, previous);
25285
25581
  deleteRuntimeSessionValue(ctx, valueId);
25286
25582
  ctx.__valueOverlay?.delete(valueId);
25287
25583
  }
25288
25584
  for (const row of resolution.rows) {
25289
25585
  if (row.id === rootRow.id) continue;
25290
25586
  const previous = readNeoScriptRuntimeRow(ctx, row.id);
25291
- if (previous) ctx.beforeValueWrite?.(previous);
25587
+ if (previous) observeValueWrite(ctx, previous);
25292
25588
  ctx.__valueOverlay?.set(row.id, deepClonePlainData(row));
25293
25589
  }
25294
25590
  if (typeof receiver !== "object" || receiver === null || Array.isArray(receiver) || typeof resolution.root.value !== "object" || resolution.root.value === null || Array.isArray(resolution.root.value)) {
@@ -33731,6 +34027,15 @@ function inspectListenerAnnotation(annotation2, parameters) {
33731
34027
  return;
33732
34028
  }
33733
34029
  const parameter3 = parameters.byName.get(name);
34030
+ if (!parameter3 && parameters.getters.has(name)) {
34031
+ errors.push(
34032
+ new CompileError(
34033
+ `'${name}' is a getter. Getter listeners can't be authored. Subscribe with '+=' from a function.`,
34034
+ expression.pos
34035
+ )
34036
+ );
34037
+ return;
34038
+ }
33734
34039
  if (!parameter3) {
33735
34040
  errors.push(
33736
34041
  new CompileError(
@@ -33774,7 +34079,10 @@ function createListenerAnnotationProvider(project, context = {}) {
33774
34079
  if (cached) return cached;
33775
34080
  const parameters = [];
33776
34081
  const byName = /* @__PURE__ */ new Map();
34082
+ const getters = /* @__PURE__ */ new Set();
33777
34083
  for (const symbol of declaration.members) {
34084
+ if (symbol.kind === "property" && symbol.computed === true && symbol.static !== true)
34085
+ getters.add(symbol.name);
33778
34086
  if (!isChangeListenerSymbol(symbol)) continue;
33779
34087
  if (memberAccessFailure({ project, context, owner: declaration, symbol }))
33780
34088
  continue;
@@ -33798,7 +34106,8 @@ function createListenerAnnotationProvider(project, context = {}) {
33798
34106
  const result = {
33799
34107
  namedOnly: true,
33800
34108
  parameters,
33801
- byName
34109
+ byName,
34110
+ getters
33802
34111
  };
33803
34112
  cache.set(key, result);
33804
34113
  return result;
@@ -35938,9 +36247,26 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
35938
36247
  "OnChanged requires a declared member, not a local variable.",
35939
36248
  statement.pos
35940
36249
  );
36250
+ if (observed.pointer.type === "staticMember" /* StaticMember */ || observed.pointer.type === "callGetter" /* CallGetter */ && observed.pointer.receiver.kind === "static") {
36251
+ const access = statement.target.receiver;
36252
+ const name = access.kind === "member" && access.receiver.kind === "ident" ? `${access.receiver.name}.${member.name}` : member.name;
36253
+ throw new CompileError(
36254
+ `OnChanged requires an instance member. '${name}' is static.`,
36255
+ statement.pos
36256
+ );
36257
+ }
36258
+ if (observed.pointer.type === "callGetter" /* CallGetter */)
36259
+ return this.resolveGetterSubscription(
36260
+ statement,
36261
+ statement.target.receiver,
36262
+ observed,
36263
+ observed.pointer,
36264
+ member,
36265
+ scope
36266
+ );
35941
36267
  if (observed.pointer.type !== "keyOf" /* KeyOf */)
35942
36268
  throw new CompileError(
35943
- "OnChanged requires a stored instance member, not a static member.",
36269
+ "OnChanged requires a declared member slot.",
35944
36270
  statement.pos
35945
36271
  );
35946
36272
  if (!observed.pointer.memberId)
@@ -36014,6 +36340,60 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
36014
36340
  listener
36015
36341
  };
36016
36342
  }
36343
+ /**
36344
+ * P106. A getter subscription lives only in the running client's memory:
36345
+ * nothing persists it, so construction replay would silently drop one.
36346
+ */
36347
+ resolveGetterSubscription(statement, getterRead, observed, pointer, member, scope) {
36348
+ if (pointer.dispatch === "base") {
36349
+ throw new CompileError(
36350
+ `OnChanged can't observe a base getter. Subscribe to '${member.name}' on the instance.`,
36351
+ statement.pos
36352
+ );
36353
+ }
36354
+ if (pointer.receiver.kind !== "instance") {
36355
+ throw new CompileError(
36356
+ `OnChanged requires an instance receiver for getter '${member.name}'.`,
36357
+ statement.pos
36358
+ );
36359
+ }
36360
+ const kind = this.context.kind;
36361
+ if (isNeoScriptConstructionDocumentKind(kind) || kind === "migration") {
36362
+ throw new CompileError(
36363
+ `A getter subscription lives only for the play session, so ${dialogueBodyKindLabel(kind)} can't make one. Subscribe from a function.`,
36364
+ statement.pos
36365
+ );
36366
+ }
36367
+ if (!statement.value) {
36368
+ throw new CompileError(
36369
+ "OnChanged subscription requires a handler member.",
36370
+ statement.pos
36371
+ );
36372
+ }
36373
+ const getterType = this.assignmentStorageType(
36374
+ getterRead,
36375
+ observed,
36376
+ null,
36377
+ scope
36378
+ );
36379
+ const listener = this.resolveActionListener(
36380
+ statement.value,
36381
+ { kind: "action", parameterTypes: [getterType] },
36382
+ scope,
36383
+ true,
36384
+ true
36385
+ );
36386
+ return {
36387
+ type: statement.op === "+=" ? "addChangeListener" /* AddChangeListener */ : "removeChangeListener" /* RemoveChangeListener */,
36388
+ target: {
36389
+ owner: pointer.receiver.pointer,
36390
+ memberId: member.canonicalMemberId ?? pointer.memberId,
36391
+ typeInfo: toWireType(getterType, this.project),
36392
+ writability: "session" /* Session */
36393
+ },
36394
+ listener
36395
+ };
36396
+ }
36017
36397
  /**
36018
36398
  * P62 §3.2. `+=` / `-=` on an NSAction member are subscription operators,
36019
36399
  * not arithmetic: the listener set *is* the member's value, so each one
@@ -51186,7 +51566,7 @@ var ACTION_LISTENER_TARGET_KINDS = /* @__PURE__ */ new Set([
51186
51566
  26 /* NSAction */
51187
51567
  ]);
51188
51568
 
51189
- // node_modules/valibot/dist/index.mjs
51569
+ // ../node_modules/valibot/dist/index.mjs
51190
51570
  var store$4;
51191
51571
  var DEFAULT_CONFIG = {
51192
51572
  lang: void 0,
@@ -51274,18 +51654,6 @@ function _standardSchema(schema) {
51274
51654
  };
51275
51655
  return schema;
51276
51656
  }
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
51657
  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
51658
  // @__NO_SIDE_EFFECTS__
51291
51659
  function check(requirement, message$1) {
@@ -51956,11 +52324,6 @@ function variant(key, options, message$1) {
51956
52324
  }
51957
52325
  });
51958
52326
  }
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
52327
  // @__NO_SIDE_EFFECTS__
51965
52328
  function pipe(...pipe$1) {
51966
52329
  return _standardSchema({
@@ -52584,6 +52947,43 @@ var schemaMemberSchema = variant("kind", [
52584
52947
  })
52585
52948
  ]);
52586
52949
 
52950
+ // node_modules/valibot/dist/index.mjs
52951
+ var store$42;
52952
+ var DEFAULT_CONFIG2 = {
52953
+ lang: void 0,
52954
+ message: void 0,
52955
+ abortEarly: void 0,
52956
+ abortPipeEarly: void 0
52957
+ };
52958
+ // @__NO_SIDE_EFFECTS__
52959
+ function getGlobalConfig2(config$1) {
52960
+ if (!config$1 && !store$42) return DEFAULT_CONFIG2;
52961
+ return {
52962
+ lang: config$1?.lang ?? store$42?.lang,
52963
+ message: config$1?.message,
52964
+ abortEarly: config$1?.abortEarly ?? store$42?.abortEarly,
52965
+ abortPipeEarly: config$1?.abortPipeEarly ?? store$42?.abortPipeEarly
52966
+ };
52967
+ }
52968
+ var ValiError = class extends Error {
52969
+ /**
52970
+ * Creates a Valibot error with useful information.
52971
+ *
52972
+ * @param issues The error issues.
52973
+ */
52974
+ constructor(issues) {
52975
+ super(issues[0].message);
52976
+ this.name = "ValiError";
52977
+ this.issues = issues;
52978
+ }
52979
+ };
52980
+ 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");
52981
+ function parse2(schema, input, config$1) {
52982
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig2(config$1));
52983
+ if (dataset.issues) throw new ValiError(dataset.issues);
52984
+ return dataset.value;
52985
+ }
52986
+
52587
52987
  // ../packages/neoscript-language/src/docs-text.ts
52588
52988
  var DOCS_TEXT_MAX_UTF8_BYTES2 = 16384;
52589
52989
  function docsTextUtf8ByteLength(value) {
@@ -55288,7 +55688,7 @@ var SPECIALIZED_RELATION_KINDS = /* @__PURE__ */ new Set([
55288
55688
 
55289
55689
  // ../packages/manifest/schema-contract/registry.mjs
55290
55690
  var PROJECT_SCHEMA_CONTRACT = Object.freeze({
55291
- cliVersion: "0.73.1",
55691
+ cliVersion: "0.74.0",
55292
55692
  projectFileUploadBatchSize: 32,
55293
55693
  documentRecords: {
55294
55694
  member: {
@@ -57010,7 +57410,7 @@ var PRESIGN_BATCH_SIZE = Math.min(
57010
57410
  PROJECT_FILE_UPLOAD_BATCH_SIZE
57011
57411
  );
57012
57412
 
57013
- // node_modules/@formatjs/icu-messageformat-parser/index.js
57413
+ // ../node_modules/@formatjs/icu-messageformat-parser/index.js
57014
57414
  var SPACE_SEPARATOR_REGEX = /[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/;
57015
57415
  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
57416
  var SPACE_SEPARATOR_START_REGEX = new RegExp(`^${SPACE_SEPARATOR_REGEX.source}*`);
@@ -57270,7 +57670,8 @@ function makeEvaluatorContext(rawDocument, interceptor) {
57270
57670
  rootValue: base.rootValue,
57271
57671
  dialogueContext: null,
57272
57672
  callInterceptor: interceptor,
57273
- nativeFunctionErrorCheckBehavior: "strict"
57673
+ nativeFunctionErrorCheckBehavior: "strict",
57674
+ getterDispatch: "afterEntryStatement" /* AfterEntryStatement */
57274
57675
  });
57275
57676
  }
57276
57677
  function createSpecCompiler(document2) {
@@ -57844,6 +58245,7 @@ function copyMocks(source, target) {
57844
58245
  function cloneTestEnvironment(source) {
57845
58246
  const cloned = createTestEnvironment(snapshotEnvironmentDocument(source));
57846
58247
  copyMocks(source, cloned);
58248
+ cloned.context.__getterSubscriptions = source.context.__getterSubscriptions?.clone();
57847
58249
  return cloned;
57848
58250
  }
57849
58251
  function runCallback(environment, delegate, deadlineMs) {