@neocompose/cli 0.7.0 → 0.7.1

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@
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.1] - 2026-07-22
11
+
12
+ ### Added
13
+
14
+ - Support `abstract readonly` getter contracts and concrete `override readonly`
15
+ implementations, including inherited Immutable storage without a repeated
16
+ storage decorator.
17
+
10
18
  ## [0.7.0] - 2026-07-22
11
19
 
12
20
  ### 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 };
@@ -25142,15 +25170,18 @@ function assertMember2(value, path) {
25142
25170
  "must be null unless the member modifier is override."
25143
25171
  );
25144
25172
  }
25173
+ if ((member.modifier === "abstract" || member.modifier === "abstractOverride") && member.defaultValue !== null) {
25174
+ invalid(
25175
+ `${path}.defaultValue`,
25176
+ "must be null for an abstract member; concrete implementations own defaults."
25177
+ );
25178
+ }
25145
25179
  assertSystem(member.system, `${path}.system`);
25146
25180
  nullableStorage(member.storage, `${path}.storage`);
25147
25181
  nullableNonEmptyString(member.storageKey, `${path}.storageKey`);
25148
25182
  if (member.isReadOnly === true) {
25149
25183
  if (owner.kind !== "classMember" && owner.kind !== "sharedClassMember") {
25150
- invalid(
25151
- `${path}.isReadOnly`,
25152
- "is allowed only on a concrete class field."
25153
- );
25184
+ invalid(`${path}.isReadOnly`, "is allowed only on a class field.");
25154
25185
  }
25155
25186
  if (member.isStatic === true) {
25156
25187
  invalid(`${path}.isReadOnly`, "cannot be combined with isStatic.");
@@ -25161,13 +25192,10 @@ function assertMember2(value, path) {
25161
25192
  `is not valid on value-less member kind ${JSON.stringify(kind)}.`
25162
25193
  );
25163
25194
  }
25164
- if (member.modifier === "abstract" || member.modifier === "abstractOverride") {
25165
- invalid(`${path}.isReadOnly`, "cannot be combined with abstract.");
25166
- }
25167
- if (member.storage !== "immutable") {
25195
+ if (member.storage !== "immutable" && (member.storage !== null || member.overrideOf === null)) {
25168
25196
  invalid(`${path}.storage`, "must be immutable when isReadOnly is true.");
25169
25197
  }
25170
- if (member.defaultValue === null) {
25198
+ if (member.defaultValue === null && member.modifier !== "abstract" && member.modifier !== "abstractOverride") {
25171
25199
  invalid(`${path}.defaultValue`, "is required when isReadOnly is true.");
25172
25200
  }
25173
25201
  }
@@ -28209,6 +28237,7 @@ function lowerMember(context, ownerClass, declaration, id2, owner) {
28209
28237
  }
28210
28238
  function memberCommon(context, ownerClass, declaration, id2, owner, base) {
28211
28239
  const storage = annotation(declaration.annotations, "storage");
28240
+ const inheritedId = declaration.modifiers.includes("override") || declaration.modifiers.includes("sealed") ? base?.overrideOf ?? inheritedMemberId(context, ownerClass, declaration.name) : null;
28212
28241
  return {
28213
28242
  id: id2,
28214
28243
  source: sourceIdentity2(declaration, "member", declaration.name),
@@ -28225,12 +28254,41 @@ function memberCommon(context, ownerClass, declaration, id2, owner, base) {
28225
28254
  locked: hasAnnotation(declaration.annotations, "locked"),
28226
28255
  required: declaration.kind === "field" ? !declaration.type.nullable : base?.required ?? false,
28227
28256
  defaultValue: null,
28228
- overrideOf: declaration.modifiers.includes("override") || declaration.modifiers.includes("sealed") ? base?.overrideOf ?? inheritedMemberId(context, ownerClass, declaration.name) : null,
28257
+ overrideOf: inheritedId,
28229
28258
  system: systemMetadata(declaration.annotations),
28230
- storage: storage ? storageValue(storage, "allowed") : null,
28259
+ storage: storage ? storageValue(storage, "allowed") : declaration.modifiers.includes("readonly") ? base !== void 0 ? base.storage : inheritedId !== null && resolvedMemberStorage(context, inheritedId) === "immutable" ? "immutable" : null : null,
28231
28260
  storageKey: storage ? stringArgument(storage, "key") : null
28232
28261
  };
28233
28262
  }
28263
+ function resolvedMemberStorage(context, memberId, visited = /* @__PURE__ */ new Set()) {
28264
+ if (visited.has(memberId)) return null;
28265
+ visited.add(memberId);
28266
+ const lowered = context.loweredMembers.get(memberId);
28267
+ if (lowered?.storage !== null && lowered?.storage !== void 0) {
28268
+ return lowered.storage;
28269
+ }
28270
+ const persisted = context.baseMembers.get(memberId);
28271
+ if (persisted !== void 0) {
28272
+ if (persisted.storage !== null) return persisted.storage;
28273
+ if (persisted.overrideOf !== null) {
28274
+ return resolvedMemberStorage(context, persisted.overrideOf, visited);
28275
+ }
28276
+ }
28277
+ const sourcePlacement = context.sourceMembersById.get(memberId);
28278
+ if (sourcePlacement !== void 0) {
28279
+ const { ownerClass: sourceClass, member: sourceMember } = sourcePlacement;
28280
+ const storage = annotation(sourceMember.annotations, "storage");
28281
+ const localStorage = storage ? storageValue(storage, "allowed") : null;
28282
+ if (localStorage !== null) return localStorage;
28283
+ const inheritedId = inheritedMemberId(
28284
+ context,
28285
+ sourceClass,
28286
+ sourceMember.name
28287
+ );
28288
+ return inheritedId === null ? null : resolvedMemberStorage(context, inheritedId, visited);
28289
+ }
28290
+ return null;
28291
+ }
28234
28292
  function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, base) {
28235
28293
  const storedStatic = declaration.modifiers.includes("static") && !declaration.modifiers.includes("readonly");
28236
28294
  const settings = annotation(declaration.annotations, "settings");
@@ -29513,6 +29571,26 @@ function lowerProjectSchemaV4(base, analysis, options = {}) {
29513
29571
  )
29514
29572
  );
29515
29573
  }
29574
+ const sourceClasses = new Map(
29575
+ analysis.schema.classes.map((entry) => [
29576
+ materializedId(entry, "class", entry.name),
29577
+ entry
29578
+ ])
29579
+ );
29580
+ const sourceMembersById = new Map(
29581
+ analysis.schema.classes.flatMap(
29582
+ (ownerClass) => ownerClass.members.map(
29583
+ (member) => [
29584
+ materializedId(
29585
+ member,
29586
+ "member",
29587
+ `${ownerClass.name}.${member.name}`
29588
+ ),
29589
+ { ownerClass, member }
29590
+ ]
29591
+ )
29592
+ )
29593
+ );
29516
29594
  const context = {
29517
29595
  base,
29518
29596
  baseClasses: new Map(base.classes.map((entry) => [entry.id, entry])),
@@ -29520,12 +29598,8 @@ function lowerProjectSchemaV4(base, analysis, options = {}) {
29520
29598
  baseInterfaces: new Map(base.interfaces.map((entry) => [entry.id, entry])),
29521
29599
  baseEnums: new Map(base.enums.map((entry) => [entry.id, entry])),
29522
29600
  baseRelationsById: indexFirstById(base.internalRecordRelations),
29523
- sourceClasses: new Map(
29524
- analysis.schema.classes.map((entry) => [
29525
- materializedId(entry, "class", entry.name),
29526
- entry
29527
- ])
29528
- ),
29601
+ sourceClasses,
29602
+ sourceMembersById,
29529
29603
  classIdsByName,
29530
29604
  interfaceIdsByName,
29531
29605
  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.1",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",