@neocompose/cli 0.19.2 → 0.19.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.
@@ -0,0 +1,150 @@
1
+ # NeoScript
2
+
3
+ Use this reference for inline code, snippets, nullability, control flow,
4
+ runtime ownership, evaluation, and migrations.
5
+
6
+ ## Contents
7
+
8
+ - Inline bodies and snippets
9
+ - Nullability and ownership
10
+ - Loops and transfer
11
+ - Switch
12
+ - Try/catch
13
+ - Evaluation and migrations
14
+
15
+ ## Inline bodies and snippets
16
+
17
+ Keep computed getters, setters, functions, dialogue logic, group logic, and
18
+ constructors inline in their owning `.neo` or `.neoflow` declaration. Do not
19
+ create `Scripts/<Class>/<Member>.neo` sidecars.
20
+
21
+ Use the shared language service through focused commands:
22
+
23
+ ```sh
24
+ neo script check --all
25
+ neo script check --this Outpost --returns string 'return $"{this.Name}!";'
26
+ neo script check --mode setter --member ComputedName 'root.Session.Name = value;'
27
+ neo script check --mode nsfunction --member Outpost.RefreshUnlock 'return this.Level > 0;'
28
+ neo script compile --mode nsfunction --member Outpost.RefreshUnlock 'return this.Level > 0;'
29
+ neo script eval --returns string 'return root.Assets.Outposts[0].FullDisplayText;'
30
+ neo script eval --function Outpost.RefreshUnlock --this-value <id> --args '[3]'
31
+ neo script apply --mode action '...'
32
+ ```
33
+
34
+ Prefer `--json` for automation.
35
+
36
+ ## Nullability and ownership
37
+
38
+ Do not access a nullable receiver directly. First narrow it with a condition, or
39
+ use null propagation `?.`, coalescing `??`, or explicit force unwrap `!` when
40
+ failure is intended:
41
+
42
+ ```neo
43
+ if (player.Target != null) {
44
+ return player.Target.Name;
45
+ }
46
+ return player.Target?.Name ?? "Unknown";
47
+ ```
48
+
49
+ Treat lookup-return values with the same nullable narrowing rules.
50
+
51
+ Let runtime ownership—not body kind—decide whether a write target is Immutable,
52
+ Save, Session, Setter, or otherwise writable. A constructor writes only its
53
+ `this` instance. A writable structured-leaf field still inherits the receiver's
54
+ effective storage restrictions.
55
+
56
+ ## Loops and transfer
57
+
58
+ Use C#-shaped loops:
59
+
60
+ ```neo
61
+ for (int i = 0; i < Items.Count; i++) {
62
+ if (Items[i].Disabled) {
63
+ continue;
64
+ }
65
+ Visit(Items[i]);
66
+ }
67
+
68
+ foreach (var item in root.Save.Items) {
69
+ item.Enabled = true;
70
+ }
71
+ ```
72
+
73
+ A `foreach` receiver must be non-null. It snapshots membership/order once at
74
+ entry, so adding/removing collection rows does not alter the scheduled
75
+ iterations. Dictionary iteration binds values, not keys. The iterator binding
76
+ is read-only, although writes through a writable entry remain valid.
77
+
78
+ Use `break` for the nearest loop or switch and `continue` for the nearest
79
+ loop. A `continue` inside a switch nested in a loop continues the loop.
80
+
81
+ All nested loops and called NeoScript bodies share a 10,000-iteration budget.
82
+ Do not use or teach `while` or `do`; they are not supported.
83
+
84
+ ## Switch
85
+
86
+ Use scalar, enum, or nullable selectors with compile-time constant labels:
87
+
88
+ ```neo
89
+ switch (direction) {
90
+ case Direction.North:
91
+ Move(0, 1);
92
+ break;
93
+ case Direction.East:
94
+ case Direction.West:
95
+ Stop();
96
+ break;
97
+ default:
98
+ throw $"Unknown direction: {direction}";
99
+ }
100
+ ```
101
+
102
+ Permit `int`, `string`, `bool`, enum, and nullable forms. Stack labels when
103
+ they share one body. End every reachable non-empty section with `break`,
104
+ `return`, `throw`, or `continue` when a surrounding loop makes it valid.
105
+ Do not use implicit fallthrough, switch expressions, pattern cases, guards, or
106
+ `goto`.
107
+
108
+ ## Try/catch
109
+
110
+ Catch the string-valued NeoScript runtime-error channel with ordered filters:
111
+
112
+ ```neo
113
+ try {
114
+ return GetUnsafe();
115
+ }
116
+ catch (string message) when (message == "1") {
117
+ return "Site moved";
118
+ }
119
+ catch (string message) {
120
+ throw;
121
+ }
122
+ ```
123
+
124
+ Require braces and at least one `catch (string name)`. The catch binding is
125
+ read-only and scoped to its filter/body. Put at most one unfiltered catch last.
126
+ A false or failing filter continues matching the original error. Bare
127
+ `throw;` is valid only inside a catch and rethrows the original message.
128
+
129
+ Do not use typed exception classes, parameterless catch, `finally`, `using`,
130
+ patterns, or host-exception recovery. Completed writes before an error are not
131
+ rolled back.
132
+
133
+ ## Evaluation and migrations
134
+
135
+ `neo script eval` uses authored values and the same evaluator as the web app.
136
+ `neo script apply` previews ordered write intents unless its explicit command
137
+ contract offers a commit mode.
138
+
139
+ Keep migrations as tracked action files under `Migrations/`:
140
+
141
+ ```sh
142
+ neo migrate new <name> --target <ClassName|project>
143
+ neo migrate list
144
+ neo migrate check
145
+ neo migrate run [--dry-run] [--skip-invalid]
146
+ neo migrate prune
147
+ ```
148
+
149
+ Run checks before migration execution. Review storage ownership and every write
150
+ intent; do not assume an action body is writable merely because it compiled.
@@ -0,0 +1,158 @@
1
+ # Values, identities, and references
2
+
3
+ Use this reference for root/static values, nested identities, references,
4
+ localization, and managed project files.
5
+
6
+ ## Contents
7
+
8
+ - Root and static bindings
9
+ - Identity rules
10
+ - Same-push references
11
+ - Defaults and localization
12
+ - Managed files
13
+
14
+ ## Root and static bindings
15
+
16
+ Edit only the initializer after `=` in the compiler-owned `Root.neo` envelope.
17
+ The root member name, type, member ID, lock, and storage metadata are read-only.
18
+
19
+ ```neo
20
+ Root root = new() {
21
+ @id("root-assets-member-id")
22
+ @locked
23
+ @storage(allowed: .Immutable)
24
+ Assets Assets = new() {
25
+ Outposts = [Assets.Capitol],
26
+ };
27
+ }
28
+ ```
29
+
30
+ Do not place another `@id` immediately before the root initializer. The member
31
+ ID owns the binding. Apply the same rule to static stored members:
32
+
33
+ ```neo
34
+ @id("assets-class-id")
35
+ class Assets {
36
+ @id("capitol-member-id")
37
+ static Outpost Capitol = new {
38
+ Name = "Capitol",
39
+ };
40
+
41
+ @id("home-getter-id")
42
+ static Outpost Home {
43
+ get { return Assets.Capitol; }
44
+ }
45
+ }
46
+ ```
47
+
48
+ A computed static getter is an alias; it creates neither a binding nor a row.
49
+
50
+ ## Identity rules
51
+
52
+ Preserve every existing ID through rename, reorder, and file moves. For a new
53
+ ordinary record, omit `@id` and let a successful real push assign and insert
54
+ it. Dry-run and failed pushes leave tracked source unchanged.
55
+
56
+ Nested class/list rows remain independently addressable and keep inline IDs:
57
+
58
+ ```neo
59
+ Tags = [
60
+ @id("story-tag-item-id")
61
+ "story",
62
+ ];
63
+ ```
64
+
65
+ Never match an existing ordered row by index or payload. A bare row is pending
66
+ create shorthand.
67
+
68
+ When a same-push structural reference has no symbolic, path, or unique-key
69
+ form, an author may assign a previously unseen UUID-v4 with `@id` to the new
70
+ row and reference it in the same edit. Reusing an existing ID means update, not
71
+ create. The server validates authored new IDs for UUID-v4 shape and uniqueness.
72
+ Prefer generated IDs unless the structural reference needs an ID immediately.
73
+
74
+ Do not invent IDs for structural rows whose identities derive from an owner
75
+ role. Do not add a separate root/static initializer ID.
76
+
77
+ ## Same-push references
78
+
79
+ Use the `Reference` intrinsic. Prefer the most readable form the target
80
+ supports:
81
+
82
+ ```neo
83
+ Reference(Assets.Capitol)
84
+ Reference(root.Assets.Cosmetics.Pants)
85
+ Reference<Outpost>(id: "capitol-value-id")
86
+ Reference<PantsAsset>(key: "pants.long")
87
+ Reference<PantsAsset>(key: "pants.long", index: Slug)
88
+ Reference<Dialogue>(id: "capitol-dialogue-id")
89
+ ```
90
+
91
+ - Use a symbol or root path when source can name the value directly.
92
+ - Use `key:` only for a collection with a unique index. Omit `index:` when the
93
+ collection has exactly one usable unique index; supply the bare member name
94
+ when it has more than one.
95
+ - Use the generic `id:` form when the ID is the only target information. A
96
+ `Reference` call whose argument is neither `id:` nor `key:` is rejected by
97
+ name rather than read as an ID.
98
+ - Both forms are legal in a member's declaration default, not only inside a
99
+ value graph. A key written there resolves after every pass has run, so it may
100
+ name a row the same push creates.
101
+ - Keep structural references, such as child-track targets, ID-based when their
102
+ collection has no stable key/path surface.
103
+
104
+ Declaration and file order do not affect resolution within one push. Cyclic
105
+ identity references are legal because they resolve IDs rather than evaluate a
106
+ dependency graph. Validate against the project after the push: a row deleted by
107
+ the edit is not a target, and lookup/dialogue references must still satisfy
108
+ assignability, collection membership, multiplicity, and group constraints.
109
+
110
+ `collectionValue` references follow the same post-push resolution and
111
+ membership rules as member defaults.
112
+
113
+ ## Defaults and localization
114
+
115
+ Materialize omitted members from their current defaults only when an instance
116
+ is created. Do not expect later class-default changes to update existing rows.
117
+ Removing an explicitly materialized field from an existing initializer requests
118
+ a reset through the current default; require that change to appear in
119
+ `neo diff`.
120
+
121
+ Write main-locale text directly in localizable string initializers. Pull/lower
122
+ preserves other locales, comments, statuses, archive state, and unrelated
123
+ localized-text links. Do not author localized-text IDs in place of prose.
124
+
125
+ ## Managed files
126
+
127
+ Declare files in typed registries:
128
+
129
+ ```neo
130
+ ImageRegistry Images = new() {
131
+ @id("sword-image-file-id")
132
+ @settings(template: PixelArt)
133
+ NeoImage Sword = new("Files/Images/Sword.png");
134
+ }
135
+
136
+ AudioClipRegistry AudioClips = new() {
137
+ @id("sword-hit-audio-file-id")
138
+ @settings(template: SoundEffect)
139
+ NeoAudioClip SwordHit = new("Files/AudioClips/SwordHit.wav");
140
+ }
141
+ ```
142
+
143
+ Reference a sprite with `Images.Sword.Slice(0)` and an audio clip with
144
+ `AudioClips.SwordHit`. A bare `Images.Sword` represents the file ID only in a
145
+ file-valued context such as `SpriteInfo.FileId`; it is not a complete sprite.
146
+
147
+ Dropping a supported binary under `Files/Images/` or `Files/AudioClips/`
148
+ creates a pending file with a provisional deterministic symbol. Status,
149
+ diff, and dry-run do not rewrite its registry. A successful push creates the
150
+ record, uploads verified bytes, and materializes its declaration and ID.
151
+
152
+ Use `neo files add <path> [--template <Name>]` to scaffold a declaration before
153
+ push when desired.
154
+
155
+ Pull/push compare server-verified SHA-256, not storage ETags. A divergent
156
+ binary keeps local bytes and writes the verified remote side under
157
+ `.neo/conflicts/files/<file-id>/`. A retained declaration with a missing binary
158
+ is an error; remove the declaration to request deletion.