@loomcli/core 0.2.0 → 0.4.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.
Files changed (51) hide show
  1. package/dist/application.d.ts +59 -34
  2. package/dist/application.js +162 -59
  3. package/dist/chain.d.ts +25 -6
  4. package/dist/chain.js +99 -15
  5. package/dist/command.d.ts +146 -42
  6. package/dist/command.js +642 -78
  7. package/dist/environment.d.ts +22 -0
  8. package/dist/environment.js +1 -0
  9. package/dist/errors.d.ts +31 -59
  10. package/dist/errors.js +49 -106
  11. package/dist/extension.d.ts +80 -20
  12. package/dist/extension.js +115 -30
  13. package/dist/globals.d.ts +14 -25
  14. package/dist/globals.js +10 -61
  15. package/dist/glyphs.generated.d.ts +464 -0
  16. package/dist/glyphs.generated.js +491 -0
  17. package/dist/host.js +2 -1
  18. package/dist/index.d.ts +15 -6
  19. package/dist/index.js +5 -2
  20. package/dist/inspect.d.ts +38 -4
  21. package/dist/inspect.js +69 -6
  22. package/dist/lanes.d.ts +26 -0
  23. package/dist/lanes.js +45 -0
  24. package/dist/output.d.ts +93 -15
  25. package/dist/output.js +307 -34
  26. package/dist/plugin.d.ts +32 -16
  27. package/dist/plugin.js +46 -18
  28. package/dist/rendering.d.ts +21 -0
  29. package/dist/rendering.js +72 -0
  30. package/dist/sequence.d.ts +41 -0
  31. package/dist/sequence.js +225 -0
  32. package/dist/style-ansi.d.ts +13 -0
  33. package/dist/style-ansi.js +306 -0
  34. package/dist/style-layout.d.ts +29 -0
  35. package/dist/style-layout.js +228 -0
  36. package/dist/style-resolve.d.ts +6 -0
  37. package/dist/style-resolve.js +26 -0
  38. package/dist/style-state.d.ts +14 -0
  39. package/dist/style-state.js +179 -0
  40. package/dist/style-wire.d.ts +31 -0
  41. package/dist/style-wire.js +201 -0
  42. package/dist/style.d.ts +86 -0
  43. package/dist/style.js +201 -0
  44. package/dist/theme.d.ts +3 -0
  45. package/dist/theme.js +22 -0
  46. package/dist/types.d.ts +174 -21
  47. package/dist/validation.d.ts +8 -1
  48. package/dist/validation.js +17 -2
  49. package/dist/view.d.ts +180 -0
  50. package/dist/view.js +307 -0
  51. package/package.json +2 -1
package/dist/view.js ADDED
@@ -0,0 +1,307 @@
1
+ import { DeclarationError, defaultText, FatalError, notTextReason, reasonOf } from './errors.js';
2
+ import { escapeText } from './style.js';
3
+ /** Authored declarations register here, so a hand-built object with an identity is a bare view. */
4
+ const declarations = new WeakMap();
5
+ /** The runtime value `view()` returns for a whole view. Its identity and function are public. */
6
+ class ViewDeclaration {
7
+ identity;
8
+ render;
9
+ constructor(identity, definition) {
10
+ this.identity = identity;
11
+ this.render = definition.render;
12
+ declarations.set(this, this);
13
+ Object.freeze(this);
14
+ }
15
+ }
16
+ /** The runtime value `view()` returns for a row view, which carries its three functions. */
17
+ class RowViewDeclaration {
18
+ identity;
19
+ row;
20
+ head;
21
+ tail;
22
+ constructor(identity, definition) {
23
+ this.identity = identity;
24
+ this.row = definition.row;
25
+ if (definition.head) {
26
+ this.head = definition.head;
27
+ }
28
+ if (definition.tail) {
29
+ this.tail = definition.tail;
30
+ }
31
+ declarations.set(this, this);
32
+ Object.freeze(this);
33
+ }
34
+ }
35
+ /**
36
+ * The shape one value names. The argument is read defensively, because every call that takes a view
37
+ * is reachable from JavaScript and `null` is one such value.
38
+ */
39
+ function shapeOf(value) {
40
+ const row = typeof value?.row === 'function';
41
+ const render = typeof value?.render === 'function';
42
+ if (row && render) {
43
+ return 'both';
44
+ }
45
+ if (row) {
46
+ return 'row';
47
+ }
48
+ return render ? 'render' : 'neither';
49
+ }
50
+ function view(identity, definition) {
51
+ const shape = shapeOf(definition);
52
+ if (shape === 'both') {
53
+ throw new DeclarationError(`View "${identity}" carries render and row. Supply one of the two.`);
54
+ }
55
+ if (shape === 'neither') {
56
+ throw new DeclarationError(`View "${identity}" carries neither render nor row. Supply a view with render or a row view with row.`);
57
+ }
58
+ return typeof definition.row === 'function'
59
+ ? new RowViewDeclaration(identity, definition)
60
+ : new ViewDeclaration(identity, definition);
61
+ }
62
+ /** Authored overrides register here, so the public type publishes nothing to reach. */
63
+ const overrides = new WeakMap();
64
+ /** The runtime value `override()` returns. Its pair lives in the registry above. */
65
+ class OverrideDeclaration {
66
+ constructor(record) {
67
+ overrides.set(this, record);
68
+ Object.freeze(this);
69
+ }
70
+ }
71
+ function override(key, replacement) {
72
+ const stored = {
73
+ head: replacement.head,
74
+ render: replacement.render,
75
+ row: replacement.row,
76
+ tail: replacement.tail,
77
+ };
78
+ const declared = declarations.get(key);
79
+ if (declared) {
80
+ return new OverrideDeclaration({ key: { kind: 'view', view: declared }, replacement: stored });
81
+ }
82
+ return new OverrideDeclaration({ key: failureKey(key), replacement: stored });
83
+ }
84
+ /**
85
+ * The key one failure-class override answers. A class is a function whose `prototype` is the
86
+ * object a thrown failure's chain holds, so anything else is no key at all and build reports it as
87
+ * the entry fault of the list that holds it, rather than colliding with every other such value.
88
+ */
89
+ function failureKey(key) {
90
+ if (typeof key !== 'function' || !('prototype' in key)) {
91
+ return { kind: 'invalid' };
92
+ }
93
+ const prototype = key.prototype;
94
+ if (typeof prototype !== 'object' || prototype === null) {
95
+ return { kind: 'invalid' };
96
+ }
97
+ const name = 'name' in key && typeof key.name === 'string' && key.name !== '' ? key.name : 'a failure class';
98
+ return { kind: 'failure', name, prototype };
99
+ }
100
+ /** The identity register one build starts from, holding the views core itself declares. */
101
+ function viewIdentities(declared) {
102
+ const identities = new Map();
103
+ for (const value of declared) {
104
+ registerIdentity(identities, value);
105
+ }
106
+ return identities;
107
+ }
108
+ /** One identity means one declared view, wherever on the graph that view appears. */
109
+ function registerIdentity(identities, declared) {
110
+ const known = identities.get(declared.identity);
111
+ if (known === undefined) {
112
+ identities.set(declared.identity, declared);
113
+ return;
114
+ }
115
+ if (known !== declared) {
116
+ throw new DeclarationError(`View "${declared.identity}" is declared by two distinct objects. Install one copy of the package that declares it.`);
117
+ }
118
+ }
119
+ /** The sentence one contributor's list reports for a value it cannot read. */
120
+ function entryFault(subject) {
121
+ return subject.declares
122
+ ? `${subject.sentence} holds a value that is not a view. Supply the value returned by view(identity, definition) or override(key, view).`
123
+ : `${subject.sentence} holds a value that is not a view override. Supply the value returned by override(key, view).`;
124
+ }
125
+ /** A `views` slot holds a list, so anything else is the same declaration fault. */
126
+ function readContributions(subject, declared) {
127
+ if (declared === undefined) {
128
+ return [];
129
+ }
130
+ if (!Array.isArray(declared)) {
131
+ throw new DeclarationError(subject.declares
132
+ ? `${subject.sentence} declares views that are not an array. Supply a list of declared views and override values.`
133
+ : entryFault(subject));
134
+ }
135
+ return declared;
136
+ }
137
+ /** The override one entry carries; anything else is a declaration fault of the slot. */
138
+ function overrideOf(subject, entry) {
139
+ const record = typeof entry === 'object' && entry !== null ? overrides.get(entry) : undefined;
140
+ if (!record) {
141
+ throw new DeclarationError(entryFault(subject));
142
+ }
143
+ return record;
144
+ }
145
+ /**
146
+ * One override recorded under the key it answers. One key answers to one override inside one
147
+ * contributor, so a second override for it is a declaration fault; the same key overridden by two
148
+ * contributors resolves first-in-wins. A key that is neither a declared view nor a failure class
149
+ * is the entry fault of the list that holds it, reported here rather than at the `override()` call.
150
+ */
151
+ function recordOverride(build, { key, replacement }) {
152
+ if (key.kind === 'invalid') {
153
+ throw new DeclarationError(entryFault(build.subject));
154
+ }
155
+ if (key.kind === 'failure') {
156
+ recordFailureOverride(build, key, replacement);
157
+ return;
158
+ }
159
+ recordViewOverride(build, key.view, replacement);
160
+ }
161
+ /** One failure class answers to one override inside one contributor, keyed by its prototype. */
162
+ function recordFailureOverride({ contributions, subject }, key, replacement) {
163
+ if (contributions.failures.has(key.prototype)) {
164
+ throw new DeclarationError(`${subject.sentence} overrides the view for "${key.name}" twice. Remove one override.`);
165
+ }
166
+ contributions.failures.set(key.prototype, replacement);
167
+ }
168
+ /** Naming a declared view as a key registers its identity, as listing the declaration does. */
169
+ function recordViewOverride({ contributions, identities, subject }, key, replacement) {
170
+ registerIdentity(identities, key);
171
+ if (contributions.views.has(key)) {
172
+ throw new DeclarationError(`${subject.sentence} overrides view "${key.identity}" twice. Remove one override.`);
173
+ }
174
+ contributions.views.set(key, replacement);
175
+ }
176
+ /**
177
+ * One contributor's own overrides, with the identity of every declared view it lists or names as a
178
+ * key registered. Listing a declared view is what puts its identity on the graph; an application
179
+ * lists overrides alone, so a declaration in its list is the same fault as any other value.
180
+ */
181
+ function buildViews(subject, declared, identities) {
182
+ const contributions = { failures: new Map(), views: new Map() };
183
+ for (const entry of readContributions(subject, declared)) {
184
+ const listed = typeof entry === 'object' && entry !== null ? declarations.get(entry) : undefined;
185
+ if (listed && !subject.declares) {
186
+ throw new DeclarationError(entryFault(subject));
187
+ }
188
+ if (listed) {
189
+ registerIdentity(identities, listed);
190
+ }
191
+ else {
192
+ recordOverride({ contributions, identities, subject }, overrideOf(subject, entry));
193
+ }
194
+ }
195
+ return contributions;
196
+ }
197
+ /** One stored view value, read back over the data its own key carries. */
198
+ function readStored(stored) {
199
+ // Last resort: no typed path exists.
200
+ // A registry holds one entry per key and cannot carry a type parameter per entry.
201
+ // A stored view value therefore reads back with its data type erased.
202
+ // It holds because `override(key, replacement)` typed the replacement against its key's data.
203
+ // Resolution reaches a stored value through that key alone.
204
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
205
+ return stored;
206
+ }
207
+ /**
208
+ * The stand-in for a function the stored shape does not carry. A JavaScript author's replacement
209
+ * of the wrong shape reaches it, and the throw is the output-view fault of the write site.
210
+ */
211
+ function missing(name, key) {
212
+ return () => {
213
+ throw new Error(`The replacement view for "${key}" supplies no ${name} function.`);
214
+ };
215
+ }
216
+ /** One stored whole view, read back as the function the write site calls, named by its key. */
217
+ function callView(stored, key) {
218
+ return readStored(stored).render ?? missing('render', key);
219
+ }
220
+ /** Every prototype in a failure's chain, most derived first, so one walk reads one contributor. */
221
+ function chainOf(failure) {
222
+ const chain = [];
223
+ let prototype = Object.getPrototypeOf(failure);
224
+ while (prototype !== null) {
225
+ chain.push(prototype);
226
+ prototype = Object.getPrototypeOf(prototype);
227
+ }
228
+ return chain;
229
+ }
230
+ /**
231
+ * The view function one declared view resolves to: the first contributor that overrides it, then
232
+ * its own default. A bare view is never overridden, so it resolves to its own function.
233
+ */
234
+ function resolveView(registry, value) {
235
+ const declared = declarations.get(value);
236
+ if (declared) {
237
+ for (const contributor of registry) {
238
+ const replacement = contributor.views.get(declared);
239
+ if (replacement) {
240
+ return callView(replacement, declared.identity);
241
+ }
242
+ }
243
+ }
244
+ return (data, context) => value.render(data, context);
245
+ }
246
+ /**
247
+ * The row functions one row view resolves to, by the walk `resolveView` defines. A replacement
248
+ * supplies the whole shape, so an override that omits `head` drops the default's `head` with it.
249
+ */
250
+ function resolveRowView(registry, value) {
251
+ const declared = declarations.get(value);
252
+ if (declared) {
253
+ for (const contributor of registry) {
254
+ const replacement = contributor.views.get(declared);
255
+ if (replacement) {
256
+ const resolved = readStored(replacement);
257
+ const row = resolved.row ?? missing('row', declared.identity);
258
+ return { head: resolved.head, row, tail: resolved.tail };
259
+ }
260
+ }
261
+ }
262
+ return { head: value.head, row: value.row, tail: value.tail };
263
+ }
264
+ /**
265
+ * The override one failure resolves to, or nothing when core's own text answers it. The chain is
266
+ * walked in full at each contributor before the next is consulted, so an application's override
267
+ * for a base class beats a plugin's override for a subclass.
268
+ */
269
+ function resolveFailure(registry, failure) {
270
+ const chain = chainOf(failure);
271
+ for (const contributor of registry) {
272
+ for (const prototype of chain) {
273
+ const replacement = contributor.failures.get(prototype);
274
+ if (replacement) {
275
+ return replacement;
276
+ }
277
+ }
278
+ }
279
+ return undefined;
280
+ }
281
+ /**
282
+ * The text core writes for one failure. Resolution walks the registry as `resolveFailure` defines
283
+ * it and falls to core's own default text, which escapes the raw facts it interpolates. A
284
+ * `FatalError` keeps the authored marked message it was given.
285
+ */
286
+ function describeFailure(registry, failure, context) {
287
+ const replacement = resolveFailure(registry, failure);
288
+ if (!replacement) {
289
+ return {
290
+ kind: 'rendered',
291
+ text: failure instanceof FatalError ? defaultText(failure) : escapeText(defaultText(failure)),
292
+ };
293
+ }
294
+ try {
295
+ if (context === undefined) {
296
+ throw new Error('Missing rendering context.');
297
+ }
298
+ const text = callView(replacement, failure.name)(failure, context);
299
+ return typeof text === 'string'
300
+ ? { kind: 'rendered', text }
301
+ : { kind: 'unrendered', reason: notTextReason(text), text: defaultText(failure) };
302
+ }
303
+ catch (error) {
304
+ return { kind: 'unrendered', reason: reasonOf(error), text: defaultText(failure) };
305
+ }
306
+ }
307
+ export { buildViews, describeFailure, override, resolveRowView, resolveView, shapeOf, view, viewIdentities, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcli/core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "The Loom CLI core package. Provides the scaffolding for creating new Loom CLI applications.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,6 +18,7 @@
18
18
  }
19
19
  },
20
20
  "dependencies": {
21
+ "@rockorager/uucode": "2.2.1",
21
22
  "@standard-schema/spec": "^1.1.0",
22
23
  "@types/node": "22.20.1"
23
24
  },