@neocompose/cli 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/neo.mjs +801 -131
  2. package/package.json +6 -4
package/dist/neo.mjs CHANGED
@@ -1149,7 +1149,7 @@ var init_projection = __esm({
1149
1149
  Custom: 7,
1150
1150
  Enum: 8,
1151
1151
  Lookup: 9,
1152
- NSGetter: 10,
1152
+ NSProperty: 10,
1153
1153
  Sprite: 11,
1154
1154
  Audio: 12,
1155
1155
  Function: 13,
@@ -1174,7 +1174,7 @@ var init_projection = __esm({
1174
1174
  [ATTRIBUTE_TYPE.Enum]: "NeoEnum",
1175
1175
  [ATTRIBUTE_TYPE.Lookup]: "NeoLookup",
1176
1176
  [ATTRIBUTE_TYPE.DialogueLookup]: "NeoDialogueLookup",
1177
- [ATTRIBUTE_TYPE.NSGetter]: "NeoGetter",
1177
+ [ATTRIBUTE_TYPE.NSProperty]: "NeoProperty",
1178
1178
  [ATTRIBUTE_TYPE.Sprite]: "NeoSprite",
1179
1179
  [ATTRIBUTE_TYPE.Audio]: "NeoAudio",
1180
1180
  [ATTRIBUTE_TYPE.Function]: "NeoFunction",
@@ -1197,7 +1197,8 @@ var init_projection = __esm({
1197
1197
  "createdAt",
1198
1198
  "updatedAt",
1199
1199
  "valueId",
1200
- "getter"
1200
+ "getter",
1201
+ "setter"
1201
1202
  ];
1202
1203
  TYPE_VOLATILE_FIELDS = [
1203
1204
  "projectId",
@@ -2180,17 +2181,19 @@ function reconstructProperty(file, property, context) {
2180
2181
  data.multiselect = shape.head === "IReadOnlyList";
2181
2182
  break;
2182
2183
  }
2183
- case ATTRIBUTE_TYPE.NSGetter: {
2184
+ case ATTRIBUTE_TYPE.NSProperty: {
2184
2185
  const code = namedString(marker, "Code", file);
2185
- if (code === void 0) {
2186
+ if (code === void 0 && typeof data.extendsAttributeId !== "string") {
2186
2187
  throw new SchemaParseError(
2187
- "[NeoGetter] requires a Code argument.",
2188
+ "[NeoProperty] requires a Code argument.",
2188
2189
  file,
2189
2190
  marker.line,
2190
2191
  marker.column
2191
2192
  );
2192
2193
  }
2193
- data.code = code;
2194
+ if (code !== void 0) data.code = code;
2195
+ const setterCode = namedString(marker, "SetterCode", file);
2196
+ if (setterCode !== void 0) data.setterCode = setterCode;
2194
2197
  const retJson = parseJsonArg(marker, "RetJson", file);
2195
2198
  if (retJson !== void 0) data.returnTypeInfo = retJson;
2196
2199
  break;
@@ -3631,11 +3634,12 @@ namespace NeoCompose.Schema
3631
3634
  }
3632
3635
 
3633
3636
  [AttributeUsage(AttributeTargets.Property)]
3634
- public sealed class NeoGetterAttribute : NeoSchemaMemberAttribute
3637
+ public sealed class NeoPropertyAttribute : NeoSchemaMemberAttribute
3635
3638
  {
3636
3639
  public string? Code { get; set; }
3640
+ public string? SetterCode { get; set; }
3637
3641
  public string? RetJson { get; set; }
3638
- public NeoGetterAttribute(string? id = null) : base(id) { }
3642
+ public NeoPropertyAttribute(string? id = null) : base(id) { }
3639
3643
  }
3640
3644
 
3641
3645
  [AttributeUsage(AttributeTargets.Property)]
@@ -3963,12 +3967,24 @@ function isGenericBindingsRecord(value) {
3963
3967
  if (Array.isArray(value)) return false;
3964
3968
  return Object.values(value).every(isGenericBinding);
3965
3969
  }
3970
+ function dedupeSchemaKeyOrder(order) {
3971
+ const seen = /* @__PURE__ */ new Set();
3972
+ const result = [];
3973
+ for (const schemaKey of order) {
3974
+ if (seen.has(schemaKey)) continue;
3975
+ seen.add(schemaKey);
3976
+ result.push(schemaKey);
3977
+ }
3978
+ return result;
3979
+ }
3966
3980
  function getCustomTypeSchemaKeyOrder(type) {
3967
3981
  const schemaKeys = Object.keys(type.schema);
3968
3982
  if (type.schemaKeyOrder === void 0) return schemaKeys;
3969
- const orderedKeys = [...type.schemaKeyOrder];
3983
+ const orderedKeys = dedupeSchemaKeyOrder(type.schemaKeyOrder);
3984
+ const seen = new Set(orderedKeys);
3970
3985
  for (const schemaKey of schemaKeys) {
3971
- if (orderedKeys.includes(schemaKey)) continue;
3986
+ if (seen.has(schemaKey)) continue;
3987
+ seen.add(schemaKey);
3972
3988
  orderedKeys.push(schemaKey);
3973
3989
  }
3974
3990
  return orderedKeys;
@@ -4171,11 +4187,15 @@ function isAttributeDialogueLookupBase(value) {
4171
4187
  if (typeof v.multiselect !== "boolean") return false;
4172
4188
  return v.dialogueGroupId === null || typeof v.dialogueGroupId === "string";
4173
4189
  }
4174
- function isAttributeNSGetterBase(value) {
4175
- const v = asTypedAttributeBase(value, 10 /* NSGetter */);
4190
+ function isAttributeNSPropertyBase(value) {
4191
+ const v = asTypedAttributeBase(value, 10 /* NSProperty */);
4176
4192
  if (v === null) return false;
4177
4193
  if (typeof v.code !== "string") return false;
4178
4194
  if (v.code.length === 0) return false;
4195
+ if (v.setterCode !== void 0 && typeof v.setterCode !== "string") {
4196
+ return false;
4197
+ }
4198
+ if (v.setterCode !== void 0 && v.setterCode.length === 0) return false;
4179
4199
  return isNSTypeInfo(v.returnTypeInfo);
4180
4200
  }
4181
4201
  function isAttributeFunctionBase(value) {
@@ -4226,7 +4246,7 @@ var init_attribute_types = __esm({
4226
4246
  AttributeType10[AttributeType10["Custom"] = 7] = "Custom";
4227
4247
  AttributeType10[AttributeType10["Enum"] = 8] = "Enum";
4228
4248
  AttributeType10[AttributeType10["Lookup"] = 9] = "Lookup";
4229
- AttributeType10[AttributeType10["NSGetter"] = 10] = "NSGetter";
4249
+ AttributeType10[AttributeType10["NSProperty"] = 10] = "NSProperty";
4230
4250
  AttributeType10[AttributeType10["Sprite"] = 11] = "Sprite";
4231
4251
  AttributeType10[AttributeType10["Audio"] = 12] = "Audio";
4232
4252
  AttributeType10[AttributeType10["Function"] = 13] = "Function";
@@ -4487,7 +4507,7 @@ var init_generics = __esm({
4487
4507
  0 /* Null */,
4488
4508
  9 /* Lookup */,
4489
4509
  18 /* DialogueLookup */,
4490
- 10 /* NSGetter */,
4510
+ 10 /* NSProperty */,
4491
4511
  13 /* Function */,
4492
4512
  21 /* Generic */
4493
4513
  ]);
@@ -6530,14 +6550,21 @@ function emitProperty(context, schemaKey, attributeRecord, memberNames, warnings
6530
6550
  csType = multiselect ? "IReadOnlyList<NeoDialogueLookupRef>" : "NeoDialogueLookupRef";
6531
6551
  break;
6532
6552
  }
6533
- case ATTRIBUTE_TYPE.NSGetter: {
6534
- if (typeof data.code !== "string") {
6553
+ case ATTRIBUTE_TYPE.NSProperty: {
6554
+ const isOverride = typeof data.extendsAttributeId === "string";
6555
+ if (!isOverride && typeof data.code !== "string") {
6535
6556
  throw new Error(
6536
- `Getter attribute "${attributeRecord.recordId}" is missing "code".`
6557
+ `NeoScript property "${attributeRecord.recordId}" is missing "code".`
6537
6558
  );
6538
6559
  }
6539
- delete remaining.code;
6540
- args.push(`Code = ${csVerbatimString(data.code)}`);
6560
+ if (typeof data.code === "string") {
6561
+ delete remaining.code;
6562
+ args.push(`Code = ${csVerbatimString(data.code)}`);
6563
+ }
6564
+ if (typeof data.setterCode === "string") {
6565
+ delete remaining.setterCode;
6566
+ args.push(`SetterCode = ${csVerbatimString(data.setterCode)}`);
6567
+ }
6541
6568
  if (data.returnTypeInfo !== void 0) {
6542
6569
  delete remaining.returnTypeInfo;
6543
6570
  args.push(
@@ -6885,9 +6912,10 @@ import {
6885
6912
  } from "node:fs";
6886
6913
  import { dirname as dirname2, join as join6 } from "node:path";
6887
6914
  async function runPull(workspace, options) {
6915
+ const destructive = options.force || options.reset;
6888
6916
  const hasBaseline = Object.keys(workspace.state.records).length > 0;
6889
6917
  let localByKey = /* @__PURE__ */ new Map();
6890
- if (hasBaseline && !options.force) {
6918
+ if (hasBaseline && !destructive) {
6891
6919
  const status = computeWorkspaceStatus(workspace);
6892
6920
  if (status.conflictedFiles.length > 0) {
6893
6921
  throw new Error(
@@ -6907,7 +6935,7 @@ ${status.parseErrors.map((error) => ` ${error.message}`).join("\n")}`
6907
6935
  ])
6908
6936
  );
6909
6937
  }
6910
- if (!options.force && workspace.state.headTransactionHash !== void 0) {
6938
+ if (!destructive && workspace.state.headTransactionHash !== void 0) {
6911
6939
  const convex = await createConvexClient(workspace);
6912
6940
  const signal = await convex.query(api2.projectExportData.schemaSignal, {
6913
6941
  projectId: workspace.config.projectId,
@@ -6933,7 +6961,7 @@ ${status.parseErrors.map((error) => ` ${error.message}`).join("\n")}`
6933
6961
  if (serverRecord.deleted) continue;
6934
6962
  const baseState = workspace.state.records[key];
6935
6963
  const local = localByKey.get(key);
6936
- if (options.force || baseState === void 0) {
6964
+ if (destructive || baseState === void 0) {
6937
6965
  plans.set(key, {
6938
6966
  emitData: serverRecord.data,
6939
6967
  serverHash: serverRecord.contentHash,
@@ -7338,7 +7366,7 @@ async function runInit(options) {
7338
7366
  config,
7339
7367
  state: readWorkspaceState(root)
7340
7368
  };
7341
- await runPull(workspace, { force: false });
7369
+ await runPull(workspace, { force: false, reset: false });
7342
7370
  }
7343
7371
  var init_init = __esm({
7344
7372
  "src/commands/init.ts"() {
@@ -7508,11 +7536,17 @@ var init_compile_error = __esm({
7508
7536
  CompileError = class extends Error {
7509
7537
  line;
7510
7538
  column;
7511
- constructor(message, pos) {
7539
+ unit;
7540
+ constructor(message, pos, unit) {
7512
7541
  super(`${pos.line}:${pos.column}: ${message}`);
7513
7542
  this.name = "CompileError";
7514
7543
  this.line = pos.line;
7515
7544
  this.column = pos.column;
7545
+ this.unit = unit;
7546
+ }
7547
+ withUnit(unit) {
7548
+ if (this.unit === void 0) this.unit = unit;
7549
+ return this;
7516
7550
  }
7517
7551
  };
7518
7552
  }
@@ -9545,6 +9579,7 @@ var init_resolver = __esm({
9545
9579
  init_project();
9546
9580
  init_neoscript();
9547
9581
  init_compile_error();
9582
+ init_core();
9548
9583
  Scope = class {
9549
9584
  constructor(parent) {
9550
9585
  this.parent = parent;
@@ -9628,11 +9663,27 @@ var init_resolver = __esm({
9628
9663
  }
9629
9664
  }
9630
9665
  } : null;
9666
+ if (ctx.mode === "setter" /* Setter */) {
9667
+ if (ctx.valueTypeInfo === void 0) {
9668
+ throw new Error("Resolver: Setter mode requires valueTypeInfo");
9669
+ }
9670
+ this.valueVariable = {
9671
+ id: "__value__",
9672
+ typeInfo: ctx.valueTypeInfo,
9673
+ pointer: {
9674
+ type: "value" /* value */,
9675
+ value: { typeInfo: ctx.valueTypeInfo, value: null }
9676
+ }
9677
+ };
9678
+ } else {
9679
+ this.valueVariable = null;
9680
+ }
9631
9681
  }
9632
9682
  ctx;
9633
9683
  thisVariable;
9634
9684
  rootVariable;
9635
9685
  contextVariable;
9686
+ valueVariable;
9636
9687
  /**
9637
9688
  * Synthesized `Root` type. The resolver fabricates this from the project's
9638
9689
  * root attribute ids so the body's `root.Assets` / `root.Save` /
@@ -9669,6 +9720,13 @@ var init_resolver = __esm({
9669
9720
  variableId: this.contextVariable.id
9670
9721
  });
9671
9722
  }
9723
+ if (this.valueVariable) {
9724
+ scope.define({
9725
+ name: "value",
9726
+ type: this.valueVariable.typeInfo,
9727
+ variableId: this.valueVariable.id
9728
+ });
9729
+ }
9672
9730
  if (this.ctx.migrationContext === true) {
9673
9731
  scope.define({
9674
9732
  name: "source",
@@ -9686,6 +9744,7 @@ var init_resolver = __esm({
9686
9744
  const instructions = this.resolveStatements(body.body, scope);
9687
9745
  const parameters = [this.thisVariable, this.rootVariable];
9688
9746
  if (this.contextVariable) parameters.push(this.contextVariable);
9747
+ if (this.valueVariable) parameters.push(this.valueVariable);
9689
9748
  return {
9690
9749
  parameters,
9691
9750
  instructions,
@@ -9728,6 +9787,12 @@ var init_resolver = __esm({
9728
9787
  resolveStatement(stmt, scope) {
9729
9788
  switch (stmt.kind) {
9730
9789
  case "varDecl": {
9790
+ if (this.valueVariable && stmt.name === "value") {
9791
+ throw new CompileError(
9792
+ "Cannot declare a local named 'value' because it is the setter value parameter.",
9793
+ stmt.pos
9794
+ );
9795
+ }
9731
9796
  const declared = this.resolveTypeAnnotation(stmt.type);
9732
9797
  const initResolved = this.resolveExpr(stmt.init, scope, declared);
9733
9798
  if (!isAssignable(initResolved.type, declared, this.ctx.types)) {
@@ -9804,7 +9869,7 @@ var init_resolver = __esm({
9804
9869
  }
9805
9870
  case "return": {
9806
9871
  const expectedReturn = this.currentExpectedReturn();
9807
- const inActionBody = this.ctx.mode === "action" /* Action */ && this.lambdaDepth === 0;
9872
+ const inActionBody = this.ctx.mode !== "getter" /* Getter */ && this.lambdaDepth === 0;
9808
9873
  if (stmt.expr === null) {
9809
9874
  if (inActionBody) {
9810
9875
  return {
@@ -9827,7 +9892,7 @@ var init_resolver = __esm({
9827
9892
  if (inActionBody) {
9828
9893
  if (resolved.type.type !== 0 /* Null */) {
9829
9894
  throw new CompileError(
9830
- `Action functions can only return null; got ${describeType(resolved.type)}`,
9895
+ `${this.ctx.mode === "setter" /* Setter */ ? "Setter" : "Action"} functions can only return null; got ${describeType(resolved.type)}`,
9831
9896
  stmt.pos
9832
9897
  );
9833
9898
  }
@@ -9873,15 +9938,17 @@ var init_resolver = __esm({
9873
9938
  );
9874
9939
  if (!localAssignmentEntry && !this.ctx.allowMutation) {
9875
9940
  throw new CompileError(
9876
- "Only local variable assignment is allowed in getter contexts; save mutations require an action.",
9941
+ "Only local variable assignment is allowed in getter contexts; save mutations require an action or setter.",
9877
9942
  stmt.pos
9878
9943
  );
9879
9944
  }
9880
- const writability = localAssignmentEntry ? "local" /* Local */ : this.validateWritableTarget(stmt.target, scope, stmt.pos);
9881
- if (!localAssignmentEntry) {
9882
- this.validateNonGetterAssignmentTarget(stmt.target, scope, stmt.pos);
9883
- }
9884
- const readTarget = this.resolveExpr(stmt.target, scope);
9945
+ const propertyTarget = localAssignmentEntry ? null : this.resolveNSPropertyAssignmentTarget(
9946
+ stmt.target,
9947
+ scope,
9948
+ stmt.pos
9949
+ );
9950
+ const writability = localAssignmentEntry ? "local" /* Local */ : propertyTarget !== null ? "setter" /* Setter */ : this.validateWritableTarget(stmt.target, scope, stmt.pos);
9951
+ const readTarget = propertyTarget ?? this.resolveExpr(stmt.target, scope);
9885
9952
  const target = localAssignmentEntry ? { pointer: readTarget.pointer, type: localAssignmentEntry.type } : readTarget;
9886
9953
  let value;
9887
9954
  if (stmt.op === "=") {
@@ -10668,7 +10735,7 @@ var init_resolver = __esm({
10668
10735
  if (attr === null) return "inherit" /* Inherit */;
10669
10736
  return this.getStorageResolver().declaredStorage(attr.id);
10670
10737
  }
10671
- validateNonGetterAssignmentTarget(expr, scope, pos) {
10738
+ validateNSPropertyCollectionMutationTarget(expr, scope, pos) {
10672
10739
  if (expr.kind !== "member") return;
10673
10740
  const attr = this.resolveMemberAttribute(
10674
10741
  expr.receiver,
@@ -10676,11 +10743,42 @@ var init_resolver = __esm({
10676
10743
  scope,
10677
10744
  pos
10678
10745
  );
10679
- if (attr?.type !== 10 /* NSGetter */) return;
10746
+ if (attr?.type !== 10 /* NSProperty */) return;
10680
10747
  throw new CompileError(
10681
- `Cannot assign to getter attribute '${attr.name}' because getter attributes are read-only.`,
10748
+ `Cannot call mutating methods on NeoScript property '${attr.name}'; assign a whole value via its setter instead.`,
10749
+ pos
10750
+ );
10751
+ }
10752
+ /** Resolve a whole-value assignment to a setter-backed NSProperty. */
10753
+ resolveNSPropertyAssignmentTarget(expr, scope, pos) {
10754
+ if (expr.kind !== "member") return null;
10755
+ const attr = this.resolveMemberAttribute(
10756
+ expr.receiver,
10757
+ expr.name,
10758
+ scope,
10682
10759
  pos
10683
10760
  );
10761
+ if (attr?.type !== 10 /* NSProperty */) return null;
10762
+ if (expr.optional === true) {
10763
+ throw new CompileError(
10764
+ `Cannot assign to NeoScript property '${attr.name}' through an optional receiver.`,
10765
+ pos
10766
+ );
10767
+ }
10768
+ const receiver = this.resolveExpr(expr.receiver, scope);
10769
+ if (!receiver.type.required) {
10770
+ throw new CompileError(
10771
+ `Cannot assign to NeoScript property '${attr.name}' through an optional receiver.`,
10772
+ pos
10773
+ );
10774
+ }
10775
+ if (!this.resolveNSPropertySetterSource(attr.id)) {
10776
+ throw new CompileError(
10777
+ `Cannot assign to NeoScript property '${attr.name}' because it does not declare a setter.`,
10778
+ pos
10779
+ );
10780
+ }
10781
+ return this.resolveExpr(expr, scope);
10684
10782
  }
10685
10783
  resolveMemberAttribute(receiverAst, name, scope, pos) {
10686
10784
  const receiver = this.resolveExpr(receiverAst, scope);
@@ -11060,8 +11158,8 @@ var init_resolver = __esm({
11060
11158
  pos
11061
11159
  );
11062
11160
  }
11063
- if (attr.type === 10 /* NSGetter */) {
11064
- const returnType = this.resolveNSGetterReturnType(attr.id);
11161
+ if (attr.type === 10 /* NSProperty */) {
11162
+ const returnType = this.resolveNSPropertyReturnType(attr.id);
11065
11163
  if (!returnType) {
11066
11164
  throw new CompileError(
11067
11165
  `Getter '${attr.name}' has no \`returnTypeInfo\` (broken override chain?)`,
@@ -11646,10 +11744,14 @@ var init_resolver = __esm({
11646
11744
  pos
11647
11745
  );
11648
11746
  }
11649
- const writability = localMutation ? "local" /* Local */ : this.validateWritableTarget(callee.receiver, scope, pos);
11650
11747
  if (!localMutation) {
11651
- this.validateNonGetterAssignmentTarget(callee.receiver, scope, pos);
11748
+ this.validateNSPropertyCollectionMutationTarget(
11749
+ callee.receiver,
11750
+ scope,
11751
+ pos
11752
+ );
11652
11753
  }
11754
+ const writability = localMutation ? "local" /* Local */ : this.validateWritableTarget(callee.receiver, scope, pos);
11653
11755
  const args = this.resolveCollectionMutationArgs(
11654
11756
  callee,
11655
11757
  expr.args,
@@ -11893,9 +11995,9 @@ var init_resolver = __esm({
11893
11995
  pos
11894
11996
  );
11895
11997
  }
11896
- if (signature.deferred && this.ctx.mode !== "action" /* Action */) {
11998
+ if (signature.deferred && this.ctx.mode === "getter" /* Getter */) {
11897
11999
  throw new CompileError(
11898
- `Deferred Function '${attr.name}' can only be called from dialogue action code.`,
12000
+ `Deferred Function '${attr.name}' can only be called from action or setter code.`,
11899
12001
  pos
11900
12002
  );
11901
12003
  }
@@ -12076,7 +12178,7 @@ var init_resolver = __esm({
12076
12178
  }
12077
12179
  /**
12078
12180
  * Maps an attribute to the TNSTypeInfo the compiler exposes to user code.
12079
- * NSGetter attributes expose their `returnTypeInfo`. Lookup attributes
12181
+ * NSProperty attributes expose their `returnTypeInfo`. Lookup attributes
12080
12182
  * resolve to the *referenced collection's entry type* so `lookup.field`
12081
12183
  * autocompletes naturally.
12082
12184
  *
@@ -12232,10 +12334,10 @@ var init_resolver = __esm({
12232
12334
  }
12233
12335
  return { type: 18 /* DialogueLookup */, required };
12234
12336
  }
12235
- case 10 /* NSGetter */:
12236
- if (!isAttributeNSGetterBase(attr)) {
12337
+ case 10 /* NSProperty */:
12338
+ if (!isAttributeNSPropertyBase(attr)) {
12237
12339
  throw new CompileError(
12238
- `NSGetter attribute ${attr.id} failed isAttributeNSGetterBase`,
12340
+ `NSProperty attribute ${attr.id} failed isAttributeNSPropertyBase`,
12239
12341
  { line: 1, column: 1 }
12240
12342
  );
12241
12343
  }
@@ -12418,26 +12520,38 @@ var init_resolver = __esm({
12418
12520
  return t;
12419
12521
  }
12420
12522
  /**
12421
- * Walks an NSGetter attribute's `extendsAttributeId` chain to find
12523
+ * Walks an NSProperty attribute's `extendsAttributeId` chain to find
12422
12524
  * the first non-`undefined` `returnTypeInfo`.
12423
12525
  *
12424
12526
  * Override rows don't carry their own `returnTypeInfo` (per spec
12425
12527
  * § 1.2) — they inherit from their parent. The walk terminates at
12426
12528
  * the first row with `returnTypeInfo` set, the chain end (no
12427
- * `extendsAttributeId`), or a non-NSGetter row (broken chain).
12529
+ * `extendsAttributeId`), or a non-NSProperty row (broken chain).
12428
12530
  */
12429
- resolveNSGetterReturnType(attrId) {
12531
+ resolveNSPropertyReturnType(attrId) {
12430
12532
  const found = walkExtendsAttributeChain(
12431
12533
  attrId,
12432
12534
  this.ctx.attributes,
12433
- // `requireType: AttributeType.NSGetter` only feeds NSGetter rows
12535
+ // `requireType: AttributeType.NSProperty` only feeds NSProperty rows
12434
12536
  // into this picker, but TS doesn't carry the filter through.
12435
- // `isAttributeNSGetterBase` narrows on the field set and returns
12537
+ // `isAttributeNSPropertyBase` narrows on the field set and returns
12436
12538
  // a typed `returnTypeInfo` directly. Override rows that lack
12437
12539
  // their own `returnTypeInfo` fail the guard, which is the
12438
12540
  // correct "keep walking the chain" signal.
12439
- (a) => isAttributeNSGetterBase(a) ? a.returnTypeInfo : void 0,
12440
- { requireType: 10 /* NSGetter */ }
12541
+ (a) => isAttributeNSPropertyBase(a) ? a.returnTypeInfo : void 0,
12542
+ { requireType: 10 /* NSProperty */ }
12543
+ );
12544
+ return found ?? null;
12545
+ }
12546
+ resolveNSPropertySetterSource(attrId) {
12547
+ const found = walkExtendsAttributeChain(
12548
+ attrId,
12549
+ this.ctx.attributes,
12550
+ (attribute) => {
12551
+ const setterCode = getField(attribute, "setterCode");
12552
+ return typeof setterCode === "string" ? setterCode : void 0;
12553
+ },
12554
+ { requireType: 10 /* NSProperty */ }
12441
12555
  );
12442
12556
  return found ?? null;
12443
12557
  }
@@ -12485,6 +12599,17 @@ function compileNSAction(code, ctx) {
12485
12599
  });
12486
12600
  return resolver.compileAction(ast);
12487
12601
  }
12602
+ function compileNSSetter(code, ctx) {
12603
+ const ast = parseFunctionBody(code);
12604
+ const resolver = new Resolver({
12605
+ ...ctx,
12606
+ returnTypeInfo: { type: 0 /* Null */, required: true },
12607
+ mode: "setter" /* Setter */,
12608
+ allowMutation: true,
12609
+ dialogueContext: null
12610
+ });
12611
+ return resolver.compileAction(ast);
12612
+ }
12488
12613
  var init_compile = __esm({
12489
12614
  "../src/database/neoscript/compile.ts"() {
12490
12615
  "use strict";
@@ -12807,6 +12932,19 @@ function evaluateNSAction(action, ctx) {
12807
12932
  }
12808
12933
  return { writes };
12809
12934
  }
12935
+ function evaluateNSSetter(setter, ctx, value, writes) {
12936
+ const scope = createTopLevelScope(ctx);
12937
+ scope.set("__value__", value);
12938
+ const result = evalInstructions(setter.instructions, scope, ctx, {
12939
+ allowFallthroughReturn: true,
12940
+ writes
12941
+ });
12942
+ if (result.kind === "return" && result.value !== null && result.value !== void 0) {
12943
+ throw new NSGetterRuntimeError(
12944
+ `Setter returned a non-null value: ${String(result.value)}`
12945
+ );
12946
+ }
12947
+ }
12810
12948
  function createTopLevelScope(ctx) {
12811
12949
  const scope = /* @__PURE__ */ new Map();
12812
12950
  scope.set("__this__", ctx.thisValue);
@@ -12869,6 +13007,21 @@ function evalInstructions(instructions, scope, ctx, options) {
12869
13007
  scope.set(ins.target.pointer.variableId, value);
12870
13008
  break;
12871
13009
  }
13010
+ if (ins.target.writability === "setter" /* Setter */) {
13011
+ if (!options.writes) {
13012
+ throw new NSGetterRuntimeError(
13013
+ "Setter calls cannot be evaluated in getter context."
13014
+ );
13015
+ }
13016
+ dispatchNSSetter(
13017
+ ins.target.pointer,
13018
+ value,
13019
+ scope,
13020
+ ctx,
13021
+ options.writes
13022
+ );
13023
+ break;
13024
+ }
12872
13025
  validateAssignmentTarget(ins.target.pointer, scope, ctx);
12873
13026
  if (!options.writes) {
12874
13027
  throw new NSGetterRuntimeError(
@@ -12878,7 +13031,8 @@ function evalInstructions(instructions, scope, ctx, options) {
12878
13031
  options.writes.push({
12879
13032
  kind: "assign",
12880
13033
  target: ins.target,
12881
- value
13034
+ value,
13035
+ ...resolvedDestinationFieldsForAssign(ins.target.pointer, scope, ctx)
12882
13036
  });
12883
13037
  break;
12884
13038
  }
@@ -12908,6 +13062,90 @@ function validateAssignmentTarget(target, scope, ctx) {
12908
13062
  throw new NSGetterRuntimeError(`List index out of bounds: ${index}`);
12909
13063
  }
12910
13064
  }
13065
+ function resolvedDestinationFieldsForAssign(target, scope, ctx) {
13066
+ if (target.type !== "keyOf" /* keyOf */) return {};
13067
+ const container = evalPointer(target.keyOf.pointer, scope, ctx);
13068
+ const containerValueId = findKnownRowIdByValueReference(container, ctx);
13069
+ if (containerValueId === null) return {};
13070
+ const key = evalPointer(target.keyOf.key, scope, ctx);
13071
+ if (typeof key !== "string" && typeof key !== "number") return {};
13072
+ return { resolvedDestination: { containerValueId, key } };
13073
+ }
13074
+ function resolvedDestinationFieldsForContainer(targetValue, ctx) {
13075
+ const containerValueId = findKnownRowIdByValueReference(targetValue, ctx);
13076
+ return containerValueId === null ? {} : { resolvedDestination: { containerValueId } };
13077
+ }
13078
+ function dispatchNSSetter(target, value, scope, ctx, writes) {
13079
+ if (target.type !== "callGetter" /* callGetter */) {
13080
+ throw new NSGetterRuntimeError(
13081
+ "Setter write target must be a callGetter pointer."
13082
+ );
13083
+ }
13084
+ const receiver = evalPointer(target.thisPointer, scope, ctx);
13085
+ if (receiver === null || receiver === void 0) {
13086
+ throw new NSGetterRuntimeError("Cannot invoke setter on a null receiver.");
13087
+ }
13088
+ const attributeId = resolveEffectiveNSPropertyAttributeId(
13089
+ target.attributeId,
13090
+ receiver,
13091
+ ctx
13092
+ );
13093
+ const stack = ctx.__setterCallStack ?? /* @__PURE__ */ new Set();
13094
+ if (stack.has(attributeId)) {
13095
+ const name = evalAttributeById(ctx.vm, attributeId)?.name ?? attributeId;
13096
+ throw new NSGetterRuntimeError(`Circular setter call: '${name}'.`);
13097
+ }
13098
+ const setter = resolveCompiledSetter(attributeId, ctx.vm.attributes);
13099
+ if (setter === null) {
13100
+ const name = evalAttributeById(ctx.vm, attributeId)?.name ?? attributeId;
13101
+ throw new NSGetterRuntimeError(
13102
+ `NeoScript property '${name}' has no compiled setter \u2014 save its code to compile it.`
13103
+ );
13104
+ }
13105
+ const innerStack = new Set(stack);
13106
+ innerStack.add(attributeId);
13107
+ evaluateNSSetter(
13108
+ setter,
13109
+ {
13110
+ ...ctx,
13111
+ thisValue: receiver,
13112
+ __setterCallStack: innerStack
13113
+ },
13114
+ value,
13115
+ writes
13116
+ );
13117
+ }
13118
+ function resolveEffectiveNSPropertyAttributeId(staticAttributeId, receiver, ctx) {
13119
+ const placement = findSchemaPlacement(staticAttributeId, ctx.vm.types);
13120
+ if (placement === null) return staticAttributeId;
13121
+ const row = ctx.vm.values.find((candidate) => candidate.value === receiver);
13122
+ if (row === void 0) return staticAttributeId;
13123
+ const runtimeTypeId = customTypeIdForValueRow(row, ctx);
13124
+ if (runtimeTypeId === void 0) return staticAttributeId;
13125
+ let chain;
13126
+ try {
13127
+ chain = resolveInheritanceChain(runtimeTypeId, ctx.vm.types);
13128
+ } catch {
13129
+ return staticAttributeId;
13130
+ }
13131
+ const entry = mergeSchemas(chain).find(
13132
+ (candidate) => candidate.schemaKey === placement.schemaKey
13133
+ );
13134
+ if (entry?.attributeId === null || entry?.attributeId === void 0) {
13135
+ return staticAttributeId;
13136
+ }
13137
+ const effective = evalAttributeById(ctx.vm, entry.attributeId);
13138
+ return effective?.type === 10 /* NSProperty */ ? entry.attributeId : staticAttributeId;
13139
+ }
13140
+ function resolveCompiledSetter(attributeId, attributes) {
13141
+ const found = walkExtendsAttributeChain(
13142
+ attributeId,
13143
+ attributes,
13144
+ (attribute) => attribute.setter,
13145
+ { requireType: 10 /* NSProperty */ }
13146
+ );
13147
+ return found ?? null;
13148
+ }
12911
13149
  function evalCollectionMutationInstruction(ins, scope, ctx, options) {
12912
13150
  const targetValue = evalPointer(ins.target.pointer, scope, ctx);
12913
13151
  const args = ins.args.map((arg) => evalPointer(arg, scope, ctx));
@@ -12929,7 +13167,8 @@ function evalCollectionMutationInstruction(ins, scope, ctx, options) {
12929
13167
  options.writes.push({
12930
13168
  kind: "add",
12931
13169
  target: ins.target,
12932
- value: isLookupSet ? lookupSetSelectionValue(args[0], ctx) : args[0]
13170
+ value: isLookupSet ? lookupSetSelectionValue(args[0], ctx) : args[0],
13171
+ ...resolvedDestinationFieldsForContainer(targetValue, ctx)
12933
13172
  });
12934
13173
  return;
12935
13174
  }
@@ -12937,7 +13176,8 @@ function evalCollectionMutationInstruction(ins, scope, ctx, options) {
12937
13176
  kind: "add",
12938
13177
  target: ins.target,
12939
13178
  key: String(args[0]),
12940
- value: args[1]
13179
+ value: args[1],
13180
+ ...resolvedDestinationFieldsForContainer(targetValue, ctx)
12941
13181
  });
12942
13182
  return;
12943
13183
  case "Remove" /* Remove */:
@@ -12945,27 +13185,31 @@ function evalCollectionMutationInstruction(ins, scope, ctx, options) {
12945
13185
  options.writes.push({
12946
13186
  kind: "remove",
12947
13187
  target: ins.target,
12948
- value: isLookupSet ? lookupSetSelectionValue(args[0], ctx) : args[0]
13188
+ value: isLookupSet ? lookupSetSelectionValue(args[0], ctx) : args[0],
13189
+ ...resolvedDestinationFieldsForContainer(targetValue, ctx)
12949
13190
  });
12950
13191
  return;
12951
13192
  }
12952
13193
  options.writes.push({
12953
13194
  kind: "remove",
12954
13195
  target: ins.target,
12955
- key: String(args[0])
13196
+ key: String(args[0]),
13197
+ ...resolvedDestinationFieldsForContainer(targetValue, ctx)
12956
13198
  });
12957
13199
  return;
12958
13200
  case "RemoveAt" /* RemoveAt */:
12959
13201
  options.writes.push({
12960
13202
  kind: "remove",
12961
13203
  target: ins.target,
12962
- index: numberArg(args[0], "RemoveAt index")
13204
+ index: numberArg(args[0], "RemoveAt index"),
13205
+ ...resolvedDestinationFieldsForContainer(targetValue, ctx)
12963
13206
  });
12964
13207
  return;
12965
13208
  case "Clear" /* Clear */:
12966
13209
  options.writes.push({
12967
13210
  kind: "clear",
12968
- target: ins.target
13211
+ target: ins.target,
13212
+ ...resolvedDestinationFieldsForContainer(targetValue, ctx)
12969
13213
  });
12970
13214
  return;
12971
13215
  }
@@ -13290,9 +13534,8 @@ function dispatchNSGetterById(attributeId, receiver, ctx) {
13290
13534
  const innerStack = new Set(stack);
13291
13535
  innerStack.add(attributeId);
13292
13536
  return evaluateNSGetter(getter, {
13293
- vm: ctx.vm,
13537
+ ...ctx,
13294
13538
  thisValue: receiver,
13295
- rootValue: ctx.rootValue,
13296
13539
  __getterCallStack: innerStack
13297
13540
  });
13298
13541
  }
@@ -13301,7 +13544,7 @@ function resolveCompiledGetter(attrId, attributes) {
13301
13544
  attrId,
13302
13545
  attributes,
13303
13546
  (a) => a.getter,
13304
- { requireType: 10 /* NSGetter */ }
13547
+ { requireType: 10 /* NSProperty */ }
13305
13548
  );
13306
13549
  return found ?? null;
13307
13550
  }
@@ -13441,7 +13684,7 @@ function dispatchSchemaMember(receiver, schemaKey, ctx) {
13441
13684
  if (entry.attributeId === null) return { kind: "no-info" };
13442
13685
  const attr = evalAttributeById(ctx.vm, entry.attributeId);
13443
13686
  if (!attr) return { kind: "no-info" };
13444
- if (attr.type === 10 /* NSGetter */) {
13687
+ if (attr.type === 10 /* NSProperty */) {
13445
13688
  if (!resolveCompiledGetter(entry.attributeId, ctx.vm.attributes)) {
13446
13689
  return { kind: "no-info" };
13447
13690
  }
@@ -14338,6 +14581,71 @@ function resolveThisType(thisRef, document) {
14338
14581
  `--this "${thisRef}" is not a known type name, type id, or value id.`
14339
14582
  );
14340
14583
  }
14584
+ function resolveNSPropertyScriptTarget(attributeRef, document) {
14585
+ if (attributeRef === null) {
14586
+ throw new Error(
14587
+ "Setter mode requires --attribute <id-or-name> so `this` and `value` can be typed."
14588
+ );
14589
+ }
14590
+ const matches = document.attributes.filter(
14591
+ (attribute) => attribute.type === 10 && (attribute.id === attributeRef || attribute.name === attributeRef)
14592
+ );
14593
+ if (matches.length === 0) {
14594
+ throw new Error(
14595
+ `--attribute "${attributeRef}" is not a known NeoScript property id or name.`
14596
+ );
14597
+ }
14598
+ if (matches.length > 1) {
14599
+ throw new Error(
14600
+ `--attribute "${attributeRef}" matches multiple NeoScript properties; pass the stable attribute id.`
14601
+ );
14602
+ }
14603
+ return matches[0];
14604
+ }
14605
+ function resolveAttributeContainingType(attribute, document) {
14606
+ const attributeId = attribute.id;
14607
+ if (typeof attributeId !== "string") {
14608
+ throw new Error(
14609
+ "Setter mode selected a NeoScript property without a stable id."
14610
+ );
14611
+ }
14612
+ const matches = document.types.filter((type) => {
14613
+ const schema = type.schema;
14614
+ return isObjectRecord2(schema) && Object.values(schema).includes(attributeId);
14615
+ });
14616
+ if (matches.length === 0) {
14617
+ throw new Error(
14618
+ `NeoScript property "${String(attribute.name ?? attributeId)}" is not placed on a custom type.`
14619
+ );
14620
+ }
14621
+ if (matches.length > 1) {
14622
+ throw new Error(
14623
+ `NeoScript property "${String(attribute.name ?? attributeId)}" is placed on multiple custom types; pass a stable override attribute id that identifies one placement.`
14624
+ );
14625
+ }
14626
+ return matches[0];
14627
+ }
14628
+ function resolveScriptPropertyReturnTypeInfo(attribute, attributes) {
14629
+ let current = attribute;
14630
+ const visited = /* @__PURE__ */ new Set();
14631
+ while (current !== void 0) {
14632
+ if (current.returnTypeInfo !== void 0) {
14633
+ return current.returnTypeInfo;
14634
+ }
14635
+ const parentId = current.extendsAttributeId;
14636
+ if (typeof parentId !== "string") break;
14637
+ if (visited.has(parentId)) {
14638
+ throw new Error(
14639
+ `Cannot resolve NeoScript property return type: circular attribute chain at "${parentId}".`
14640
+ );
14641
+ }
14642
+ visited.add(parentId);
14643
+ current = attributes.find((candidate) => candidate.id === parentId);
14644
+ }
14645
+ throw new Error(
14646
+ `NeoScript property "${String(attribute.name ?? attribute.id)}" has no returnTypeInfo in its base chain.`
14647
+ );
14648
+ }
14341
14649
  function resolveThisValue(thisRef, document) {
14342
14650
  if (thisRef === null) return null;
14343
14651
  const value = document.values.find((entry) => entry.id === thisRef);
@@ -14379,7 +14687,18 @@ async function runScript(workspace, command, options) {
14379
14687
  return;
14380
14688
  }
14381
14689
  const source = readSource(options);
14382
- const thisType = resolveThisType(options.thisRef, document);
14690
+ if (options.mode === "setter" && command !== "check" && command !== "compile") {
14691
+ throw new Error(
14692
+ "Setter mode supports only `neo script check` and `neo script compile` in v1; eval/apply require a concrete receiver and transactional write semantics."
14693
+ );
14694
+ }
14695
+ const setterAttribute = options.mode === "setter" ? resolveNSPropertyScriptTarget(options.attributeRef, document) : null;
14696
+ if (setterAttribute !== null && options.returns !== null) {
14697
+ throw new Error(
14698
+ "Setter mode derives the value type from --attribute; do not pass --returns."
14699
+ );
14700
+ }
14701
+ const thisType = setterAttribute === null ? resolveThisType(options.thisRef, document) : resolveAttributeContainingType(setterAttribute, document);
14383
14702
  const compileContext = {
14384
14703
  project: document.project,
14385
14704
  attributes: document.attributes,
@@ -14391,14 +14710,32 @@ async function runScript(workspace, command, options) {
14391
14710
  };
14392
14711
  let compiled;
14393
14712
  try {
14394
- compiled = command === "apply" || options.mode === "action" ? compileNSAction(source, compileContext) : compileNSGetter(source, compileContext);
14713
+ if (options.mode === "setter") {
14714
+ if (setterAttribute === null) {
14715
+ throw new Error("Setter mode requires an NSProperty attribute target.");
14716
+ }
14717
+ compiled = compileNSSetter(source, {
14718
+ project: document.project,
14719
+ attributes: document.attributes,
14720
+ types: document.types,
14721
+ enums: document.enums,
14722
+ thisType,
14723
+ valueTypeInfo: resolveScriptPropertyReturnTypeInfo(
14724
+ setterAttribute,
14725
+ document.attributes
14726
+ )
14727
+ });
14728
+ } else {
14729
+ compiled = command === "apply" || options.mode === "action" ? compileNSAction(source, compileContext) : compileNSGetter(source, compileContext);
14730
+ }
14395
14731
  } catch (error) {
14396
14732
  if (error instanceof CompileError) {
14397
14733
  const report2 = {
14398
14734
  ok: false,
14399
14735
  error: error.message,
14400
14736
  line: error.line,
14401
- column: error.column
14737
+ column: error.column,
14738
+ unit: options.mode === "setter" ? "setter" : void 0
14402
14739
  };
14403
14740
  const hint = options.returns === null && error.message.includes("not assignable to declared return type") ? '\n(eval defaults to an unknown return type \u2014 pass --returns, e.g. --returns "string[]")' : "";
14404
14741
  console.log(
@@ -14556,40 +14893,86 @@ function runCheckAll(document, options) {
14556
14893
  const results = [];
14557
14894
  for (const attribute of document.attributes) {
14558
14895
  if (attribute.type !== 10) continue;
14559
- if (typeof attribute.code !== "string") continue;
14560
- checked += 1;
14896
+ if (typeof attribute.code !== "string" && typeof attribute.setterCode !== "string") {
14897
+ continue;
14898
+ }
14561
14899
  const thisType = typeByAttributeId.get(String(attribute.id)) ?? null;
14562
- try {
14563
- compileNSGetter(attribute.code, {
14564
- project: document.project,
14565
- attributes: document.attributes,
14566
- types: document.types,
14567
- enums: document.enums,
14568
- thisType,
14569
- returnTypeInfo: attribute.returnTypeInfo ?? RETURN_SHORTHANDS.unknown,
14570
- dialogueContext: null
14900
+ const context = {
14901
+ project: document.project,
14902
+ attributes: document.attributes,
14903
+ types: document.types,
14904
+ enums: document.enums,
14905
+ thisType,
14906
+ returnTypeInfo: resolveScriptPropertyReturnTypeInfo(
14907
+ attribute,
14908
+ document.attributes
14909
+ ),
14910
+ dialogueContext: null
14911
+ };
14912
+ const checks = [];
14913
+ if (typeof attribute.code === "string") {
14914
+ checks.push({
14915
+ unit: "getter",
14916
+ run: () => {
14917
+ compileNSGetter(attribute.code, context);
14918
+ }
14571
14919
  });
14572
- results.push({ id: attribute.id, name: attribute.name, ok: true });
14573
- } catch (error) {
14574
- failures += 1;
14575
- const message = error instanceof CompileError ? `${error.line}:${error.column} ${error.message}` : error instanceof Error ? error.message : String(error);
14576
- results.push({
14577
- id: attribute.id,
14578
- name: attribute.name,
14579
- ok: false,
14580
- error: message
14920
+ }
14921
+ if (typeof attribute.setterCode === "string") {
14922
+ checks.push({
14923
+ unit: "setter",
14924
+ run: () => {
14925
+ compileNSSetter(
14926
+ attribute.setterCode,
14927
+ {
14928
+ project: document.project,
14929
+ attributes: document.attributes,
14930
+ types: document.types,
14931
+ enums: document.enums,
14932
+ thisType,
14933
+ valueTypeInfo: resolveScriptPropertyReturnTypeInfo(
14934
+ attribute,
14935
+ document.attributes
14936
+ )
14937
+ }
14938
+ );
14939
+ }
14581
14940
  });
14582
- if (!options.json) {
14583
- console.log(
14584
- `FAIL ${String(attribute.name)} (${String(attribute.id)}): ${message}`
14585
- );
14941
+ }
14942
+ for (const check of checks) {
14943
+ checked += 1;
14944
+ try {
14945
+ check.run();
14946
+ results.push({
14947
+ id: attribute.id,
14948
+ name: attribute.name,
14949
+ unit: check.unit,
14950
+ ok: true
14951
+ });
14952
+ } catch (error) {
14953
+ failures += 1;
14954
+ const message = error instanceof CompileError ? `${error.line}:${error.column} ${error.message}` : error instanceof Error ? error.message : String(error);
14955
+ results.push({
14956
+ id: attribute.id,
14957
+ name: attribute.name,
14958
+ unit: check.unit,
14959
+ ok: false,
14960
+ error: message
14961
+ });
14962
+ if (!options.json) {
14963
+ console.log(
14964
+ `FAIL ${String(attribute.name)} (${String(attribute.id)}) [${check.unit}]: ${message}`
14965
+ );
14966
+ }
14586
14967
  }
14587
14968
  }
14588
14969
  }
14589
14970
  if (options.json) {
14590
14971
  console.log(JSON.stringify({ checked, failures, results }, null, 2));
14591
14972
  } else {
14592
- console.log(`${checked} getter(s) checked, ${failures} failure(s).`);
14973
+ console.log(
14974
+ `${checked} property body/bodies checked, ${failures} failure(s).`
14975
+ );
14593
14976
  }
14594
14977
  if (failures > 0) process.exitCode = 1;
14595
14978
  }
@@ -14837,6 +15220,17 @@ function resolveWriteTarget(write, env, migrationName) {
14837
15220
  `Migration "${migrationName}" produced a "${write.kind}" collection mutation; the runner applies scalar assignments only (creating/deleting value records is not yet supported).`
14838
15221
  );
14839
15222
  }
15223
+ const resolved = write.resolvedDestination;
15224
+ if (resolved !== void 0 && resolved.key !== void 0) {
15225
+ const container2 = env.valueById.get(resolved.containerValueId)?.value;
15226
+ const leaf2 = Array.isArray(container2) && typeof resolved.key === "number" ? container2[resolved.key] : isObjectRecord2(container2) ? container2[String(resolved.key)] : void 0;
15227
+ if (typeof leaf2 !== "string") {
15228
+ throw new Error(
15229
+ `Migration "${migrationName}" resolved its assignment to container "${resolved.containerValueId}", but key "${String(resolved.key)}" has no stored value id.`
15230
+ );
15231
+ }
15232
+ return { leafValueId: leaf2 };
15233
+ }
14840
15234
  const target = write.target;
14841
15235
  if (!isObjectRecord2(target) || !isObjectRecord2(target.pointer)) {
14842
15236
  throw new Error(
@@ -14920,6 +15314,11 @@ function resolveContainerMap(pointer, env, migrationName) {
14920
15314
  );
14921
15315
  }
14922
15316
  function resolveCollectionTarget(write, env, migrationName) {
15317
+ if (write.resolvedDestination !== void 0) {
15318
+ return {
15319
+ containerValueId: write.resolvedDestination.containerValueId
15320
+ };
15321
+ }
14923
15322
  const target = isObjectRecord2(write.target) ? write.target : {};
14924
15323
  const pointer = isObjectRecord2(target.pointer) ? target.pointer : {};
14925
15324
  if (pointer.type === "keyOf" && isObjectRecord2(pointer.keyOf)) {
@@ -15526,7 +15925,7 @@ async function runBranch(workspace, subcommand, positional, fromRef) {
15526
15925
  success(
15527
15926
  `Switched to ${color.bold(displayLabel(target))} ${color.dim(`(${target.id})`)}. Pulling\u2026`
15528
15927
  );
15529
- await runPull(workspace, { force: true });
15928
+ await runPull(workspace, { force: true, reset: false });
15530
15929
  return;
15531
15930
  }
15532
15931
  if (subcommand === "delete") {
@@ -15758,7 +16157,7 @@ async function runMerge(workspace, sourceRef, dryRun, runMigrations = false) {
15758
16157
  }
15759
16158
  if (merged > 0) {
15760
16159
  console.log("Pulling merged state...");
15761
- await runPull(workspace, { force: false });
16160
+ await runPull(workspace, { force: false, reset: false });
15762
16161
  }
15763
16162
  if (runMigrations) {
15764
16163
  const { runMigrate: runMigrate2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
@@ -16640,7 +17039,13 @@ function checkWriteOwnership(write, compiledAction, label, ctx, world, violate)
16640
17039
  );
16641
17040
  return;
16642
17041
  }
16643
- const rowId = resolveWriteRowId(target, compiledAction, ctx, world);
17042
+ const rowId = resolveWriteRowId(
17043
+ write,
17044
+ target,
17045
+ compiledAction,
17046
+ ctx,
17047
+ world
17048
+ );
16644
17049
  if (rowId === null) {
16645
17050
  violate(
16646
17051
  label,
@@ -16690,7 +17095,15 @@ function targetLiteralKey(target) {
16690
17095
  if (!isObjectRecord2(key) || !isObjectRecord2(key.value)) return null;
16691
17096
  return typeof key.value.value === "string" ? key.value.value : null;
16692
17097
  }
16693
- function resolveWriteRowId(target, compiledAction, ctx, world) {
17098
+ function resolveWriteRowId(write, target, compiledAction, ctx, world) {
17099
+ const resolved = write.resolvedDestination;
17100
+ if (resolved !== void 0) {
17101
+ if (write.kind !== "assign") return resolved.containerValueId;
17102
+ if (resolved.key === void 0) return null;
17103
+ const container = world.valueById.get(resolved.containerValueId)?.value;
17104
+ const leaf = Array.isArray(container) && typeof resolved.key === "number" ? container[resolved.key] : isObjectRecord2(container) ? container[String(resolved.key)] : void 0;
17105
+ return typeof leaf === "string" && world.valueById.has(leaf) ? leaf : null;
17106
+ }
16694
17107
  const pointer = isObjectRecord2(target.pointer) ? target.pointer : target;
16695
17108
  const evalPointer2 = (p) => {
16696
17109
  const getter = {
@@ -17898,6 +18311,7 @@ var init_export = __esm({
17898
18311
  // src/commands/push.ts
17899
18312
  var push_exports = {};
17900
18313
  __export(push_exports, {
18314
+ prepareNSPropertySetterChanges: () => prepareNSPropertySetterChanges,
17901
18315
  rewriteFilesFromState: () => rewriteFilesFromState,
17902
18316
  runPush: () => runPush
17903
18317
  });
@@ -17962,6 +18376,7 @@ ${status.parseErrors.map((error) => ` ${error.message}`).join("\n")}`
17962
18376
  console.log("Nothing to push \u2014 working copy is clean.");
17963
18377
  return;
17964
18378
  }
18379
+ prepareNSPropertySetterChanges(workspace, status);
17965
18380
  assignPendingIds(status.changes);
17966
18381
  const now = Date.now();
17967
18382
  for (const change of status.changes) {
@@ -17972,15 +18387,12 @@ ${status.parseErrors.map((error) => ` ${error.message}`).join("\n")}`
17972
18387
  action: compileMigrationAction(status, change.nextData)
17973
18388
  };
17974
18389
  }
17975
- if (change.recordKind === "attribute" && change.nextData.type === 10 && typeof change.nextData.code === "string" && change.nextData.code.trim().length > 0) {
17976
- change.nextData = {
17977
- ...change.nextData,
17978
- getter: await compileAttributeGetter(
17979
- workspace,
17980
- status,
17981
- change.nextData
17982
- )
17983
- };
18390
+ if (change.recordKind === "attribute" && change.nextData.type === 10) {
18391
+ change.nextData = await compileNSPropertyChange(
18392
+ workspace,
18393
+ status,
18394
+ change.nextData
18395
+ );
17984
18396
  }
17985
18397
  if (change.kind === "create") {
17986
18398
  change.nextData = {
@@ -18191,7 +18603,7 @@ function rewriteFilesFromState(workspace) {
18191
18603
  workspace.state.records[key] = nextState;
18192
18604
  }
18193
18605
  }
18194
- async function compileAttributeGetter(workspace, status, attributeData) {
18606
+ async function compileNSPropertyChange(workspace, status, attributeData) {
18195
18607
  const types = [];
18196
18608
  const attributes = [];
18197
18609
  const enums = [];
@@ -18208,17 +18620,170 @@ async function compileAttributeGetter(workspace, status, attributeData) {
18208
18620
  }) ?? null;
18209
18621
  const { raw } = await fetchProjectDocument(workspace);
18210
18622
  if (typeof raw.project !== "object" || raw.project === null) {
18211
- throw new Error("Cannot compile getter: project record is unavailable.");
18623
+ throw new Error(
18624
+ "Cannot compile NeoScript property: project record is unavailable."
18625
+ );
18626
+ }
18627
+ const returnTypeInfo = resolveNSPropertyReturnTypeInfo(
18628
+ attributeData,
18629
+ attributes
18630
+ );
18631
+ const next = { ...attributeData };
18632
+ if (typeof attributeData.code === "string") {
18633
+ next.getter = compileNSGetter(attributeData.code, {
18634
+ project: raw.project,
18635
+ attributes,
18636
+ types,
18637
+ enums,
18638
+ thisType,
18639
+ returnTypeInfo,
18640
+ dialogueContext: null
18641
+ });
18642
+ } else {
18643
+ delete next.getter;
18644
+ }
18645
+ if (typeof attributeData.setterCode === "string") {
18646
+ next.setter = compileNSSetter(attributeData.setterCode, {
18647
+ project: raw.project,
18648
+ attributes,
18649
+ types,
18650
+ enums,
18651
+ thisType,
18652
+ valueTypeInfo: returnTypeInfo
18653
+ });
18654
+ } else {
18655
+ delete next.setter;
18656
+ }
18657
+ return next;
18658
+ }
18659
+ function prepareNSPropertySetterChanges(workspace, status) {
18660
+ const attributes = /* @__PURE__ */ new Map();
18661
+ for (const record of status.reconstructed.values()) {
18662
+ if (record.recordKind !== "attribute") continue;
18663
+ if (typeof record.fullData.id !== "string") continue;
18664
+ attributes.set(record.fullData.id, record.fullData);
18665
+ }
18666
+ const changesById = /* @__PURE__ */ new Map();
18667
+ for (const change of status.changes) {
18668
+ if (change.recordKind !== "attribute") continue;
18669
+ changesById.set(change.recordId, change);
18670
+ if (change.nextData === void 0) continue;
18671
+ if (change.nextData.type === 10) {
18672
+ if (typeof change.nextData.code !== "string") {
18673
+ delete change.nextData.getter;
18674
+ }
18675
+ if (typeof change.nextData.setterCode !== "string") {
18676
+ delete change.nextData.setter;
18677
+ }
18678
+ }
18679
+ attributes.set(change.recordId, change.nextData);
18680
+ }
18681
+ const removedDeclarations = status.changes.filter(
18682
+ (change) => change.recordKind === "attribute" && change.nextData?.type === 10 && isObjectRecord2(change.baseData) && typeof change.baseData.setterCode === "string" && typeof change.nextData.setterCode !== "string" && !ancestorDeclaresSetter(change.nextData, attributes)
18683
+ ).map((change) => change.recordId);
18684
+ for (const declarationId of removedDeclarations) {
18685
+ for (const [attributeId, attribute] of attributes) {
18686
+ if (attributeId === declarationId) continue;
18687
+ if (!attributeDescendsFrom(attribute, declarationId, attributes)) {
18688
+ continue;
18689
+ }
18690
+ if (typeof attribute.setterCode !== "string" && attribute.setter === void 0) {
18691
+ continue;
18692
+ }
18693
+ const next = { ...attribute };
18694
+ delete next.setterCode;
18695
+ delete next.setter;
18696
+ attributes.set(attributeId, next);
18697
+ const key = recordStateKey("attribute", attributeId);
18698
+ const reconstructed = status.reconstructed.get(key);
18699
+ if (reconstructed !== void 0) reconstructed.fullData = next;
18700
+ const existingChange = changesById.get(attributeId);
18701
+ if (existingChange !== void 0) {
18702
+ existingChange.nextData = next;
18703
+ continue;
18704
+ }
18705
+ const base = workspace.state.records[key];
18706
+ if (base === void 0 || reconstructed === void 0) {
18707
+ throw new Error(
18708
+ `Cannot cascade removal of NeoScript property setter to override "${attributeId}": its working-copy state is missing.`
18709
+ );
18710
+ }
18711
+ const cascadeChange = {
18712
+ kind: "update",
18713
+ recordKind: "attribute",
18714
+ recordId: attributeId,
18715
+ file: reconstructed.file,
18716
+ nextData: next,
18717
+ baseData: base.data,
18718
+ baseContentHash: base.contentHash,
18719
+ casBaseHash: base.conflictServerHash ?? base.contentHash
18720
+ };
18721
+ status.changes.push(cascadeChange);
18722
+ changesById.set(attributeId, cascadeChange);
18723
+ }
18724
+ }
18725
+ for (const attribute of attributes.values()) {
18726
+ if (attribute.type !== 10) continue;
18727
+ if (typeof attribute.extendsAttributeId !== "string") continue;
18728
+ if (typeof attribute.setterCode !== "string") continue;
18729
+ if (ancestorDeclaresSetter(attribute, attributes)) continue;
18730
+ throw new Error(
18731
+ `Override NeoScript property "${String(attribute.name ?? attribute.id)}" cannot add a setter because its base chain does not declare one.`
18732
+ );
18212
18733
  }
18213
- return compileNSGetter(String(attributeData.code), {
18214
- project: raw.project,
18215
- attributes,
18216
- types,
18217
- enums,
18218
- thisType,
18219
- returnTypeInfo: attributeData.returnTypeInfo,
18220
- dialogueContext: null
18221
- });
18734
+ }
18735
+ function ancestorDeclaresSetter(attribute, attributes) {
18736
+ let parentId = typeof attribute.extendsAttributeId === "string" ? attribute.extendsAttributeId : null;
18737
+ const visited = /* @__PURE__ */ new Set();
18738
+ while (parentId !== null) {
18739
+ if (visited.has(parentId)) {
18740
+ throw new Error(
18741
+ `Cannot resolve NeoScript property setter inheritance: circular attribute chain at "${parentId}".`
18742
+ );
18743
+ }
18744
+ visited.add(parentId);
18745
+ const parent = attributes.get(parentId);
18746
+ if (parent === void 0) {
18747
+ throw new Error(
18748
+ `Cannot resolve NeoScript property setter inheritance: missing parent attribute "${parentId}".`
18749
+ );
18750
+ }
18751
+ if (typeof parent.setterCode === "string") return true;
18752
+ parentId = typeof parent.extendsAttributeId === "string" ? parent.extendsAttributeId : null;
18753
+ }
18754
+ return false;
18755
+ }
18756
+ function attributeDescendsFrom(attribute, ancestorId, attributes) {
18757
+ let parentId = typeof attribute.extendsAttributeId === "string" ? attribute.extendsAttributeId : null;
18758
+ const visited = /* @__PURE__ */ new Set();
18759
+ while (parentId !== null) {
18760
+ if (parentId === ancestorId) return true;
18761
+ if (visited.has(parentId)) return false;
18762
+ visited.add(parentId);
18763
+ const parent = attributes.get(parentId);
18764
+ if (parent === void 0) return false;
18765
+ parentId = typeof parent.extendsAttributeId === "string" ? parent.extendsAttributeId : null;
18766
+ }
18767
+ return false;
18768
+ }
18769
+ function resolveNSPropertyReturnTypeInfo(attribute, attributes) {
18770
+ let current = attribute;
18771
+ const visited = /* @__PURE__ */ new Set();
18772
+ while (current !== void 0) {
18773
+ if (current.returnTypeInfo !== void 0) return current.returnTypeInfo;
18774
+ const parentId = current.extendsAttributeId;
18775
+ if (typeof parentId !== "string") break;
18776
+ if (visited.has(parentId)) {
18777
+ throw new Error(
18778
+ `Cannot compile NeoScript property "${String(attribute.id)}": circular attribute inheritance at "${parentId}".`
18779
+ );
18780
+ }
18781
+ visited.add(parentId);
18782
+ current = attributes.find((candidate) => candidate.id === parentId);
18783
+ }
18784
+ throw new Error(
18785
+ `Cannot compile NeoScript property "${String(attribute.id)}": its returnTypeInfo could not be resolved from the base chain.`
18786
+ );
18222
18787
  }
18223
18788
  function compileMigrationAction(status, migrationData) {
18224
18789
  const types = [];
@@ -18301,7 +18866,7 @@ async function runDev(workspace, options) {
18301
18866
  try {
18302
18867
  refreshState();
18303
18868
  console.log(`[dev] ${label} \u2014 pulling...`);
18304
- await runPull(workspace, { force: false });
18869
+ await runPull(workspace, { force: false, reset: false });
18305
18870
  if (pendingSignalHash !== null) {
18306
18871
  workspace.state.headTransactionHash = pendingSignalHash;
18307
18872
  lastSeenHash = pendingSignalHash;
@@ -18635,11 +19200,7 @@ async function runWhoami(apiBaseUrl) {
18635
19200
  console.log(`Signed in as ${name} <${email}> at ${apiBaseUrl}`);
18636
19201
  }
18637
19202
 
18638
- // src/main.ts
18639
- init_status();
18640
- init_workspace();
18641
- init_ui();
18642
- var DEFAULT_API_BASE_URL = "http://localhost:3000";
19203
+ // src/args.ts
18643
19204
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
18644
19205
  "dry-run",
18645
19206
  "json",
@@ -18655,7 +19216,52 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
18655
19216
  "migrate",
18656
19217
  "server",
18657
19218
  "replace",
18658
- "generate-ids"
19219
+ "reset",
19220
+ "generate-ids",
19221
+ "help"
19222
+ ]);
19223
+ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
19224
+ "accept-bump",
19225
+ "all",
19226
+ "api",
19227
+ "attribute",
19228
+ "bump",
19229
+ "commit",
19230
+ "dir",
19231
+ "dry-run",
19232
+ "entries",
19233
+ "file",
19234
+ "force",
19235
+ "from",
19236
+ "generate-ids",
19237
+ "help",
19238
+ "into",
19239
+ "json",
19240
+ "key",
19241
+ "kind",
19242
+ "migrate",
19243
+ "mine",
19244
+ "mode",
19245
+ "out",
19246
+ "profile",
19247
+ "project",
19248
+ "push",
19249
+ "replace",
19250
+ "reset",
19251
+ "returns",
19252
+ "save",
19253
+ "save-project",
19254
+ "server",
19255
+ "skip-invalid",
19256
+ "status",
19257
+ "summary",
19258
+ "target",
19259
+ "template",
19260
+ "theirs",
19261
+ "this",
19262
+ "token-stdin",
19263
+ "type",
19264
+ "version"
18659
19265
  ]);
18660
19266
  function parseArgs(argv) {
18661
19267
  const [command = null, ...rest] = argv;
@@ -18689,6 +19295,19 @@ function stringFlag(args, name) {
18689
19295
  function boolFlag(args, name) {
18690
19296
  return args.flags.get(name) === true;
18691
19297
  }
19298
+ function assertKnownFlags(args) {
19299
+ for (const name of args.flags.keys()) {
19300
+ if (KNOWN_FLAGS.has(name)) continue;
19301
+ const hint = args.command === null ? "Run `neo help` for usage." : `Run \`neo ${args.command} --help\` for usage.`;
19302
+ throw new Error(`Unknown flag "--${name}". ${hint}`);
19303
+ }
19304
+ }
19305
+
19306
+ // src/main.ts
19307
+ init_status();
19308
+ init_workspace();
19309
+ init_ui();
19310
+ var DEFAULT_API_BASE_URL = "http://localhost:3000";
18692
19311
  function profileFlag(args) {
18693
19312
  const value = stringFlag(args, "profile");
18694
19313
  if (value === null) return null;
@@ -18717,7 +19336,7 @@ ${h("Start")}
18717
19336
  whoami ${d("[--api <url>]")}
18718
19337
 
18719
19338
  ${h("Working copy")}
18720
- pull ${d("[--force]")} push ${d("[--dry-run] [--summary <text>] [--accept-bump]")}
19339
+ pull ${d("[--force|--reset]")} push ${d("[--dry-run] [--summary <text>] [--accept-bump]")}
18721
19340
  status ${d("[--json]")} diff ${d("[--json]")}
18722
19341
  dev ${d("[--push]")} resolve ${d("[--mine|--theirs]")}
18723
19342
 
@@ -18731,13 +19350,58 @@ ${h("Content & scripts")}
18731
19350
  values ${d("list | get | set | create | delete | ...")} loc ${d("locales | list | set | archive | restore | ...")}
18732
19351
  dialogue ${d("list | show | node | group | priority-group")}
18733
19352
  records ${d("get | query")} files ${d("list | delete")}
18734
- script ${d("check | eval | apply")} migrate ${d("new | list | check | run | prune")}
19353
+ script ${d("check | eval | apply | compile [--mode getter|action|setter] [--attribute <id-or-name>]")}
19354
+ migrate ${d("new | list | check | run | prune")}
18735
19355
 
18736
19356
  Run any command with missing arguments in a terminal and ${h("neo")} will ask.
18737
19357
  `;
18738
19358
  }
19359
+ function commandHelp(command) {
19360
+ const h = color.bold;
19361
+ const d = color.dim;
19362
+ if (command === "pull") {
19363
+ return `${h("neo pull")} \u2014 synchronize the schema working copy
19364
+
19365
+ ${h("Usage")}
19366
+ neo pull ${d("[--force|--reset]")}
19367
+
19368
+ ${h("Flags")}
19369
+ --force ${d("Discard local edits and take the current server schema.")}
19370
+ --reset ${d("Destructively regenerate the entire working copy and schema marker scaffold, even when the sync head is unchanged.")}
19371
+
19372
+ Use ${h("--reset")} for schema-vocabulary upgrades such as NeoGetter \u2192
19373
+ NeoProperty. It does not attempt to parse the old generated marker files.
19374
+ `;
19375
+ }
19376
+ if (command === "push") {
19377
+ return `${h("neo push")} \u2014 commit the working copy to the server
19378
+
19379
+ ${h("Usage")}
19380
+ neo push ${d("[--dry-run] [--summary <text>] [--accept-bump]")}
19381
+
19382
+ ${h("Flags")}
19383
+ --dry-run ${d("Preview the change set; commit nothing.")}
19384
+ --summary <text> ${d("Attach a summary message to the transaction.")}
19385
+ --accept-bump ${d("Accept a server-required version bump instead of aborting.")}
19386
+
19387
+ Push sends the ${h("entire")} working copy as one atomic compare-and-swap
19388
+ transaction. Run ${h("neo status")} / ${h("neo diff")} first \u2014 local-ahead changes
19389
+ you did not make this session ride along too.
19390
+ `;
19391
+ }
19392
+ return usage();
19393
+ }
18739
19394
  async function main() {
18740
19395
  const args = parseArgs(process.argv.slice(2));
19396
+ if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
19397
+ console.log(usage());
19398
+ return;
19399
+ }
19400
+ if (boolFlag(args, "help")) {
19401
+ console.log(commandHelp(args.command));
19402
+ return;
19403
+ }
19404
+ assertKnownFlags(args);
18741
19405
  const apiBaseUrl = stringFlag(args, "api") ?? DEFAULT_API_BASE_URL;
18742
19406
  switch (args.command) {
18743
19407
  case "login":
@@ -18766,7 +19430,10 @@ async function main() {
18766
19430
  case "pull": {
18767
19431
  const workspace = loadWorkspaceForCommand(args);
18768
19432
  const { runPull: runPull2 } = await Promise.resolve().then(() => (init_pull(), pull_exports));
18769
- await runPull2(workspace, { force: boolFlag(args, "force") });
19433
+ await runPull2(workspace, {
19434
+ force: boolFlag(args, "force"),
19435
+ reset: boolFlag(args, "reset")
19436
+ });
18770
19437
  return;
18771
19438
  }
18772
19439
  case "branch": {
@@ -18935,11 +19602,18 @@ async function main() {
18935
19602
  );
18936
19603
  }
18937
19604
  const { runScript: runScript2 } = await Promise.resolve().then(() => (init_script(), script_exports));
19605
+ const requestedMode = stringFlag(args, "mode") ?? "getter";
19606
+ if (requestedMode !== "getter" && requestedMode !== "action" && requestedMode !== "setter") {
19607
+ throw new Error(
19608
+ `--mode must be getter, action, or setter; received "${requestedMode}".`
19609
+ );
19610
+ }
18938
19611
  await runScript2(workspace, sub, {
18939
- mode: stringFlag(args, "mode") === "action" ? "action" : "getter",
19612
+ mode: requestedMode,
18940
19613
  source: args.positional[1] ?? null,
18941
19614
  file: stringFlag(args, "file"),
18942
19615
  thisRef: stringFlag(args, "this"),
19616
+ attributeRef: stringFlag(args, "attribute"),
18943
19617
  returns: stringFlag(args, "returns"),
18944
19618
  all: boolFlag(args, "all"),
18945
19619
  json: boolFlag(args, "json"),
@@ -19089,10 +19763,6 @@ async function main() {
19089
19763
  }
19090
19764
  return;
19091
19765
  }
19092
- case null:
19093
- case "help":
19094
- console.log(usage());
19095
- return;
19096
19766
  default:
19097
19767
  console.error(`Unknown command "${args.command}".
19098
19768
  `);