@neocompose/cli 0.7.0 → 0.7.2

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 (3) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/neo.mjs +107 -32
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@
7
7
  - Document the final relation-only world layer-link contract and keep the
8
8
  format-4 Hello World corpus free of the retired value-side binding field.
9
9
 
10
+ ## [0.7.2] - 2026-07-22
11
+
12
+ ### Fixed
13
+
14
+ - Preserve declaration-local `isReadOnly` on every override when an entire
15
+ inheritance chain converts to read-only in one source push.
16
+
17
+ ## [0.7.1] - 2026-07-22
18
+
19
+ ### Added
20
+
21
+ - Support `abstract readonly` getter contracts and concrete `override readonly`
22
+ implementations, including inherited Immutable storage without a repeated
23
+ storage decorator.
24
+
10
25
  ## [0.7.0] - 2026-07-22
11
26
 
12
27
  ### Added
package/dist/neo.mjs CHANGED
@@ -16240,6 +16240,7 @@ function createEnvironment2(documents) {
16240
16240
  const declarations = /* @__PURE__ */ new Map();
16241
16241
  const classes = /* @__PURE__ */ new Map();
16242
16242
  const interfaces = /* @__PURE__ */ new Map();
16243
+ const memberOwners = /* @__PURE__ */ new Map();
16243
16244
  const typeKinds = /* @__PURE__ */ new Map();
16244
16245
  for (const [uri, document] of documents) {
16245
16246
  for (const declaration of document.declarations) {
@@ -16264,6 +16265,9 @@ function createEnvironment2(documents) {
16264
16265
  };
16265
16266
  declarations.set(declaration.name, ref);
16266
16267
  classes.set(declaration.name, ref);
16268
+ for (const member of declaration.members) {
16269
+ memberOwners.set(member, ref);
16270
+ }
16267
16271
  typeKinds.set(declaration.name, {
16268
16272
  kind: "class",
16269
16273
  location: ref.location,
@@ -16286,7 +16290,7 @@ function createEnvironment2(documents) {
16286
16290
  }
16287
16291
  }
16288
16292
  }
16289
- return { declarations, classes, interfaces, typeKinds };
16293
+ return { declarations, classes, interfaces, memberOwners, typeKinds };
16290
16294
  }
16291
16295
  function validatePersistedNames(documents, diagnostics) {
16292
16296
  for (const [uri, document] of documents) {
@@ -16921,16 +16925,7 @@ function validateClassMemberModifiers(ref, environment, diagnostics) {
16921
16925
  `Read-only field '${member.name}' cannot be static.`
16922
16926
  );
16923
16927
  }
16924
- if (modifiers.has("abstract")) {
16925
- diagnose(
16926
- diagnostics,
16927
- ref.uri,
16928
- member.nameRange,
16929
- "abstract-readonly-member",
16930
- `Read-only field '${member.name}' cannot be abstract.`
16931
- );
16932
- }
16933
- if (member.initializer === void 0) {
16928
+ if (member.initializer === void 0 && !modifiers.has("abstract")) {
16934
16929
  diagnose(
16935
16930
  diagnostics,
16936
16931
  ref.uri,
@@ -17102,7 +17097,38 @@ function validateClassOverrides(ref, environment, diagnostics) {
17102
17097
  environment,
17103
17098
  true
17104
17099
  );
17100
+ const readOnlyContract = candidates.find(
17101
+ (candidate) => candidate.member.modifiers.includes("abstract") && candidate.member.modifiers.includes("readonly")
17102
+ );
17103
+ if (readOnlyContract !== void 0 && !member.modifiers.includes("readonly")) {
17104
+ diagnose(
17105
+ diagnostics,
17106
+ ref.uri,
17107
+ member.nameRange,
17108
+ "override-readonly-contract",
17109
+ `Override '${ref.declaration.name}.${member.name}' must remain readonly because '${readOnlyContract.owner.declaration.name}.${readOnlyContract.member.name}' is an abstract readonly contract.`,
17110
+ related(
17111
+ memberLocation(readOnlyContract),
17112
+ "The abstract readonly contract is declared here."
17113
+ )
17114
+ );
17115
+ continue;
17116
+ }
17105
17117
  if (mismatch) {
17118
+ if (mismatch === "the implementation must provide a setter" && member.modifiers.includes("readonly")) {
17119
+ diagnose(
17120
+ diagnostics,
17121
+ ref.uri,
17122
+ member.nameRange,
17123
+ "override-readonly-setter",
17124
+ `Read-only override '${ref.declaration.name}.${member.name}' cannot satisfy the setter required by abstract member '${target.owner.declaration.name}.${target.member.name}'.`,
17125
+ related(
17126
+ memberLocation(target),
17127
+ "The settable abstract member is declared here."
17128
+ )
17129
+ );
17130
+ continue;
17131
+ }
17106
17132
  diagnose(
17107
17133
  diagnostics,
17108
17134
  ref.uri,
@@ -17281,7 +17307,7 @@ function memberCompatibility(implementation, implementationSubstitution, target,
17281
17307
  implementationSubstitution
17282
17308
  );
17283
17309
  const targetType = typeShape(target.member.type, target.substitution);
17284
- const targetAccessors = target.member.kind === "function" ? { get: false, set: false } : valueAccessors(target.member);
17310
+ const targetAccessors = target.member.kind === "function" ? { get: false, set: false } : valueAccessors(target.member, environment);
17285
17311
  const permitsCovariantGetter = allowStoredInterfaceImplementation && implementationFamily === "value" && targetAccessors.get && !targetAccessors.set;
17286
17312
  const permitsStoredOverrideNullability = allowStoredOverrideNullability && implementation.kind === "field" && target.member.kind === "field" && typesEqual(
17287
17313
  { ...implementationType, nullable: false },
@@ -17291,7 +17317,7 @@ function memberCompatibility(implementation, implementationSubstitution, target,
17291
17317
  return `type '${formatType2(implementationType)}' does not match '${formatType2(targetType)}'`;
17292
17318
  }
17293
17319
  if (implementation.kind !== "function" || target.member.kind !== "function") {
17294
- const implementationAccessors = valueAccessors(implementation);
17320
+ const implementationAccessors = valueAccessors(implementation, environment);
17295
17321
  if (targetAccessors.get && !implementationAccessors.get) {
17296
17322
  return "the implementation must provide a getter";
17297
17323
  }
@@ -17323,11 +17349,13 @@ function memberCompatibility(implementation, implementationSubstitution, target,
17323
17349
  }
17324
17350
  return null;
17325
17351
  }
17326
- function valueAccessors(member) {
17352
+ function valueAccessors(member, environment) {
17327
17353
  if (member.kind === "field") {
17354
+ const owner = environment.memberOwners.get(member);
17355
+ const immutable = member.modifiers.includes("abstract") && owner !== void 0 && resolvedSourceMemberStorage(owner, member, environment) === "immutable";
17328
17356
  return {
17329
17357
  get: true,
17330
- set: !member.modifiers.includes("readonly")
17358
+ set: !member.modifiers.includes("readonly") && !immutable
17331
17359
  };
17332
17360
  }
17333
17361
  if (member.kind === "function") return { get: false, set: false };
@@ -22622,6 +22650,7 @@ function memberToDocument(member, baseData3, parentMember) {
22622
22650
  const normalizedBaseData = normalizeDocumentFields("member", baseData3);
22623
22651
  for (const [field, value] of Object.entries(fields)) {
22624
22652
  if (MEMBER_IDENTITY_FIELDS.has(field)) continue;
22653
+ if (field === "isReadOnly") continue;
22625
22654
  if (Object.prototype.hasOwnProperty.call(normalizedBaseData, field))
22626
22655
  continue;
22627
22656
  if (!Object.prototype.hasOwnProperty.call(inheritedFields, field)) continue;
@@ -25142,15 +25171,18 @@ function assertMember2(value, path) {
25142
25171
  "must be null unless the member modifier is override."
25143
25172
  );
25144
25173
  }
25174
+ if ((member.modifier === "abstract" || member.modifier === "abstractOverride") && member.defaultValue !== null) {
25175
+ invalid(
25176
+ `${path}.defaultValue`,
25177
+ "must be null for an abstract member; concrete implementations own defaults."
25178
+ );
25179
+ }
25145
25180
  assertSystem(member.system, `${path}.system`);
25146
25181
  nullableStorage(member.storage, `${path}.storage`);
25147
25182
  nullableNonEmptyString(member.storageKey, `${path}.storageKey`);
25148
25183
  if (member.isReadOnly === true) {
25149
25184
  if (owner.kind !== "classMember" && owner.kind !== "sharedClassMember") {
25150
- invalid(
25151
- `${path}.isReadOnly`,
25152
- "is allowed only on a concrete class field."
25153
- );
25185
+ invalid(`${path}.isReadOnly`, "is allowed only on a class field.");
25154
25186
  }
25155
25187
  if (member.isStatic === true) {
25156
25188
  invalid(`${path}.isReadOnly`, "cannot be combined with isStatic.");
@@ -25161,13 +25193,10 @@ function assertMember2(value, path) {
25161
25193
  `is not valid on value-less member kind ${JSON.stringify(kind)}.`
25162
25194
  );
25163
25195
  }
25164
- if (member.modifier === "abstract" || member.modifier === "abstractOverride") {
25165
- invalid(`${path}.isReadOnly`, "cannot be combined with abstract.");
25166
- }
25167
- if (member.storage !== "immutable") {
25196
+ if (member.storage !== "immutable" && (member.storage !== null || member.overrideOf === null)) {
25168
25197
  invalid(`${path}.storage`, "must be immutable when isReadOnly is true.");
25169
25198
  }
25170
- if (member.defaultValue === null) {
25199
+ if (member.defaultValue === null && member.modifier !== "abstract" && member.modifier !== "abstractOverride") {
25171
25200
  invalid(`${path}.defaultValue`, "is required when isReadOnly is true.");
25172
25201
  }
25173
25202
  }
@@ -28209,6 +28238,7 @@ function lowerMember(context, ownerClass, declaration, id2, owner) {
28209
28238
  }
28210
28239
  function memberCommon(context, ownerClass, declaration, id2, owner, base) {
28211
28240
  const storage = annotation(declaration.annotations, "storage");
28241
+ const inheritedId = declaration.modifiers.includes("override") || declaration.modifiers.includes("sealed") ? base?.overrideOf ?? inheritedMemberId(context, ownerClass, declaration.name) : null;
28212
28242
  return {
28213
28243
  id: id2,
28214
28244
  source: sourceIdentity2(declaration, "member", declaration.name),
@@ -28225,12 +28255,41 @@ function memberCommon(context, ownerClass, declaration, id2, owner, base) {
28225
28255
  locked: hasAnnotation(declaration.annotations, "locked"),
28226
28256
  required: declaration.kind === "field" ? !declaration.type.nullable : base?.required ?? false,
28227
28257
  defaultValue: null,
28228
- overrideOf: declaration.modifiers.includes("override") || declaration.modifiers.includes("sealed") ? base?.overrideOf ?? inheritedMemberId(context, ownerClass, declaration.name) : null,
28258
+ overrideOf: inheritedId,
28229
28259
  system: systemMetadata(declaration.annotations),
28230
- storage: storage ? storageValue(storage, "allowed") : null,
28260
+ storage: storage ? storageValue(storage, "allowed") : declaration.modifiers.includes("readonly") ? base !== void 0 ? base.storage : inheritedId !== null && resolvedMemberStorage(context, inheritedId) === "immutable" ? "immutable" : null : null,
28231
28261
  storageKey: storage ? stringArgument(storage, "key") : null
28232
28262
  };
28233
28263
  }
28264
+ function resolvedMemberStorage(context, memberId, visited = /* @__PURE__ */ new Set()) {
28265
+ if (visited.has(memberId)) return null;
28266
+ visited.add(memberId);
28267
+ const lowered = context.loweredMembers.get(memberId);
28268
+ if (lowered?.storage !== null && lowered?.storage !== void 0) {
28269
+ return lowered.storage;
28270
+ }
28271
+ const persisted = context.baseMembers.get(memberId);
28272
+ if (persisted !== void 0) {
28273
+ if (persisted.storage !== null) return persisted.storage;
28274
+ if (persisted.overrideOf !== null) {
28275
+ return resolvedMemberStorage(context, persisted.overrideOf, visited);
28276
+ }
28277
+ }
28278
+ const sourcePlacement = context.sourceMembersById.get(memberId);
28279
+ if (sourcePlacement !== void 0) {
28280
+ const { ownerClass: sourceClass, member: sourceMember } = sourcePlacement;
28281
+ const storage = annotation(sourceMember.annotations, "storage");
28282
+ const localStorage = storage ? storageValue(storage, "allowed") : null;
28283
+ if (localStorage !== null) return localStorage;
28284
+ const inheritedId = inheritedMemberId(
28285
+ context,
28286
+ sourceClass,
28287
+ sourceMember.name
28288
+ );
28289
+ return inheritedId === null ? null : resolvedMemberStorage(context, inheritedId, visited);
28290
+ }
28291
+ return null;
28292
+ }
28234
28293
  function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, base) {
28235
28294
  const storedStatic = declaration.modifiers.includes("static") && !declaration.modifiers.includes("readonly");
28236
28295
  const settings = annotation(declaration.annotations, "settings");
@@ -29513,6 +29572,26 @@ function lowerProjectSchemaV4(base, analysis, options = {}) {
29513
29572
  )
29514
29573
  );
29515
29574
  }
29575
+ const sourceClasses = new Map(
29576
+ analysis.schema.classes.map((entry) => [
29577
+ materializedId(entry, "class", entry.name),
29578
+ entry
29579
+ ])
29580
+ );
29581
+ const sourceMembersById = new Map(
29582
+ analysis.schema.classes.flatMap(
29583
+ (ownerClass) => ownerClass.members.map(
29584
+ (member) => [
29585
+ materializedId(
29586
+ member,
29587
+ "member",
29588
+ `${ownerClass.name}.${member.name}`
29589
+ ),
29590
+ { ownerClass, member }
29591
+ ]
29592
+ )
29593
+ )
29594
+ );
29516
29595
  const context = {
29517
29596
  base,
29518
29597
  baseClasses: new Map(base.classes.map((entry) => [entry.id, entry])),
@@ -29520,12 +29599,8 @@ function lowerProjectSchemaV4(base, analysis, options = {}) {
29520
29599
  baseInterfaces: new Map(base.interfaces.map((entry) => [entry.id, entry])),
29521
29600
  baseEnums: new Map(base.enums.map((entry) => [entry.id, entry])),
29522
29601
  baseRelationsById: indexFirstById(base.internalRecordRelations),
29523
- sourceClasses: new Map(
29524
- analysis.schema.classes.map((entry) => [
29525
- materializedId(entry, "class", entry.name),
29526
- entry
29527
- ])
29528
- ),
29602
+ sourceClasses,
29603
+ sourceMembersById,
29529
29604
  classIdsByName,
29530
29605
  interfaceIdsByName,
29531
29606
  enumIdsByName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",