@astrale-os/cli 0.8.1-alpha.7 → 1.0.0-beta.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 (64) hide show
  1. package/README.md +15 -3
  2. package/dist/astrale.js +86 -35
  3. package/package.json +5 -5
  4. package/src/commands/__tests__/domain-install-operation.test.ts +121 -0
  5. package/src/commands/__tests__/domain-install-owned.test.ts +66 -0
  6. package/src/commands/__tests__/install-direct.test.ts +3 -2
  7. package/src/commands/domain/install.ts +78 -15
  8. package/src/connection/__tests__/errors.test.ts +82 -6
  9. package/src/connection/command.ts +9 -1
  10. package/src/connection/errors.ts +19 -8
  11. package/src/connection/index.ts +1 -1
  12. package/src/connection/reasons.ts +26 -12
  13. package/src/program/__tests__/program.test.ts +1 -1
  14. package/studio/client/dist/assets/{elk-api-D0cBetPW.js → elk-api-D2xgMJvi.js} +1 -1
  15. package/studio/client/dist/assets/{index-BQnJ5sgd.css → index-BMdnsIJA.css} +1 -1
  16. package/studio/client/dist/assets/index-D-vRV8w7.js +8 -0
  17. package/studio/client/dist/assets/index-LGSWRrk8.js +81 -0
  18. package/studio/client/dist/index.html +2 -2
  19. package/studio/package.json +1 -1
  20. package/studio/server/agent/prompts/anchors.test.ts +74 -0
  21. package/studio/server/agent/prompts/anchors.ts +85 -15
  22. package/studio/server/agent/prompts/system.test.ts +12 -0
  23. package/studio/server/agent/prompts/system.ts +6 -6
  24. package/studio/server/api.ts +1 -5
  25. package/studio/server/cache.ts +5 -2
  26. package/studio/server/domain.test.ts +67 -0
  27. package/studio/server/domain.ts +29 -6
  28. package/studio/server/index.ts +1 -3
  29. package/studio/server/introspect/anatomy-extras.test.ts +104 -1
  30. package/studio/server/introspect/anatomy-extras.ts +340 -8
  31. package/studio/server/introspect/anatomy.test.ts +33 -0
  32. package/studio/server/introspect/anatomy.ts +22 -7
  33. package/studio/server/introspect/bundle.ts +7 -1
  34. package/studio/server/introspect/canonical-schema.test.ts +395 -0
  35. package/studio/server/introspect/canonical-schema.ts +751 -0
  36. package/studio/server/introspect/core-extractor.ts +30 -10
  37. package/studio/server/introspect/core.ts +4 -2
  38. package/studio/server/introspect/diff.test.ts +124 -0
  39. package/studio/server/introspect/diff.ts +252 -23
  40. package/studio/server/introspect/extractor.ts +46 -14
  41. package/studio/server/introspect/overlay-tsmorph.test.ts +164 -1
  42. package/studio/server/introspect/overlay-tsmorph.ts +381 -106
  43. package/studio/server/introspect/overlay.test.ts +72 -0
  44. package/studio/server/introspect/overlay.ts +16 -6
  45. package/studio/server/introspect/runtime.test.ts +217 -0
  46. package/studio/server/introspect/runtime.ts +18 -6
  47. package/studio/server/introspect/schema-refs.test.ts +100 -0
  48. package/studio/server/introspect/schema-refs.ts +23 -2
  49. package/studio/server/state/baseline.test.ts +51 -0
  50. package/studio/server/state/baseline.ts +31 -2
  51. package/studio/server/state/create.test.ts +37 -0
  52. package/studio/server/state/create.ts +21 -15
  53. package/studio/server/state/instance.test.ts +63 -0
  54. package/studio/server/state/instance.ts +65 -29
  55. package/studio/server/state/views.test.ts +209 -9
  56. package/studio/server/state/views.ts +176 -69
  57. package/studio/server/watch.test.ts +36 -0
  58. package/studio/server/watch.ts +24 -16
  59. package/studio/server/workspace-watch.ts +8 -3
  60. package/studio/shared/types.ts +164 -33
  61. package/studio/client/dist/assets/index-Dspir4w7.js +0 -81
  62. package/studio/client/dist/assets/index-bVD2KJgz.js +0 -8
  63. package/studio/server/view-dev-server.test.ts +0 -111
  64. package/studio/server/view-dev-server.ts +0 -372
@@ -0,0 +1,395 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import type { CanonicalDomainSchemaV1 } from './canonical-schema'
4
+
5
+ import {
6
+ closureFromSdk,
7
+ findCanonicalDomainSchemaExport,
8
+ normalizeLegacySchemaIR,
9
+ projectCanonicalCore,
10
+ projectCanonicalSchema,
11
+ } from './canonical-schema'
12
+
13
+ const emptyProperties = {
14
+ type: 'object',
15
+ properties: {},
16
+ required: [],
17
+ additionalProperties: false,
18
+ }
19
+
20
+ const dependency = {
21
+ format: 'astrale.dsl',
22
+ version: 'v1',
23
+ origin: 'directory.example.dev',
24
+ dependencies: [],
25
+ types: {},
26
+ interfaces: {
27
+ Named: {
28
+ family: 'node',
29
+ extends: [],
30
+ properties: {
31
+ ...emptyProperties,
32
+ properties: { name: { type: 'string' } },
33
+ required: ['name'],
34
+ },
35
+ propertyMetadata: {},
36
+ methods: {},
37
+ },
38
+ },
39
+ classes: {},
40
+ functions: {},
41
+ policies: {},
42
+ views: {},
43
+ core: { nodes: {}, edges: [] },
44
+ } satisfies CanonicalDomainSchemaV1
45
+
46
+ const namedRef = {
47
+ origin: dependency.origin,
48
+ kind: 'interface',
49
+ name: 'Named',
50
+ } as const
51
+ const namedKey = `${dependency.origin}:interface.Named` as const
52
+ const documentRef = {
53
+ origin: 'documents.example.dev',
54
+ kind: 'class',
55
+ name: 'Document',
56
+ } as const
57
+
58
+ const root = {
59
+ format: 'astrale.dsl',
60
+ version: 'v1',
61
+ origin: 'documents.example.dev',
62
+ dependencies: [{ origin: dependency.origin, revision: `sha256:${'1'.repeat(64)}` }],
63
+ types: { State: { type: 'string', enum: ['draft', 'published'] } },
64
+ interfaces: {
65
+ Publishable: {
66
+ family: 'node',
67
+ extends: [namedRef],
68
+ properties: emptyProperties,
69
+ propertyMetadata: {},
70
+ methods: {},
71
+ },
72
+ },
73
+ classes: {
74
+ Document: {
75
+ family: 'node',
76
+ implements: [{ origin: 'documents.example.dev', kind: 'interface', name: 'Publishable' }],
77
+ properties: {
78
+ ...emptyProperties,
79
+ properties: {
80
+ title: { type: 'string' },
81
+ summary: { type: 'string' },
82
+ note: { type: ['string', 'null'] },
83
+ },
84
+ required: ['title', 'note'],
85
+ },
86
+ propertyMetadata: {},
87
+ data: { mediaType: 'application/json', indexed: true },
88
+ methods: {
89
+ watch: {
90
+ input: {
91
+ ...emptyProperties,
92
+ properties: { cursor: { type: 'string' }, limit: { type: 'integer' } },
93
+ required: ['cursor'],
94
+ },
95
+ output: { mode: 'stream', item: { type: 'string' } },
96
+ auth: 'authorized',
97
+ static: false,
98
+ inheritance: 'default',
99
+ },
100
+ },
101
+ policies: {},
102
+ },
103
+ references: {
104
+ family: 'edge',
105
+ implements: [],
106
+ properties: emptyProperties,
107
+ propertyMetadata: {},
108
+ orientation: 'directed',
109
+ endpoints: {
110
+ source: { role: 'document', accepts: [documentRef], outgoing: '1..*' },
111
+ target: { role: 'named', accepts: [namedRef], incoming: '0..1' },
112
+ },
113
+ policies: {},
114
+ constraints: { noSelf: true },
115
+ },
116
+ },
117
+ functions: {
118
+ exportAll: {
119
+ description: 'Export every document.',
120
+ input: emptyProperties,
121
+ output: { mode: 'binary' },
122
+ auth: 'authenticated',
123
+ },
124
+ },
125
+ policies: {},
126
+ views: {
127
+ editor: {
128
+ description: 'Document editor',
129
+ target: { kind: 'definition', definitions: [documentRef, namedRef] },
130
+ auth: 'optional',
131
+ },
132
+ },
133
+ core: {
134
+ nodes: {
135
+ welcome: {
136
+ class: documentRef,
137
+ properties: {
138
+ 'documents.example.dev:class.Document.property.title': 'Welcome',
139
+ },
140
+ },
141
+ },
142
+ edges: [
143
+ {
144
+ class: { origin: 'documents.example.dev', kind: 'class', name: 'references' },
145
+ source: { origin: 'documents.example.dev', kind: 'core', name: 'welcome' },
146
+ target: { kind: 'domain' },
147
+ properties: { weight: 1 },
148
+ },
149
+ ],
150
+ },
151
+ } satisfies CanonicalDomainSchemaV1
152
+
153
+ describe('canonical DomainSchema V1 projection', () => {
154
+ test('admits only an SDK-verified, exact dependency closure', () => {
155
+ expect(
156
+ closureFromSdk({ bundle: { create: () => ({ root, closure: [dependency] }) } }, root),
157
+ ).toEqual([dependency])
158
+
159
+ expect(
160
+ closureFromSdk(
161
+ {
162
+ bundle: { create: () => ({ root: dependency, closure: [dependency] }) },
163
+ schema: { resolve: () => ({ $: { schema: root, closure: [dependency] } }) },
164
+ },
165
+ root,
166
+ ),
167
+ ).toEqual([dependency])
168
+
169
+ expect(
170
+ closureFromSdk({ bundle: { create: () => ({ root, closure: [root, dependency] }) } }, root),
171
+ ).toEqual([])
172
+ })
173
+
174
+ test('finds any directly exported V1 root while preferring `schema`', () => {
175
+ expect(findCanonicalDomainSchemaExport({ Other: dependency, schema: root })).toBe(root)
176
+ expect(findCanonicalDomainSchemaExport({ Current: root })).toBe(root)
177
+ expect(findCanonicalDomainSchemaExport({ schema: { kind: 'legacy' } })).toBeNull()
178
+ })
179
+
180
+ test('keeps legacy fields and enriches refs, callables, views, and imported interfaces', () => {
181
+ const projected = projectCanonicalSchema(root, [dependency])
182
+ const ir = projected.ir
183
+
184
+ expect(ir).toMatchObject({
185
+ format: 'astrale.dsl',
186
+ version: 'v1',
187
+ domain: root.origin,
188
+ dependencies: root.dependencies,
189
+ })
190
+ expect(ir.interfaces.Publishable.extends).toEqual(['Named'])
191
+ expect(ir.interfaces.Publishable.extendsRefs).toEqual([namedRef])
192
+ expect(ir.imports.Named).toEqual({
193
+ origin: dependency.origin,
194
+ definition: 'interface',
195
+ ref: namedRef,
196
+ key: namedKey,
197
+ })
198
+ expect(ir.importsByKey?.[namedKey]).toEqual(ir.imports.Named)
199
+ expect(ir.importedInterfacesByKey?.[namedKey]).toMatchObject({
200
+ name: 'Named',
201
+ origin: dependency.origin,
202
+ })
203
+ expect(projected.importedInterfaces.Named).toMatchObject({
204
+ name: 'Named',
205
+ origin: dependency.origin,
206
+ family: 'node',
207
+ properties: { name: { type: 'string' } },
208
+ required: ['name'],
209
+ })
210
+
211
+ expect(ir.classes.Document.properties).toEqual({
212
+ title: { type: 'string' },
213
+ summary: { type: 'string' },
214
+ note: { type: ['string', 'null'] },
215
+ })
216
+ expect(ir.classes.Document.required).toEqual(['title', 'note'])
217
+ expect(ir.classes.Document.data).toEqual({ mediaType: 'application/json', indexed: true })
218
+ expect(ir.classes.Document.methods.watch).toMatchObject({
219
+ name: 'watch',
220
+ auth: 'authorized',
221
+ static: false,
222
+ inheritance: 'default',
223
+ params: {
224
+ cursor: { type: 'string' },
225
+ limit: { type: 'integer' },
226
+ },
227
+ requiredParams: ['cursor'],
228
+ output: { mode: 'stream', item: { type: 'string' } },
229
+ returns: { type: 'string' },
230
+ })
231
+ expect(ir.classes.references.endpoints).toEqual([
232
+ {
233
+ name: 'document',
234
+ types: ['Document'],
235
+ refs: [documentRef],
236
+ cardinality: { min: 1, max: null },
237
+ },
238
+ {
239
+ name: 'named',
240
+ types: ['Named'],
241
+ refs: [namedRef],
242
+ cardinality: { min: 0, max: 1 },
243
+ },
244
+ ])
245
+ expect(ir.functions.exportAll).toMatchObject({
246
+ name: 'exportAll',
247
+ auth: 'authenticated',
248
+ static: true,
249
+ inheritance: 'default',
250
+ params: {},
251
+ requiredParams: [],
252
+ output: { mode: 'binary' },
253
+ returns: {},
254
+ })
255
+ expect(ir.views?.editor).toEqual({
256
+ name: 'editor',
257
+ description: 'Document editor',
258
+ target: { kind: 'definition', definitions: [documentRef, namedRef] },
259
+ auth: 'optional',
260
+ })
261
+ expect(ir.core).toEqual(root.core)
262
+ })
263
+
264
+ test('keeps colliding imported definitions under exact DSL Keys only', () => {
265
+ const alternate = {
266
+ ...dependency,
267
+ origin: 'people.example.dev',
268
+ } satisfies CanonicalDomainSchemaV1
269
+ const classOwner = {
270
+ ...dependency,
271
+ origin: 'catalog.example.dev',
272
+ interfaces: {},
273
+ classes: {
274
+ Named: {
275
+ family: 'node',
276
+ implements: [],
277
+ properties: emptyProperties,
278
+ propertyMetadata: {},
279
+ methods: {},
280
+ policies: {},
281
+ },
282
+ },
283
+ } satisfies CanonicalDomainSchemaV1
284
+ const alternateRef = {
285
+ origin: alternate.origin,
286
+ kind: 'interface',
287
+ name: 'Named',
288
+ } as const
289
+ const classRef = {
290
+ origin: classOwner.origin,
291
+ kind: 'class',
292
+ name: 'Named',
293
+ } as const
294
+ const collisionRoot = {
295
+ format: 'astrale.dsl',
296
+ version: 'v1',
297
+ origin: 'collisions.example.dev',
298
+ dependencies: [],
299
+ types: {},
300
+ interfaces: {
301
+ Local: {
302
+ family: 'node',
303
+ extends: [namedRef, alternateRef],
304
+ properties: emptyProperties,
305
+ propertyMetadata: {},
306
+ methods: {},
307
+ },
308
+ },
309
+ classes: {},
310
+ functions: {},
311
+ policies: {},
312
+ views: {
313
+ everything: {
314
+ target: { kind: 'definition', definitions: [namedRef, alternateRef, classRef] },
315
+ auth: 'required',
316
+ },
317
+ },
318
+ core: { nodes: {}, edges: [] },
319
+ } satisfies CanonicalDomainSchemaV1
320
+
321
+ const projected = projectCanonicalSchema(collisionRoot, [dependency, alternate, classOwner])
322
+ const keys = [
323
+ 'directory.example.dev:interface.Named',
324
+ 'people.example.dev:interface.Named',
325
+ 'catalog.example.dev:class.Named',
326
+ ]
327
+
328
+ expect(Object.keys(projected.ir.importsByKey ?? {}).sort()).toEqual(keys.sort())
329
+ expect(Object.keys(projected.ir.importedInterfacesByKey ?? {}).sort()).toEqual(
330
+ keys.filter((key) => key.includes(':interface.')).sort(),
331
+ )
332
+ expect(
333
+ projected.ir.importedInterfacesByKey?.['directory.example.dev:interface.Named']?.origin,
334
+ ).toBe('directory.example.dev')
335
+ expect(
336
+ projected.ir.importedInterfacesByKey?.['people.example.dev:interface.Named']?.origin,
337
+ ).toBe('people.example.dev')
338
+ expect(projected.ir.importsByKey?.['catalog.example.dev:class.Named']).toMatchObject({
339
+ definition: 'class',
340
+ key: 'catalog.example.dev:class.Named',
341
+ ref: classRef,
342
+ })
343
+ expect(projected.ir.imports.Named).toBeUndefined()
344
+ expect(projected.importedInterfaces.Named).toBeUndefined()
345
+ })
346
+
347
+ test('projects canonical core paths and the owning Domain endpoint', () => {
348
+ expect(projectCanonicalCore(root)).toEqual({
349
+ domain: root.origin,
350
+ nodes: [
351
+ {
352
+ path: '/:documents.example.dev:core.welcome',
353
+ className: 'Document',
354
+ data: { 'documents.example.dev:class.Document.property.title': 'Welcome' },
355
+ },
356
+ ],
357
+ edges: [
358
+ {
359
+ from: '/:documents.example.dev:core.welcome',
360
+ to: '/:documents.example.dev',
361
+ edgeName: 'references',
362
+ data: { weight: 1 },
363
+ },
364
+ ],
365
+ })
366
+ })
367
+
368
+ test('normalizes a legacy IR to the required standalone-function shape', () => {
369
+ const ir = normalizeLegacySchemaIR({
370
+ version: '1.0',
371
+ domain: 'legacy.example.dev',
372
+ types: {},
373
+ interfaces: {},
374
+ classes: {},
375
+ imports: {},
376
+ functions: {
377
+ legacy: {
378
+ params: {
379
+ required: { type: 'string' },
380
+ maybe: { type: ['string', 'null'] },
381
+ },
382
+ returns: { type: 'boolean' },
383
+ },
384
+ },
385
+ })
386
+ expect(ir.functions.legacy).toMatchObject({
387
+ params: {
388
+ required: { type: 'string' },
389
+ maybe: { type: ['string', 'null'] },
390
+ },
391
+ requiredParams: ['required'],
392
+ input: { required: ['required'] },
393
+ })
394
+ })
395
+ })