@neocompose/cli 0.9.4 → 0.10.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.
- package/CHANGELOG.md +14 -0
- package/README.md +17 -2
- package/dist/neo.mjs +233 -5
- package/package.json +1 -1
- package/skills/neocompose-cli/SKILL.md +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@
|
|
|
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.10.0] - 2026-07-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Declare List indexes and column settings in source with `@index(member: ...)`
|
|
15
|
+
and `@column(member: ...)`. Both were already in the schema contract and
|
|
16
|
+
offered by the language server, but lowering carried them from the manifest
|
|
17
|
+
cache and the emitter never wrote them back, so a workspace that pushed
|
|
18
|
+
without `neo/obj/` silently lost them.
|
|
19
|
+
- Declare a class hidden from the member selector and classes tree with
|
|
20
|
+
`@hidden`, the class-level counterpart to `@locked`. It maps to
|
|
21
|
+
`NeoSchemaClassAttribute.Hidden`, which the contract already carried; absent
|
|
22
|
+
means visible.
|
|
23
|
+
|
|
10
24
|
## [0.9.4] - 2026-07-25
|
|
11
25
|
|
|
12
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -141,8 +141,23 @@ abstract class InventoryItem<
|
|
|
141
141
|
The source identifier is the persisted name. `@settings` and `@storage` are
|
|
142
142
|
typed by their declaration context; invalid fields and enum cases are
|
|
143
143
|
diagnostics. Swift-like `.EnumCase` syntax works whenever the expected enum
|
|
144
|
-
type is known. Focused annotations such as `@id`, `@locked`, `@
|
|
145
|
-
`@relations` carry cross-cutting behavior.
|
|
144
|
+
type is known. Focused annotations such as `@id`, `@locked`, `@hidden`,
|
|
145
|
+
`@system`, and `@relations` carry cross-cutting behavior. `@hidden` marks a
|
|
146
|
+
class the member selector and classes tree leave out; absent means visible.
|
|
147
|
+
|
|
148
|
+
Lists take repeatable `@index` and `@column` annotations naming a field on the
|
|
149
|
+
entry class. An index requires a non-localizable `string` or a single-select
|
|
150
|
+
enum; a column may also target the reserved `__other__` key, which carries the
|
|
151
|
+
default layout for every field a subclass adds.
|
|
152
|
+
|
|
153
|
+
```neo
|
|
154
|
+
@id("outpost-list-id")
|
|
155
|
+
@settings(kind: .Unordered)
|
|
156
|
+
@index(member: Slug, unique: true)
|
|
157
|
+
@column(member: Slug, width: 158, frozen: true)
|
|
158
|
+
@column(member: Notes, hidden: true)
|
|
159
|
+
public List<Outpost> Outposts = [];
|
|
160
|
+
```
|
|
146
161
|
|
|
147
162
|
Stable IDs preserve identity across rename, reorder, and file moves. Omit an
|
|
148
163
|
`@id` only to create something. A successful real push assigns the ID and
|
package/dist/neo.mjs
CHANGED
|
@@ -14285,6 +14285,9 @@ function validateMember(uri, member, documentKind, ownerScope, environment, diag
|
|
|
14285
14285
|
diagnostics
|
|
14286
14286
|
);
|
|
14287
14287
|
validateTypeAnnotations(uri, member.type, environment, diagnostics);
|
|
14288
|
+
if (documentKind !== "flow") {
|
|
14289
|
+
validateListAnnotations(uri, member, environment, diagnostics);
|
|
14290
|
+
}
|
|
14288
14291
|
const scope = new Map(ownerScope);
|
|
14289
14292
|
if (member.kind === "function") {
|
|
14290
14293
|
for (const parameter3 of member.parameters) {
|
|
@@ -14435,6 +14438,9 @@ function validateAnnotations(uri, annotations, context, environment, diagnostics
|
|
|
14435
14438
|
continue;
|
|
14436
14439
|
}
|
|
14437
14440
|
seen.add(field.name);
|
|
14441
|
+
if (field.name === "member" && (annotation2.name === "index" || annotation2.name === "column")) {
|
|
14442
|
+
continue;
|
|
14443
|
+
}
|
|
14438
14444
|
validateExpressionText(
|
|
14439
14445
|
uri,
|
|
14440
14446
|
argument2.text,
|
|
@@ -14458,6 +14464,7 @@ function fieldsForAnnotation(name, context, environment) {
|
|
|
14458
14464
|
if (name === "locked") {
|
|
14459
14465
|
return context.kind === "member" || context.kind === "flowBinding" ? [] : null;
|
|
14460
14466
|
}
|
|
14467
|
+
if (name === "hidden") return context.kind === "class" ? [] : null;
|
|
14461
14468
|
if (name === "settings") return settingsFields(context);
|
|
14462
14469
|
if (name === "storage") return storageFields(context);
|
|
14463
14470
|
if (name === "relations") return relationFields(context, environment);
|
|
@@ -15489,6 +15496,121 @@ function contextualAnnotationEnum(annotations, name, field) {
|
|
|
15489
15496
|
const value = annotations.find((annotation2) => annotation2.name === name)?.arguments.find((argument2) => argument2.name === field)?.text.trim();
|
|
15490
15497
|
return value?.startsWith(".") ? value.slice(1) : null;
|
|
15491
15498
|
}
|
|
15499
|
+
function validateListAnnotations(uri, member, environment, diagnostics) {
|
|
15500
|
+
const declared = member.annotations.filter(
|
|
15501
|
+
(annotation2) => annotation2.name === "index" || annotation2.name === "column"
|
|
15502
|
+
);
|
|
15503
|
+
if (declared.length === 0) return;
|
|
15504
|
+
const entryClass = listEntryClass(member.type, environment);
|
|
15505
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15506
|
+
for (const annotation2 of declared) {
|
|
15507
|
+
const target = memberKeyArgument(annotation2);
|
|
15508
|
+
if (target === null) {
|
|
15509
|
+
pushDiagnostic(
|
|
15510
|
+
diagnostics,
|
|
15511
|
+
uri,
|
|
15512
|
+
annotation2.range,
|
|
15513
|
+
"missing-list-annotation-member",
|
|
15514
|
+
`@${annotation2.name} on '${member.name}' requires a member naming a field on the list entry, such as @${annotation2.name}(member: Name).`
|
|
15515
|
+
);
|
|
15516
|
+
continue;
|
|
15517
|
+
}
|
|
15518
|
+
if (target.key === null) {
|
|
15519
|
+
pushDiagnostic(
|
|
15520
|
+
diagnostics,
|
|
15521
|
+
uri,
|
|
15522
|
+
target.range,
|
|
15523
|
+
"invalid-list-annotation-member",
|
|
15524
|
+
`@${annotation2.name} on '${member.name}' must name a list entry field directly, such as @${annotation2.name}(member: Name).`
|
|
15525
|
+
);
|
|
15526
|
+
continue;
|
|
15527
|
+
}
|
|
15528
|
+
if (seen.has(`${annotation2.name} ${target.key}`)) {
|
|
15529
|
+
pushDiagnostic(
|
|
15530
|
+
diagnostics,
|
|
15531
|
+
uri,
|
|
15532
|
+
target.range,
|
|
15533
|
+
"duplicate-list-annotation",
|
|
15534
|
+
`@${annotation2.name}(member: ${target.key}) is declared more than once on '${member.name}'.`
|
|
15535
|
+
);
|
|
15536
|
+
continue;
|
|
15537
|
+
}
|
|
15538
|
+
seen.add(`${annotation2.name} ${target.key}`);
|
|
15539
|
+
if (annotation2.name === "column" && target.key === LIST_COLUMN_INHERITANCE_KEY) {
|
|
15540
|
+
continue;
|
|
15541
|
+
}
|
|
15542
|
+
if (entryClass === null) continue;
|
|
15543
|
+
const field = resolveEntryField(entryClass, target.key, environment);
|
|
15544
|
+
if (field === null) {
|
|
15545
|
+
pushDiagnostic(
|
|
15546
|
+
diagnostics,
|
|
15547
|
+
uri,
|
|
15548
|
+
target.range,
|
|
15549
|
+
"unresolved-list-annotation-member",
|
|
15550
|
+
`@${annotation2.name}(member: ${target.key}) on '${member.name}' does not resolve on list entry class '${entryClass.name}'.`
|
|
15551
|
+
);
|
|
15552
|
+
continue;
|
|
15553
|
+
}
|
|
15554
|
+
if (annotation2.name !== "index") continue;
|
|
15555
|
+
const domainError = indexKeyDomainError(field, environment);
|
|
15556
|
+
if (domainError !== null) {
|
|
15557
|
+
pushDiagnostic(
|
|
15558
|
+
diagnostics,
|
|
15559
|
+
uri,
|
|
15560
|
+
target.range,
|
|
15561
|
+
"invalid-list-index-member",
|
|
15562
|
+
`@index(member: ${target.key}) on '${member.name}' is invalid: ${domainError}.`
|
|
15563
|
+
);
|
|
15564
|
+
}
|
|
15565
|
+
}
|
|
15566
|
+
}
|
|
15567
|
+
function memberKeyArgument(annotation2) {
|
|
15568
|
+
const positional = annotation2.arguments[0];
|
|
15569
|
+
const argument2 = annotation2.arguments.find((entry) => entry.name === "member") ?? (positional !== void 0 && positional.name === void 0 ? positional : void 0);
|
|
15570
|
+
if (argument2 === void 0) return null;
|
|
15571
|
+
const text = argument2.text.trim();
|
|
15572
|
+
return {
|
|
15573
|
+
key: IDENTIFIER_PATTERN2.test(text) ? text : null,
|
|
15574
|
+
range: argument2.range
|
|
15575
|
+
};
|
|
15576
|
+
}
|
|
15577
|
+
function listEntryClass(type, environment) {
|
|
15578
|
+
if (type.name !== "List" && type.name !== "Set") return null;
|
|
15579
|
+
const entry = type.typeArguments[0];
|
|
15580
|
+
if (entry === void 0) return null;
|
|
15581
|
+
return environment.types.get(entry.name) ?? null;
|
|
15582
|
+
}
|
|
15583
|
+
function resolveEntryField(entryClass, key, environment) {
|
|
15584
|
+
const visited = /* @__PURE__ */ new Set();
|
|
15585
|
+
let current = entryClass;
|
|
15586
|
+
while (current !== void 0 && !visited.has(current.name)) {
|
|
15587
|
+
visited.add(current.name);
|
|
15588
|
+
const match = current.members.find((entry) => entry.name === key);
|
|
15589
|
+
if (match !== void 0) return match;
|
|
15590
|
+
current = current.baseTypes.map((base) => environment.types.get(base.name)).find((base) => base !== void 0);
|
|
15591
|
+
}
|
|
15592
|
+
return null;
|
|
15593
|
+
}
|
|
15594
|
+
function indexKeyDomainError(field, environment) {
|
|
15595
|
+
if (field.kind !== "field") {
|
|
15596
|
+
return `'${field.name}' is a ${field.kind}, and index keys require a stored field`;
|
|
15597
|
+
}
|
|
15598
|
+
if (field.type.name === "string") {
|
|
15599
|
+
return annotationBoolean(field.annotations, "settings", "localizable") === false ? null : `'${field.name}' is localizable, and String index keys must be non-localizable`;
|
|
15600
|
+
}
|
|
15601
|
+
if (environment.enums.has(field.type.name)) return null;
|
|
15602
|
+
const entry = field.type.typeArguments[0];
|
|
15603
|
+
if (field.type.name === "List" && entry !== void 0 && environment.enums.has(entry.name) && annotationBoolean(field.annotations, "settings", "multiselect") === true) {
|
|
15604
|
+
return `'${field.name}' is a multiselect Enum, and index keys require a single-select Enum`;
|
|
15605
|
+
}
|
|
15606
|
+
return `'${field.name}' is a ${field.type.name} field, and index keys require a non-localizable String or a single-select Enum`;
|
|
15607
|
+
}
|
|
15608
|
+
function annotationBoolean(annotations, name, field) {
|
|
15609
|
+
const value = annotations.find((annotation2) => annotation2.name === name)?.arguments.find((argument2) => argument2.name === field)?.text.trim();
|
|
15610
|
+
if (value === "true") return true;
|
|
15611
|
+
if (value === "false") return false;
|
|
15612
|
+
return null;
|
|
15613
|
+
}
|
|
15492
15614
|
function isListContext(context) {
|
|
15493
15615
|
return (context.kind === "member" || context.kind === "type") && (context.type.name === "List" || context.type.name === "Set");
|
|
15494
15616
|
}
|
|
@@ -15533,7 +15655,7 @@ function pushDiagnostic(diagnostics, uri, range2, code, message) {
|
|
|
15533
15655
|
message
|
|
15534
15656
|
});
|
|
15535
15657
|
}
|
|
15536
|
-
var primitiveType;
|
|
15658
|
+
var primitiveType, IDENTIFIER_PATTERN2, LIST_COLUMN_INHERITANCE_KEY;
|
|
15537
15659
|
var init_project_source_semantics = __esm({
|
|
15538
15660
|
"../packages/neoscript-language/src/project-source-semantics.ts"() {
|
|
15539
15661
|
"use strict";
|
|
@@ -15542,6 +15664,8 @@ var init_project_source_semantics = __esm({
|
|
|
15542
15664
|
init_strict_parser();
|
|
15543
15665
|
init_project_root();
|
|
15544
15666
|
primitiveType = (name) => ({ name });
|
|
15667
|
+
IDENTIFIER_PATTERN2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
15668
|
+
LIST_COLUMN_INHERITANCE_KEY = "__other__";
|
|
15545
15669
|
}
|
|
15546
15670
|
});
|
|
15547
15671
|
|
|
@@ -21308,6 +21432,16 @@ function annotationSignatureParameters(name) {
|
|
|
21308
21432
|
return ["relation descriptors"];
|
|
21309
21433
|
case "incomplete":
|
|
21310
21434
|
return ["string reason"];
|
|
21435
|
+
case "index":
|
|
21436
|
+
return ["entry member", "bool unique"];
|
|
21437
|
+
case "column":
|
|
21438
|
+
return [
|
|
21439
|
+
"entry member",
|
|
21440
|
+
"int width",
|
|
21441
|
+
"bool hidden",
|
|
21442
|
+
"bool frozen",
|
|
21443
|
+
"bool wrapContent"
|
|
21444
|
+
];
|
|
21311
21445
|
default:
|
|
21312
21446
|
return null;
|
|
21313
21447
|
}
|
|
@@ -28783,7 +28917,7 @@ function lowerClass(context, declaration) {
|
|
|
28783
28917
|
schemaKeyOrder: persistedSchemaOrder,
|
|
28784
28918
|
extendsClassId,
|
|
28785
28919
|
implementsInterfaceIds,
|
|
28786
|
-
hiddenInMemberSelector:
|
|
28920
|
+
hiddenInMemberSelector: hasAnnotation(declaration.annotations, "hidden"),
|
|
28787
28921
|
system: systemMetadata(declaration.annotations),
|
|
28788
28922
|
allowedStorage: storage ? storageValue(storage, "allowed") : null,
|
|
28789
28923
|
// Class storage keys never exist in source. Retain only the historical
|
|
@@ -28800,6 +28934,11 @@ function lowerClass(context, declaration) {
|
|
|
28800
28934
|
};
|
|
28801
28935
|
}
|
|
28802
28936
|
function lowerMember(context, ownerClass, declaration, id2, owner) {
|
|
28937
|
+
const member = lowerMemberKind(context, ownerClass, declaration, id2, owner);
|
|
28938
|
+
assertListAnnotationPlacement(ownerClass, declaration, member.kind);
|
|
28939
|
+
return member;
|
|
28940
|
+
}
|
|
28941
|
+
function lowerMemberKind(context, ownerClass, declaration, id2, owner) {
|
|
28803
28942
|
const base = context.baseMembers.get(id2);
|
|
28804
28943
|
const common = memberCommon(
|
|
28805
28944
|
context,
|
|
@@ -29139,8 +29278,8 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
|
|
|
29139
29278
|
kind: "list",
|
|
29140
29279
|
entryMemberId: entryId,
|
|
29141
29280
|
listKind: contextualEnumArgument(settings, "kind") === "Unordered" ? "unordered" : "ordered",
|
|
29142
|
-
indexes:
|
|
29143
|
-
columns:
|
|
29281
|
+
indexes: lowerListIndexes(ownerClass, declaration),
|
|
29282
|
+
columns: lowerListColumns(ownerClass, declaration)
|
|
29144
29283
|
};
|
|
29145
29284
|
}
|
|
29146
29285
|
if (name === "Dictionary") {
|
|
@@ -29210,6 +29349,70 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
|
|
|
29210
29349
|
`Unsupported field type ${JSON.stringify(name)} on ${ownerClass.name}.${declaration.name}.`
|
|
29211
29350
|
);
|
|
29212
29351
|
}
|
|
29352
|
+
function lowerListIndexes(ownerClass, declaration) {
|
|
29353
|
+
const declared = /* @__PURE__ */ new Set();
|
|
29354
|
+
return declaration.annotations.filter((entry) => entry.name === "index").map((entry) => {
|
|
29355
|
+
const schemaKey = memberKeyArgument2(entry, ownerClass, declaration);
|
|
29356
|
+
if (declared.has(schemaKey)) {
|
|
29357
|
+
throw new Error(
|
|
29358
|
+
`@index(member: ${schemaKey}) is declared more than once on ${ownerClass.name}.${declaration.name}.`
|
|
29359
|
+
);
|
|
29360
|
+
}
|
|
29361
|
+
declared.add(schemaKey);
|
|
29362
|
+
return { schemaKey, unique: booleanArgument(entry, "unique") ?? false };
|
|
29363
|
+
});
|
|
29364
|
+
}
|
|
29365
|
+
function lowerListColumns(ownerClass, declaration) {
|
|
29366
|
+
const declared = /* @__PURE__ */ new Set();
|
|
29367
|
+
return declaration.annotations.filter((entry) => entry.name === "column").map((entry) => {
|
|
29368
|
+
const memberKey = memberKeyArgument2(entry, ownerClass, declaration);
|
|
29369
|
+
if (declared.has(memberKey)) {
|
|
29370
|
+
throw new Error(
|
|
29371
|
+
`@column(member: ${memberKey}) is declared more than once on ${ownerClass.name}.${declaration.name}.`
|
|
29372
|
+
);
|
|
29373
|
+
}
|
|
29374
|
+
declared.add(memberKey);
|
|
29375
|
+
const width = numberArgument(entry, "width");
|
|
29376
|
+
return {
|
|
29377
|
+
memberKey,
|
|
29378
|
+
// `-1` is the generated C# marker's "unset" sentinel for an int
|
|
29379
|
+
// property that has no nullable form; the record spells that as null.
|
|
29380
|
+
width: width === null || width === UNSET_LIST_COLUMN_WIDTH ? null : width,
|
|
29381
|
+
hidden: booleanArgument(entry, "hidden") ?? false,
|
|
29382
|
+
frozen: booleanArgument(entry, "frozen") ?? false,
|
|
29383
|
+
wrapContent: booleanArgument(entry, "wrapContent") ?? false
|
|
29384
|
+
};
|
|
29385
|
+
});
|
|
29386
|
+
}
|
|
29387
|
+
function memberKeyArgument2(value, ownerClass, declaration) {
|
|
29388
|
+
const positional = value.arguments[0];
|
|
29389
|
+
const raw = argument(value, "member") ?? (positional !== void 0 && positional.name === void 0 ? positional.expression : null);
|
|
29390
|
+
if (raw === null) {
|
|
29391
|
+
throw new Error(
|
|
29392
|
+
`@${value.name} on ${ownerClass.name}.${declaration.name} requires a member naming a field on the list entry, such as @${value.name}(member: Name).`
|
|
29393
|
+
);
|
|
29394
|
+
}
|
|
29395
|
+
const expression = parseAnnotationExpression(raw);
|
|
29396
|
+
if (expression.kind !== "ident") {
|
|
29397
|
+
throw new Error(
|
|
29398
|
+
`@${value.name}(member: ${raw.trim()}) on ${ownerClass.name}.${declaration.name} must name a list entry field directly, such as @${value.name}(member: Name).`
|
|
29399
|
+
);
|
|
29400
|
+
}
|
|
29401
|
+
return expression.name;
|
|
29402
|
+
}
|
|
29403
|
+
function assertListAnnotationPlacement(ownerClass, declaration, loweredKind) {
|
|
29404
|
+
if (loweredKind === "list") return;
|
|
29405
|
+
if (hasAnnotation(declaration.annotations, "index")) {
|
|
29406
|
+
throw new Error(
|
|
29407
|
+
`@index on ${ownerClass.name}.${declaration.name} is only valid on a List member; this declaration lowers to a ${loweredKind} member.`
|
|
29408
|
+
);
|
|
29409
|
+
}
|
|
29410
|
+
if (hasAnnotation(declaration.annotations, "column")) {
|
|
29411
|
+
throw new Error(
|
|
29412
|
+
`@column on ${ownerClass.name}.${declaration.name} is only valid on a List member; this declaration lowers to a ${loweredKind} member.`
|
|
29413
|
+
);
|
|
29414
|
+
}
|
|
29415
|
+
}
|
|
29213
29416
|
function lowerCollectionEntry(context, ownerClass, entryType, entryId, parentMemberId) {
|
|
29214
29417
|
if (context.loweredMembers.has(entryId)) return;
|
|
29215
29418
|
const base = context.baseMembers.get(entryId);
|
|
@@ -30041,13 +30244,14 @@ function* zip(left, right) {
|
|
|
30041
30244
|
yield [left[index], right[index]];
|
|
30042
30245
|
}
|
|
30043
30246
|
}
|
|
30044
|
-
var primitiveMemberKinds;
|
|
30247
|
+
var UNSET_LIST_COLUMN_WIDTH, primitiveMemberKinds;
|
|
30045
30248
|
var init_lower_members = __esm({
|
|
30046
30249
|
"src/project-source/lower-members.ts"() {
|
|
30047
30250
|
"use strict";
|
|
30048
30251
|
init_src();
|
|
30049
30252
|
init_lower_support();
|
|
30050
30253
|
init_ui_action_source();
|
|
30254
|
+
UNSET_LIST_COLUMN_WIDTH = -1;
|
|
30051
30255
|
primitiveMemberKinds = /* @__PURE__ */ new Set([
|
|
30052
30256
|
"null",
|
|
30053
30257
|
"bool",
|
|
@@ -30752,6 +30956,7 @@ ${namedArguments(
|
|
|
30752
30956
|
function emitClass(context, schemaClass2, memberIds) {
|
|
30753
30957
|
const annotations = [
|
|
30754
30958
|
id(schemaClass2.id).trimEnd(),
|
|
30959
|
+
...schemaClass2.hiddenInMemberSelector ? ["@hidden"] : [],
|
|
30755
30960
|
...schemaClass2.allowedStorage ? [`@storage(allowed: .${enumCase(schemaClass2.allowedStorage)})`] : [],
|
|
30756
30961
|
...schemaClass2.system ? [systemAnnotation(schemaClass2.system)] : [],
|
|
30757
30962
|
...relationsAnnotations(context, schemaClass2)
|
|
@@ -30883,6 +31088,7 @@ function emitMember(context, member) {
|
|
|
30883
31088
|
].join(", ")})`
|
|
30884
31089
|
] : [],
|
|
30885
31090
|
...memberSettings(context, member),
|
|
31091
|
+
...listAnnotations(member),
|
|
30886
31092
|
...member.system ? [systemAnnotation(member.system)] : []
|
|
30887
31093
|
];
|
|
30888
31094
|
const prefix = memberModifier3(member);
|
|
@@ -31024,6 +31230,28 @@ function memberSettings(context, member) {
|
|
|
31024
31230
|
}
|
|
31025
31231
|
return settings.length ? [`@settings(${settings.join(", ")})`] : [];
|
|
31026
31232
|
}
|
|
31233
|
+
function listAnnotations(member) {
|
|
31234
|
+
if (member.kind !== "list") return [];
|
|
31235
|
+
return [
|
|
31236
|
+
...member.indexes.map((entry) => {
|
|
31237
|
+
assertIdentifier(entry.schemaKey, "list index member");
|
|
31238
|
+
return `@index(${[
|
|
31239
|
+
`member: ${entry.schemaKey}`,
|
|
31240
|
+
...entry.unique ? ["unique: true"] : []
|
|
31241
|
+
].join(", ")})`;
|
|
31242
|
+
}),
|
|
31243
|
+
...member.columns.map((entry) => {
|
|
31244
|
+
assertIdentifier(entry.memberKey, "list column member");
|
|
31245
|
+
return `@column(${[
|
|
31246
|
+
`member: ${entry.memberKey}`,
|
|
31247
|
+
...entry.width === null ? [] : [`width: ${entry.width}`],
|
|
31248
|
+
...entry.hidden ? ["hidden: true"] : [],
|
|
31249
|
+
...entry.frozen ? ["frozen: true"] : [],
|
|
31250
|
+
...entry.wrapContent ? ["wrapContent: true"] : []
|
|
31251
|
+
].join(", ")})`;
|
|
31252
|
+
})
|
|
31253
|
+
];
|
|
31254
|
+
}
|
|
31027
31255
|
function renderMemberType(context, member) {
|
|
31028
31256
|
const nullable = !member.required;
|
|
31029
31257
|
let value;
|
package/package.json
CHANGED
|
@@ -196,8 +196,14 @@ abstract class InventoryItem<
|
|
|
196
196
|
- `@settings(...)` and `@storage(...)` are context-aware typed contracts.
|
|
197
197
|
Numeric limits are numeric literals. A class supports only
|
|
198
198
|
`@storage(allowed: ...)`; storage keys are member-only.
|
|
199
|
-
- Keep focused annotations such as `@locked`, `@system`, and
|
|
200
|
-
separate from contextual settings.
|
|
199
|
+
- Keep focused annotations such as `@locked`, `@hidden`, `@system`, and
|
|
200
|
+
`@relations` separate from contextual settings. `@hidden` is class-only and
|
|
201
|
+
hides the class from the member selector and classes tree; absent is visible.
|
|
202
|
+
- `@index(member: Field, unique: ...)` and
|
|
203
|
+
`@column(member: Field, width: ..., hidden: ..., frozen: ..., wrapContent: ...)`
|
|
204
|
+
are repeatable List annotations. `member:` is a bare field name on the entry
|
|
205
|
+
class — an index needs a non-localizable `string` or a single-select enum,
|
|
206
|
+
and a column may also target the reserved `__other__` key.
|
|
201
207
|
- `.EnumCase` works only when one expected enum type is known. Use explicit
|
|
202
208
|
`EnumType.Case` when context is ambiguous.
|
|
203
209
|
- Defaults and settings must be statically analyzable. Project source is parsed
|