@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.2

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 (180) hide show
  1. package/README.md +7 -11
  2. package/dist/astrale.js +3737 -3523
  3. package/dist/public/connect-core.js +396 -392
  4. package/dist/public/keys/index.js +371 -378
  5. package/dist/public/paths/index.js +371 -378
  6. package/dist/types/lib/idp.d.ts +6 -0
  7. package/dist/types/lib/meta.d.ts +2 -2
  8. package/package.json +6 -4
  9. package/src/admin/binding.ts +168 -0
  10. package/src/admin/catalog/__tests__/client.test.ts +2 -2
  11. package/src/admin/catalog/client.ts +4 -9
  12. package/src/admin/instance/__tests__/client.test.ts +2 -2
  13. package/src/admin/instance/client.ts +6 -11
  14. package/src/commands/__tests__/auth-login.test.ts +54 -1
  15. package/src/commands/__tests__/call.test.ts +5 -1
  16. package/src/commands/__tests__/domain-install-operation.test.ts +23 -7
  17. package/src/commands/__tests__/domain-install-owned.test.ts +5 -1
  18. package/src/commands/__tests__/domain-list.test.ts +83 -4
  19. package/src/commands/__tests__/install-identity-override.test.ts +30 -19
  20. package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
  21. package/src/commands/__tests__/update.test.ts +23 -16
  22. package/src/commands/domain/install.ts +11 -15
  23. package/src/commands/domain/list.ts +17 -12
  24. package/src/commands/studio.ts +20 -0
  25. package/src/commands/update.ts +9 -5
  26. package/src/connection/__tests__/errors.test.ts +16 -12
  27. package/src/connection/__tests__/exchange.test.ts +6 -5
  28. package/src/connection/exchange.ts +5 -4
  29. package/src/lib/__tests__/domain-publication.test.ts +144 -0
  30. package/src/lib/__tests__/idp-session.test.ts +22 -0
  31. package/src/lib/__tests__/idp.test.ts +2 -0
  32. package/src/lib/__tests__/instance.test.ts +6 -0
  33. package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
  34. package/src/lib/__tests__/update.test.ts +12 -0
  35. package/src/lib/domain-publication.ts +102 -0
  36. package/src/lib/idp.ts +5 -1
  37. package/src/lib/instance.ts +6 -1
  38. package/src/lib/login-flow.ts +13 -3
  39. package/src/lib/meta.ts +2 -2
  40. package/src/lib/update.ts +4 -4
  41. package/src/program/__tests__/program.test.ts +1 -1
  42. package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-lwhFo7vB.js} +1 -1
  43. package/studio/client/dist/assets/index-B-c-smo9.js +8 -0
  44. package/studio/client/dist/assets/index-BckHuAWk.js +81 -0
  45. package/studio/client/dist/assets/index-C4aNQ6dz.css +1 -0
  46. package/studio/client/dist/index.html +2 -2
  47. package/studio/package.json +3 -1
  48. package/studio/server/agent/bridge/stdio.ts +8 -1
  49. package/studio/server/agent/conversation.test.ts +10 -5
  50. package/studio/server/agent/conversation.ts +56 -8
  51. package/studio/server/agent/harness/codex/loadout.ts +18 -21
  52. package/studio/server/agent/harness/gateway/config.ts +20 -18
  53. package/studio/server/agent/harness/selection.ts +9 -1
  54. package/studio/server/agent/prompts/system.ts +3 -2
  55. package/studio/server/agent/prompts/turn.ts +13 -4
  56. package/studio/server/agent/run/completion.ts +2 -2
  57. package/studio/server/agent/run/preparation.ts +6 -5
  58. package/studio/server/agent/run/transcript.test.ts +35 -1
  59. package/studio/server/agent/run/transcript.ts +169 -3
  60. package/studio/server/agent/run/usage.test.ts +24 -0
  61. package/studio/server/agent/run/usage.ts +26 -1
  62. package/studio/server/api/canvas.ts +44 -0
  63. package/studio/server/api/comments.ts +85 -0
  64. package/studio/server/api/context.ts +61 -0
  65. package/studio/server/api/deployment.ts +39 -0
  66. package/studio/server/api/documents.ts +55 -0
  67. package/studio/server/api/domain.ts +59 -0
  68. package/studio/server/api/http.ts +45 -0
  69. package/studio/server/api/project.ts +57 -0
  70. package/studio/server/api/schema.ts +12 -0
  71. package/studio/server/api/views.ts +57 -0
  72. package/studio/server/api/workspace.ts +60 -0
  73. package/studio/server/api.test.ts +147 -0
  74. package/studio/server/api.ts +20 -422
  75. package/studio/server/cache.test.ts +111 -0
  76. package/studio/server/cache.ts +163 -9
  77. package/studio/server/cli-consumers.test.ts +494 -0
  78. package/studio/server/cli.test.ts +88 -0
  79. package/studio/server/cli.ts +240 -0
  80. package/studio/server/client-package.ts +25 -1
  81. package/studio/server/domain.test.ts +30 -2
  82. package/studio/server/domain.ts +14 -2
  83. package/studio/server/environment/dotenv-preview.test.ts +39 -0
  84. package/studio/server/environment/dotenv-preview.ts +27 -0
  85. package/studio/server/environment/files.test.ts +53 -0
  86. package/studio/server/{state/env.ts → environment/files.ts} +7 -48
  87. package/studio/server/{state → handoff}/copy.ts +21 -6
  88. package/studio/server/handoff/service.test.ts +22 -0
  89. package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
  90. package/studio/server/instances/active.ts +72 -0
  91. package/studio/server/instances/deploy-record.test.ts +31 -0
  92. package/studio/server/instances/deploy-record.ts +37 -0
  93. package/studio/server/instances/deploy.ts +56 -0
  94. package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
  95. package/studio/server/instances/probe.ts +67 -0
  96. package/studio/server/instances/status.test.ts +12 -0
  97. package/studio/server/instances/status.ts +49 -0
  98. package/studio/server/introspect/anatomy/client-tree.ts +57 -0
  99. package/studio/server/introspect/anatomy/env-fields.ts +66 -0
  100. package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
  101. package/studio/server/introspect/anatomy/source.ts +181 -0
  102. package/studio/server/introspect/anatomy/views/legacy.ts +232 -0
  103. package/studio/server/introspect/anatomy/views/routes.ts +105 -0
  104. package/studio/server/introspect/anatomy/views.ts +58 -0
  105. package/studio/server/introspect/anatomy-extras.test.ts +91 -4
  106. package/studio/server/introspect/anatomy-extras.ts +7 -726
  107. package/studio/server/introspect/anatomy.ts +46 -19
  108. package/studio/server/introspect/bundle.ts +15 -6
  109. package/studio/server/introspect/canonical-schema.test.ts +79 -0
  110. package/studio/server/introspect/canonical-schema.ts +80 -18
  111. package/studio/server/introspect/config-preview.test.ts +32 -0
  112. package/studio/server/introspect/config-preview.ts +119 -0
  113. package/studio/server/introspect/diff.test.ts +7 -11
  114. package/studio/server/introspect/diff.ts +28 -37
  115. package/studio/server/introspect/extractor.ts +8 -6
  116. package/studio/server/introspect/hash.ts +5 -2
  117. package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
  118. package/studio/server/introspect/revision.test.ts +54 -0
  119. package/studio/server/introspect/revision.ts +49 -0
  120. package/studio/server/introspect/runtime.test.ts +63 -5
  121. package/studio/server/introspect/runtime.ts +49 -3
  122. package/studio/server/introspect/schema-ir-json.ts +181 -0
  123. package/studio/server/introspect/source-overlay/annotations.ts +14 -0
  124. package/studio/server/introspect/source-overlay/handlers.ts +681 -0
  125. package/studio/server/introspect/source-overlay/kernel-calls.ts +49 -0
  126. package/studio/server/introspect/source-overlay/project.ts +248 -0
  127. package/studio/server/introspect/source-overlay/spans.ts +360 -0
  128. package/studio/server/json.ts +46 -0
  129. package/studio/server/lifecycle.ts +5 -1
  130. package/studio/server/sse.test.ts +26 -0
  131. package/studio/server/sse.ts +4 -1
  132. package/studio/server/state/baseline.test.ts +212 -1
  133. package/studio/server/state/baseline.ts +145 -29
  134. package/studio/server/state/comments.test.ts +66 -1
  135. package/studio/server/state/comments.ts +173 -10
  136. package/studio/server/state/context.ts +34 -2
  137. package/studio/server/state/documents.ts +42 -1
  138. package/studio/server/state/integrations.ts +27 -1
  139. package/studio/server/state/layout.test.ts +34 -0
  140. package/studio/server/state/layout.ts +44 -17
  141. package/studio/server/state/settings.ts +29 -22
  142. package/studio/server/state/store.test.ts +122 -0
  143. package/studio/server/state/store.ts +66 -17
  144. package/studio/server/state/visibility.ts +22 -7
  145. package/studio/server/views/model.test.ts +57 -0
  146. package/studio/server/views/model.ts +66 -0
  147. package/studio/server/views/runtime.ts +40 -0
  148. package/studio/server/views/selection-repository.test.ts +31 -0
  149. package/studio/server/views/selection-repository.ts +60 -0
  150. package/studio/server/views/session.test.ts +73 -0
  151. package/studio/server/views/session.ts +166 -0
  152. package/studio/server/{state/views.test.ts → views/target.test.ts} +8 -159
  153. package/studio/server/views/target.ts +248 -0
  154. package/studio/server/watch.ts +1 -1
  155. package/studio/server/workspace/catalog.test.ts +17 -0
  156. package/studio/server/workspace/catalog.ts +43 -0
  157. package/studio/server/{state → workspace}/create.test.ts +1 -1
  158. package/studio/server/{state → workspace}/create.ts +1 -1
  159. package/studio/server/{state → workspace}/git.ts +1 -1
  160. package/studio/server/workspace/updates.ts +83 -0
  161. package/studio/server/workspace-state.ts +1 -1
  162. package/studio/shared/contracts/README.md +16 -0
  163. package/studio/shared/contracts/agent.ts +270 -0
  164. package/studio/shared/contracts/runtime.ts +50 -0
  165. package/studio/shared/contracts/schema.ts +462 -0
  166. package/studio/shared/contracts/surface.test.ts +319 -0
  167. package/studio/shared/contracts/workspace.ts +274 -0
  168. package/studio/shared/layout.test.ts +116 -0
  169. package/studio/shared/schema/identity.test.ts +58 -0
  170. package/studio/shared/schema/identity.ts +82 -0
  171. package/studio/shared/types.ts +11 -1099
  172. package/studio/tsconfig.json +1 -1
  173. package/viewer/dist/main.js +35 -35
  174. package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
  175. package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
  176. package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
  177. package/studio/server/state/catalog.ts +0 -117
  178. package/studio/server/state/instance.ts +0 -280
  179. package/studio/server/state/updates.ts +0 -63
  180. package/studio/server/state/views.ts +0 -535
@@ -0,0 +1,144 @@
1
+ import { publication } from '@astrale-os/kernel-protocol'
2
+ import { defineDomain } from '@astrale-os/sdk'
3
+ import { issuer } from '@astrale-os/sdk/auth'
4
+ import { createDeployment } from '@astrale-os/sdk/deployment'
5
+ import { defineSchema } from '@astrale-os/sdk/schema/v1'
6
+ import { describe, expect, test } from 'bun:test'
7
+
8
+ import { fetchDomainPublication } from '../domain-publication'
9
+
10
+ const ROOT = 'https://publication-reader.example.dev'
11
+ const PUBLICATION_URL = `${ROOT}/.well-known/astrale/domain.json`
12
+ const MAXIMUM_PUBLICATION_BYTES = 1024 * 1024
13
+ const schema = defineSchema('publication-reader.example.dev', {})
14
+ const definition = defineDomain({
15
+ schema,
16
+ handlers: { functions: {}, classes: {}, interfaces: {} },
17
+ })
18
+ const deployed = createDeployment({
19
+ definition,
20
+ issuer: issuer.accept(ROOT),
21
+ bundleHref: `${ROOT}/domain.bundle.json`,
22
+ bindings: { callables: [] },
23
+ }).publication
24
+
25
+ describe('Domain Publication reader', () => {
26
+ test('fetches only the canonical path with redirects disabled', async () => {
27
+ const requests: Array<{ readonly url: string; readonly redirect?: RequestRedirect }> = []
28
+ const fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
29
+ requests.push({ url: String(input), redirect: init?.redirect })
30
+ return Response.json(deployed)
31
+ }
32
+
33
+ await expect(fetchDomainPublication(`${ROOT}/`, undefined, fetch)).resolves.toEqual(deployed)
34
+ expect(requests).toEqual([{ url: PUBLICATION_URL, redirect: 'error' }])
35
+ })
36
+
37
+ test('propagates redirect rejection instead of accepting another target', async () => {
38
+ const fetch = async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
39
+ expect(init?.redirect).toBe('error')
40
+ throw new TypeError('redirect rejected')
41
+ }
42
+
43
+ await expect(fetchDomainPublication(ROOT, undefined, fetch)).rejects.toThrow(
44
+ 'redirect rejected',
45
+ )
46
+ })
47
+
48
+ test('stops at one MiB and cancels an over-limit response stream', async () => {
49
+ let pulls = 0
50
+ let cancelled = false
51
+ const body = new ReadableStream<Uint8Array>(
52
+ {
53
+ pull(controller) {
54
+ pulls += 1
55
+ if (pulls === 1) {
56
+ controller.enqueue(new Uint8Array(MAXIMUM_PUBLICATION_BYTES))
57
+ return
58
+ }
59
+ if (pulls === 2) {
60
+ controller.enqueue(Uint8Array.of(0))
61
+ return
62
+ }
63
+ controller.error(new Error('reader consumed beyond its byte limit'))
64
+ },
65
+ cancel() {
66
+ cancelled = true
67
+ },
68
+ },
69
+ { highWaterMark: 0 },
70
+ )
71
+ const fetch = async (): Promise<Response> => new Response(body)
72
+
73
+ await expect(fetchDomainPublication(ROOT, undefined, fetch)).rejects.toThrow(
74
+ `exceeded ${MAXIMUM_PUBLICATION_BYTES} bytes`,
75
+ )
76
+ expect(pulls).toBe(2)
77
+ expect(cancelled).toBe(true)
78
+ })
79
+
80
+ test('rejects an oversized Content-Length before pulling and cancels the body', async () => {
81
+ let pulls = 0
82
+ let cancelled = false
83
+ const body = new ReadableStream<Uint8Array>(
84
+ {
85
+ pull() {
86
+ pulls += 1
87
+ },
88
+ cancel() {
89
+ cancelled = true
90
+ },
91
+ },
92
+ { highWaterMark: 0 },
93
+ )
94
+ const fetch = async (): Promise<Response> =>
95
+ new Response(body, {
96
+ headers: { 'content-length': String(MAXIMUM_PUBLICATION_BYTES + 1) },
97
+ })
98
+
99
+ await expect(fetchDomainPublication(ROOT, undefined, fetch)).rejects.toThrow(
100
+ `exceeded ${MAXIMUM_PUBLICATION_BYTES} bytes`,
101
+ )
102
+ expect(pulls).toBe(0)
103
+ expect(cancelled).toBe(true)
104
+ })
105
+
106
+ test.each([
107
+ ['invalid UTF-8', Uint8Array.of(0xc3, 0x28), TypeError],
108
+ ['invalid JSON', new TextEncoder().encode('{'), SyntaxError],
109
+ ['invalid Publication', new TextEncoder().encode('{}'), publication.PublicationError],
110
+ ] as const)('rejects %s before returning a Publication', async (_label, bytes, Cause) => {
111
+ const fetch = async (): Promise<Response> => new Response(bytes)
112
+ const error = await rejection(fetchDomainPublication(ROOT, undefined, fetch))
113
+
114
+ expect(error.message).toBe(`GET ${PUBLICATION_URL} returned an invalid Domain Publication`)
115
+ expect(error.cause).toBeInstanceOf(Cause)
116
+ })
117
+
118
+ test.each([
119
+ `${ROOT}/nested`,
120
+ `${ROOT}?deployment=current`,
121
+ `${ROOT}#fragment`,
122
+ 'https://user:secret@publication-reader.example.dev',
123
+ 'ftp://publication-reader.example.dev',
124
+ ])('rejects a non-origin deployment URL before fetch: %s', async (input) => {
125
+ let called = false
126
+ const fetch = async (): Promise<Response> => {
127
+ called = true
128
+ return Response.json(deployed)
129
+ }
130
+
131
+ await expect(fetchDomainPublication(input, undefined, fetch)).rejects.toBeInstanceOf(TypeError)
132
+ expect(called).toBe(false)
133
+ })
134
+ })
135
+
136
+ async function rejection(input: Promise<unknown>): Promise<Error> {
137
+ try {
138
+ await input
139
+ } catch (cause) {
140
+ if (cause instanceof Error) return cause
141
+ throw new Error('Expected an Error rejection.', { cause })
142
+ }
143
+ throw new Error('Expected the promise to reject.')
144
+ }
@@ -68,11 +68,29 @@ describe('ensureFreshSession', () => {
68
68
  expect(result.orgHintCalls).toBe(1)
69
69
  expect(server.refreshCount()).toBe(1)
70
70
  const session = await readSession()
71
+ expect(session.clientId).toBe('c_1')
71
72
  expect(session.refresh_token).toBe('rt-1')
72
73
  expect((session.tokens as Record<string, { access_token: string }>)[AUD].access_token).toBe(
73
74
  result.token as string,
74
75
  )
75
76
  expect(server.lastOrganizationId()).toBe('org_1')
77
+ expect(server.lastClientId()).toBe('c_1')
78
+ } finally {
79
+ await server.stop()
80
+ }
81
+ })
82
+
83
+ test('refreshes with the client that issued the session instead of the IdP default', async () => {
84
+ const server = rotationServer()
85
+ try {
86
+ await writeIdpConfig(server.url)
87
+ await writeSession({ clientId: 'c_session', expires_at: past() })
88
+
89
+ const result = await runDriver('ensure', { DRIVER_AUDIENCE: AUD })
90
+
91
+ expect(result.ok).toBe(true)
92
+ expect(server.lastClientId()).toBe('c_session')
93
+ expect((await readSession()).clientId).toBe('c_session')
76
94
  } finally {
77
95
  await server.stop()
78
96
  }
@@ -273,6 +291,7 @@ function rotationServer(opts?: {
273
291
  }): {
274
292
  url: string
275
293
  refreshCount: () => number
294
+ lastClientId: () => string | undefined
276
295
  lastOrganizationId: () => string | undefined
277
296
  stop: () => Promise<void>
278
297
  } {
@@ -280,6 +299,7 @@ function rotationServer(opts?: {
280
299
  // refresh token is invalid_grant.
281
300
  let current = 'rt-0'
282
301
  let count = 0
302
+ let lastClientId: string | undefined
283
303
  let lastOrgId: string | undefined
284
304
  const server = Bun.serve({
285
305
  port: 0,
@@ -300,6 +320,7 @@ function rotationServer(opts?: {
300
320
  if (opts?.delayMs) await new Promise((r) => setTimeout(r, opts.delayMs))
301
321
  count += 1
302
322
  current = `rt-${count}`
323
+ lastClientId = form.get('client_id') ?? undefined
303
324
  lastOrgId = form.get('organization_id') ?? undefined
304
325
  return Response.json({
305
326
  access_token: unsignedJwt({ aud: AUD, exp: futureEpoch() }),
@@ -312,6 +333,7 @@ function rotationServer(opts?: {
312
333
  return {
313
334
  url: `http://${server.hostname}:${server.port}`,
314
335
  refreshCount: () => count,
336
+ lastClientId: () => lastClientId,
315
337
  lastOrganizationId: () => lastOrgId,
316
338
  stop: () => server.stop(true),
317
339
  }
@@ -100,6 +100,7 @@ describe('IdP schemas and token helpers', () => {
100
100
  idp: 'workos',
101
101
  issuer: 'https://example.authkit.app',
102
102
  subject: 'user_123',
103
+ clientId: 'client_123',
103
104
  access_token: 'opaque-token',
104
105
  refresh_token: 'refresh-token',
105
106
  token_type: 'Bearer',
@@ -110,6 +111,7 @@ describe('IdP schemas and token helpers', () => {
110
111
  })
111
112
 
112
113
  expect(session.claims?.email).toBe('alice@example.com')
114
+ expect(session.clientId).toBe('client_123')
113
115
  expect(session.refresh_token).toBe('refresh-token')
114
116
  })
115
117
 
@@ -74,6 +74,12 @@ describe('InstanceStoreSchema', () => {
74
74
  expect(normalizeInstanceKernelUrl('https://testmarc.eu.astrale.ai/')).toBe(
75
75
  'https://testmarc.eu.astrale.ai/api',
76
76
  )
77
+ expect(normalizeInstanceKernelUrl('https://testmarc.eu.beta.astrale.ai')).toBe(
78
+ 'https://testmarc.eu.beta.astrale.ai/api',
79
+ )
80
+ expect(normalizeInstanceKernelUrl('https://shell.beta.astrale.ai')).toBe(
81
+ 'https://shell.beta.astrale.ai',
82
+ )
77
83
  })
78
84
 
79
85
  test('does not rewrite explicit kernel or non-managed URLs', () => {
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { readFileSync } from 'node:fs'
3
- import { join } from 'node:path'
2
+ import { readFileSync, readdirSync, statSync } from 'node:fs'
3
+ import { join, relative } from 'node:path'
4
4
  import { Project } from 'ts-morph'
5
5
 
6
6
  /**
@@ -19,6 +19,18 @@ import { Project } from 'ts-morph'
19
19
 
20
20
  const CLI_ROOT = new URL('../../../', import.meta.url).pathname
21
21
 
22
+ interface PackageManifest {
23
+ dependencies?: Record<string, string>
24
+ scripts?: Record<string, string>
25
+ }
26
+
27
+ const cliPackage = JSON.parse(
28
+ readFileSync(join(CLI_ROOT, 'package.json'), 'utf8'),
29
+ ) as PackageManifest
30
+ const studioPackage = JSON.parse(
31
+ readFileSync(join(CLI_ROOT, 'studio/package.json'), 'utf8'),
32
+ ) as PackageManifest
33
+
22
34
  /** Base package of a bare specifier: `@scope/pkg/sub` → `@scope/pkg`, `pkg/sub` → `pkg`. */
23
35
  function basePackage(spec: string): string {
24
36
  const parts = spec.split('/')
@@ -29,26 +41,33 @@ function isBuiltin(spec: string): boolean {
29
41
  return spec.startsWith('node:') || spec === 'bun' || spec.startsWith('bun:')
30
42
  }
31
43
 
44
+ function runtimeFiles(directory: string): string[] {
45
+ return readdirSync(directory).flatMap((entry) => {
46
+ const path = join(directory, entry)
47
+ if (statSync(path).isDirectory()) return runtimeFiles(path)
48
+ return /\.(?:ts|tsx)$/.test(path) && !/\.test\.(?:ts|tsx)$/.test(path) ? [path] : []
49
+ })
50
+ }
51
+
52
+ const studioPath = (path: string) => relative(CLI_ROOT, path).replaceAll('\\', '/')
53
+
32
54
  describe('studio server runtime deps', () => {
33
- const deps = new Set(
34
- Object.keys(
35
- (
36
- JSON.parse(readFileSync(join(CLI_ROOT, 'package.json'), 'utf8')) as {
37
- dependencies?: Record<string, string>
38
- }
39
- ).dependencies ?? {},
40
- ),
41
- )
55
+ const deps = new Set(Object.keys(cliPackage.dependencies ?? {}))
42
56
 
43
57
  const project = new Project({ skipAddingFilesFromTsConfig: true })
44
58
  project.addSourceFilesAtPaths([
45
59
  join(CLI_ROOT, 'studio/server/**/*.ts'),
60
+ join(CLI_ROOT, 'studio/server/**/*.tsx'),
46
61
  join(CLI_ROOT, 'studio/shared/**/*.ts'),
62
+ join(CLI_ROOT, 'studio/shared/**/*.tsx'),
47
63
  ])
64
+ const runtimeSourceFiles = project
65
+ .getSourceFiles()
66
+ .filter((sourceFile) => !/\.test\.(?:ts|tsx)$/.test(sourceFile.getBaseName()))
48
67
 
49
68
  // getImportStringLiterals covers static imports, re-exports, and dynamic import().
50
69
  const offenders: Record<string, string[]> = {}
51
- for (const sf of project.getSourceFiles()) {
70
+ for (const sf of runtimeSourceFiles) {
52
71
  const specs = sf.getImportStringLiterals().map((l) => l.getLiteralValue())
53
72
  for (const spec of specs) {
54
73
  if (spec.startsWith('.') || spec.startsWith('/') || isBuiltin(spec)) continue
@@ -66,9 +85,27 @@ describe('studio server runtime deps', () => {
66
85
  ).toEqual({})
67
86
  })
68
87
 
69
- test('parsed at least the known studio server files', () => {
70
- // Guard against the glob silently matching nothing (which would make the
71
- // invariant vacuously pass).
72
- expect(project.getSourceFiles().length).toBeGreaterThan(10)
88
+ test('parses every shipped Studio server and shared runtime source', () => {
89
+ const expected = [
90
+ ...runtimeFiles(join(CLI_ROOT, 'studio/server')),
91
+ ...runtimeFiles(join(CLI_ROOT, 'studio/shared')),
92
+ ]
93
+ .map(studioPath)
94
+ .sort()
95
+ const parsed = runtimeSourceFiles
96
+ .map((sourceFile) => studioPath(sourceFile.getFilePath()))
97
+ .sort()
98
+
99
+ expect(parsed).toEqual(expected)
100
+ })
101
+ })
102
+
103
+ describe('studio test gate', () => {
104
+ test('the root and Studio suites include all client, server, and shared tests', () => {
105
+ expect(cliPackage.scripts?.test).toBe(
106
+ 'bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs',
107
+ )
108
+ expect(cliPackage.scripts?.['test:studio']).toBe('pnpm --dir studio test')
109
+ expect(studioPackage.scripts?.test).toBe('bun test client/src server shared')
73
110
  })
74
111
  })
@@ -4,6 +4,8 @@ import { tmpdir } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
 
6
6
  import {
7
+ DEFAULT_UPDATE_CHANNEL,
8
+ InstallMetadataSchema,
7
9
  releaseBase,
8
10
  shouldUpdate,
9
11
  updateAstrale,
@@ -84,6 +86,16 @@ async function makeInstall(
84
86
  }
85
87
 
86
88
  describe('update helpers', () => {
89
+ test('defaults missing install metadata to the beta channel', () => {
90
+ expect(DEFAULT_UPDATE_CHANNEL).toBe('beta')
91
+ expect(
92
+ InstallMetadataSchema.parse({
93
+ method: 'script',
94
+ bin: '/tmp/astrale',
95
+ }).channel,
96
+ ).toBe('beta')
97
+ })
98
+
87
99
  test('releaseBase resolves exact versions and channels', () => {
88
100
  const meta: InstallMetadata = {
89
101
  method: 'script',
@@ -0,0 +1,102 @@
1
+ import { publication } from '@astrale-os/kernel-protocol'
2
+
3
+ const MAXIMUM_PUBLICATION_BYTES = 1024 * 1024
4
+ type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
5
+
6
+ /** Fetch and admit one canonical Domain Publication without following its bundle. */
7
+ export async function fetchDomainPublication(
8
+ input: string,
9
+ signal?: AbortSignal,
10
+ fetchImpl: FetchLike = globalThis.fetch,
11
+ ): Promise<publication.Publication> {
12
+ const root = deploymentRoot(input)
13
+ const url = new URL(publication.PATH, root)
14
+ const response = await fetchImpl(url, {
15
+ redirect: 'error',
16
+ ...(signal === undefined ? {} : { signal }),
17
+ })
18
+ if (!response.ok) {
19
+ await cancel(response.body)
20
+ throw new Error(`GET ${url.href} → ${response.status}`)
21
+ }
22
+ const bytes = await readBounded(response, url)
23
+ try {
24
+ const source = new TextDecoder('utf-8', { fatal: true }).decode(bytes)
25
+ return publication.accept(JSON.parse(source) as unknown)
26
+ } catch (cause) {
27
+ throw new Error(`GET ${url.href} returned an invalid Domain Publication`, { cause })
28
+ }
29
+ }
30
+
31
+ async function readBounded(response: Response, url: URL): Promise<Uint8Array> {
32
+ const declared = response.headers.get('content-length')
33
+ if (
34
+ declared !== null &&
35
+ (!/^\d+$/u.test(declared) ||
36
+ !Number.isSafeInteger(Number(declared)) ||
37
+ Number(declared) > MAXIMUM_PUBLICATION_BYTES)
38
+ ) {
39
+ await cancel(response.body)
40
+ throw sizeError(url)
41
+ }
42
+ if (response.body === null) return new Uint8Array()
43
+
44
+ const reader = response.body.getReader()
45
+ const chunks: Uint8Array[] = []
46
+ let size = 0
47
+ let complete = false
48
+ try {
49
+ for (;;) {
50
+ const next = await reader.read()
51
+ if (next.done) {
52
+ complete = true
53
+ break
54
+ }
55
+ if (next.value.byteLength === 0) continue
56
+ if (next.value.byteLength > MAXIMUM_PUBLICATION_BYTES - size) {
57
+ throw sizeError(url)
58
+ }
59
+ chunks.push(next.value)
60
+ size += next.value.byteLength
61
+ }
62
+ } finally {
63
+ if (!complete) await reader.cancel().catch(() => undefined)
64
+ reader.releaseLock()
65
+ }
66
+
67
+ const bytes = new Uint8Array(size)
68
+ let offset = 0
69
+ for (const chunk of chunks) {
70
+ bytes.set(chunk, offset)
71
+ offset += chunk.byteLength
72
+ }
73
+ return bytes
74
+ }
75
+
76
+ function sizeError(url: URL): Error {
77
+ return new Error(`GET ${url.href} exceeded ${MAXIMUM_PUBLICATION_BYTES} bytes`)
78
+ }
79
+
80
+ async function cancel(body: ReadableStream<Uint8Array> | null): Promise<void> {
81
+ if (body !== null) await body.cancel().catch(() => undefined)
82
+ }
83
+
84
+ function deploymentRoot(input: string): URL {
85
+ let url: URL
86
+ try {
87
+ url = new URL(input)
88
+ } catch {
89
+ throw new TypeError(`Domain deployment URL is invalid: ${input}`)
90
+ }
91
+ if (
92
+ !['http:', 'https:'].includes(url.protocol) ||
93
+ url.username !== '' ||
94
+ url.password !== '' ||
95
+ url.search !== '' ||
96
+ url.hash !== '' ||
97
+ !['', '/'].includes(url.pathname)
98
+ ) {
99
+ throw new TypeError(`Domain deployment URL must be an HTTP(S) origin: ${input}`)
100
+ }
101
+ return new URL(url.origin)
102
+ }
package/src/lib/idp.ts CHANGED
@@ -145,6 +145,8 @@ export const IdpSessionSchema = z
145
145
  idp: z.string(),
146
146
  issuer: z.string().url(),
147
147
  subject: z.string(),
148
+ /** OAuth client that issued this session's refresh token. */
149
+ clientId: z.string().optional(),
148
150
  audience: z.string().optional(),
149
151
  organizationId: z.string().optional(),
150
152
  access_token: z.string(),
@@ -644,10 +646,11 @@ export async function refreshSession(
644
646
  if (!session.refresh_token)
645
647
  throw new Error(`IdP session for "${identityName}" has no refresh token`)
646
648
  const idp = await readIdpConfig(session.idp)
649
+ const clientId = requireClientId(idp, session.clientId)
647
650
  const params = new URLSearchParams({
648
651
  grant_type: 'refresh_token',
649
652
  refresh_token: session.refresh_token,
650
- client_id: requireClientId(idp),
653
+ client_id: clientId,
651
654
  })
652
655
  const audience = opts.audience ?? session.audience
653
656
  if (audience) params.set('audience', audience)
@@ -674,6 +677,7 @@ export async function refreshSession(
674
677
  const expiresAt = tokenExpiresAt(token)
675
678
  const next: IdpSession = {
676
679
  ...session,
680
+ clientId,
677
681
  access_token: token.access_token,
678
682
  id_token: token.id_token ?? session.id_token,
679
683
  refresh_token: token.refresh_token ?? session.refresh_token,
@@ -448,7 +448,12 @@ function isRegionRoutedInstanceRoot(url: URL): boolean {
448
448
  if (url.search || url.hash) return false
449
449
 
450
450
  const labels = url.hostname.toLowerCase().split('.')
451
- return labels.length === 4 && labels[2] === 'astrale' && labels[3] === 'ai'
451
+ return (
452
+ (labels.length === 4 || labels.length === 5) &&
453
+ /^[a-z]{2}(?:-[a-z0-9]+)*$/u.test(labels[1] ?? '') &&
454
+ labels.at(-2) === 'astrale' &&
455
+ labels.at(-1) === 'ai'
456
+ )
452
457
  }
453
458
 
454
459
  function definedEntry(entry: AddInstanceOpts): AddInstanceOpts {
@@ -7,7 +7,9 @@ import {
7
7
  normalizeTokenResponse,
8
8
  pollDeviceToken,
9
9
  readIdpConfigOrBuiltin,
10
+ readIdpSession,
10
11
  readIdpStore,
12
+ requireClientId,
11
13
  requestClientCredentials,
12
14
  requestDeviceAuthorization,
13
15
  saveIdpSession,
@@ -71,10 +73,17 @@ export type LoginResult = {
71
73
  */
72
74
  export async function loginViaIdp(opts: LoginFlowOpts): Promise<LoginResult> {
73
75
  const idpName = await resolveIdpName(opts.idp)
74
- const idp = await readIdpConfigOrBuiltin(idpName, { clientId: opts.clientId, persist: true })
76
+ const previous = opts.name && !opts.clientId ? await readIdpSession(opts.name) : null
77
+ const savedClientId = previous?.idp === idpName ? previous.clientId : undefined
78
+ const requestedClientId = opts.clientId ?? savedClientId
79
+ const idp = await readIdpConfigOrBuiltin(idpName, {
80
+ clientId: requestedClientId,
81
+ persist: true,
82
+ })
83
+ const clientId = requireClientId(idp, requestedClientId)
75
84
  const scope = opts.scope ?? idp.client.scope ?? 'openid profile email offline_access'
76
85
 
77
- const token = normalizeTokenResponse(await obtainToken(idp, opts, scope))
86
+ const token = normalizeTokenResponse(await obtainToken(idp, { ...opts, clientId }, scope))
78
87
  if (!token.access_token) throw new Error('IdP response did not include access_token')
79
88
  if (opts.audience && !tokenAudienceMatches(token.access_token, opts.audience)) {
80
89
  throw new Error(
@@ -83,7 +92,7 @@ export async function loginViaIdp(opts: LoginFlowOpts): Promise<LoginResult> {
83
92
  }
84
93
 
85
94
  const claims = decodeTokenClaims(token.id_token ?? token.access_token)
86
- const subject = subjectFromToken(token, opts.clientId ?? idp.client.client_id ?? idpName)
95
+ const subject = subjectFromToken(token, clientId)
87
96
  const issuer = issuerFromToken(token, idp.metadata.issuer)
88
97
  const identityName = opts.name ?? identityNameFromClaims(claims, idpName)
89
98
  const session: IdpSession = {
@@ -91,6 +100,7 @@ export async function loginViaIdp(opts: LoginFlowOpts): Promise<LoginResult> {
91
100
  idp: idpName,
92
101
  issuer,
93
102
  subject,
103
+ clientId,
94
104
  audience: opts.audience,
95
105
  access_token: token.access_token,
96
106
  id_token: token.id_token,
package/src/lib/meta.ts CHANGED
@@ -6,8 +6,8 @@
6
6
  * `jwks_uri`) and JWKS at `/.well-known/jwks.json`. These helpers probe
7
7
  * both to verify the issuer is alive and publishes at least one key.
8
8
  *
9
- * Domain workers may expose `/meta` for deployment drift detection; that is
10
- * outside this connect-only CLI surface.
9
+ * Domain deployment readiness uses the canonical Domain Publication; that is
10
+ * outside this issuer-connectivity helper.
11
11
  */
12
12
 
13
13
  import { IssuerUnreachableError } from '../errors'
package/src/lib/update.ts CHANGED
@@ -9,11 +9,11 @@ import { INSTALL_PATH } from '../state/index'
9
9
  import { run } from './proc'
10
10
 
11
11
  const DEFAULT_REPO = 'astrale-os/cli'
12
- const DEFAULT_CHANNEL = 'alpha'
12
+ export const DEFAULT_UPDATE_CHANNEL = 'beta'
13
13
 
14
14
  export const InstallMetadataSchema = z.object({
15
15
  method: z.literal('script'),
16
- channel: z.string().min(1).default(DEFAULT_CHANNEL),
16
+ channel: z.string().min(1).default(DEFAULT_UPDATE_CHANNEL),
17
17
  version: z.string().min(1).optional(),
18
18
  repo: z.string().min(1).default(DEFAULT_REPO),
19
19
  bin: z.string().min(1),
@@ -162,7 +162,7 @@ export function releaseBase(
162
162
  const version = req.version.replace(/^cli\/v/, '').replace(/^v/, '')
163
163
  return `https://github.com/${repo}/releases/download/cli/v${version}`
164
164
  }
165
- return `https://github.com/${repo}/releases/download/${req.channel ?? meta.channel ?? DEFAULT_CHANNEL}`
165
+ return `https://github.com/${repo}/releases/download/${req.channel ?? meta.channel ?? DEFAULT_UPDATE_CHANNEL}`
166
166
  }
167
167
 
168
168
  export async function fetchManifest(base: string): Promise<UpdateManifest> {
@@ -177,7 +177,7 @@ export function shouldUpdate(currentVersion: string, manifestVersion: string): b
177
177
  export async function updateAstrale(req: UpdateRequest): Promise<UpdateResult> {
178
178
  const meta = await readInstallMetadata(req.installPath)
179
179
  const currentVersion = meta.version ?? req.currentVersion
180
- const channel = req.channel ?? meta.channel ?? DEFAULT_CHANNEL
180
+ const channel = req.channel ?? meta.channel ?? DEFAULT_UPDATE_CHANNEL
181
181
  const platform = req.platform ?? detectPlatform()
182
182
  const key = platformKey(platform)
183
183
  const base = releaseBase(meta, { channel, version: req.version })
@@ -187,7 +187,7 @@ describe('program composition', () => {
187
187
  'whoami',
188
188
  ])
189
189
  expect(createHash('sha256').update(JSON.stringify(surface)).digest('hex')).toBe(
190
- 'b21a8c98f0bb75432b460d4562c2d86f39ffb8e88349962e4611ccbf65fa9f73',
190
+ '44f78959867171661197043f5b47eb6d6890274090370e071d10f11b4204850d',
191
191
  )
192
192
  })
193
193
 
@@ -1 +1 @@
1
- import{m as L}from"./index-LGSWRrk8.js";function S(y,b){for(var u=0;u<b.length;u++){const a=b[u];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in y)){const n=Object.getOwnPropertyDescriptor(a,i);n&&Object.defineProperty(y,i,n.get?n:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(y,Symbol.toStringTag,{value:"Module"}))}function w(y){throw new Error('Could not dynamically require "'+y+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var O={exports:{}},j;function C(){return j||(j=1,(function(y,b){(function(u){y.exports=u()})(function(){return(function(){function u(a,i,n){function d(f,_){if(!i[f]){if(!a[f]){var h=typeof w=="function"&&w;if(!_&&h)return h(f,!0);if(g)return g(f,!0);var o=new Error("Cannot find module '"+f+"'");throw o.code="MODULE_NOT_FOUND",o}var e=i[f]={exports:{}};a[f][0].call(e.exports,function(r){var t=a[f][1][r];return d(t||r)},e,e.exports,u,a,i,n)}return i[f].exports}for(var g=typeof w=="function"&&w,m=0;m<n.length;m++)d(n[m]);return d}return u})()({1:[function(u,a,i){Object.defineProperty(i,"__esModule",{value:!0}),i.default=void 0;function n(o){"@babel/helpers - typeof";return n=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(o)}function d(o,e){if(!(o instanceof e))throw new TypeError("Cannot call a class as a function")}function g(o,e){for(var r=0;r<e.length;r++){var t=e[r];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(o,f(t.key),t)}}function m(o,e,r){return e&&g(o.prototype,e),Object.defineProperty(o,"prototype",{writable:!1}),o}function f(o){var e=_(o,"string");return n(e)=="symbol"?e:e+""}function _(o,e){if(n(o)!="object"||!o)return o;var r=o[Symbol.toPrimitive];if(r!==void 0){var t=r.call(o,e);if(n(t)!="object")return t;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(o)}i.default=(function(){function o(){var e=this,r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},t=r.defaultLayoutOptions,s=t===void 0?{}:t,l=r.algorithms,v=l===void 0?["layered","stress","mrtree","radial","force","disco","sporeOverlap","sporeCompaction","rectpacking"]:l,c=r.workerFactory,p=r.workerUrl;if(d(this,o),this.defaultLayoutOptions=s,this.initialized=!1,typeof p>"u"&&typeof c>"u")throw new Error("Cannot construct an ELK without both 'workerUrl' and 'workerFactory'.");var k=c;typeof p<"u"&&typeof c>"u"&&(k=function(M){return new Worker(M)});var E=k(p);if(typeof E.postMessage!="function")throw new TypeError("Created worker does not provide the required 'postMessage' function.");this.worker=new h(E),this.worker.postMessage({cmd:"register",algorithms:v}).then(function(P){return e.initialized=!0}).catch(console.err)}return m(o,[{key:"layout",value:function(r){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},s=t.layoutOptions,l=s===void 0?this.defaultLayoutOptions:s,v=t.logging,c=v===void 0?!1:v,p=t.measureExecutionTime,k=p===void 0?!1:p;return r?this.worker.postMessage({cmd:"layout",graph:r,layoutOptions:l,options:{logging:c,measureExecutionTime:k}}):Promise.reject(new Error("Missing mandatory parameter 'graph'."))}},{key:"knownLayoutAlgorithms",value:function(){return this.worker.postMessage({cmd:"algorithms"})}},{key:"knownLayoutOptions",value:function(){return this.worker.postMessage({cmd:"options"})}},{key:"knownLayoutCategories",value:function(){return this.worker.postMessage({cmd:"categories"})}},{key:"terminateWorker",value:function(){this.worker&&this.worker.terminate()}}])})();var h=(function(){function o(e){var r=this;if(d(this,o),e===void 0)throw new Error("Missing mandatory parameter 'worker'.");this.resolvers={},this.worker=e,this.worker.onmessage=function(t){setTimeout(function(){r.receive(r,t)},0)}}return m(o,[{key:"postMessage",value:function(r){var t=this.id||0;this.id=t+1,r.id=t;var s=this;return new Promise(function(l,v){s.resolvers[t]=function(c,p){c?(s.convertGwtStyleError(c),v(c)):l(p)},s.worker.postMessage(r)})}},{key:"receive",value:function(r,t){var s=t.data,l=r.resolvers[s.id];l&&(delete r.resolvers[s.id],s.error?l(s.error):l(null,s.data))}},{key:"terminate",value:function(){this.worker&&this.worker.terminate()}},{key:"convertGwtStyleError",value:function(r){if(r){var t=r.__java$exception;t&&(t.cause&&t.cause.backingJsObject&&(r.cause=t.cause.backingJsObject,this.convertGwtStyleError(r.cause)),delete r.__java$exception)}}}])})()},{}],2:[function(u,a,i){var n=u("./elk-api.js").default;Object.defineProperty(a.exports,"__esModule",{value:!0}),a.exports=n,n.default=n},{"./elk-api.js":1}]},{},[2])(2)})})(O)),O.exports}var x=C();const A=L(x),q=S({__proto__:null,default:A},[x]);export{q as e};
1
+ import{m as L}from"./index-BckHuAWk.js";function S(y,b){for(var u=0;u<b.length;u++){const a=b[u];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in y)){const n=Object.getOwnPropertyDescriptor(a,i);n&&Object.defineProperty(y,i,n.get?n:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(y,Symbol.toStringTag,{value:"Module"}))}function w(y){throw new Error('Could not dynamically require "'+y+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var O={exports:{}},j;function C(){return j||(j=1,(function(y,b){(function(u){y.exports=u()})(function(){return(function(){function u(a,i,n){function d(f,_){if(!i[f]){if(!a[f]){var h=typeof w=="function"&&w;if(!_&&h)return h(f,!0);if(g)return g(f,!0);var o=new Error("Cannot find module '"+f+"'");throw o.code="MODULE_NOT_FOUND",o}var e=i[f]={exports:{}};a[f][0].call(e.exports,function(r){var t=a[f][1][r];return d(t||r)},e,e.exports,u,a,i,n)}return i[f].exports}for(var g=typeof w=="function"&&w,m=0;m<n.length;m++)d(n[m]);return d}return u})()({1:[function(u,a,i){Object.defineProperty(i,"__esModule",{value:!0}),i.default=void 0;function n(o){"@babel/helpers - typeof";return n=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(o)}function d(o,e){if(!(o instanceof e))throw new TypeError("Cannot call a class as a function")}function g(o,e){for(var r=0;r<e.length;r++){var t=e[r];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(o,f(t.key),t)}}function m(o,e,r){return e&&g(o.prototype,e),Object.defineProperty(o,"prototype",{writable:!1}),o}function f(o){var e=_(o,"string");return n(e)=="symbol"?e:e+""}function _(o,e){if(n(o)!="object"||!o)return o;var r=o[Symbol.toPrimitive];if(r!==void 0){var t=r.call(o,e);if(n(t)!="object")return t;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(o)}i.default=(function(){function o(){var e=this,r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},t=r.defaultLayoutOptions,s=t===void 0?{}:t,l=r.algorithms,v=l===void 0?["layered","stress","mrtree","radial","force","disco","sporeOverlap","sporeCompaction","rectpacking"]:l,c=r.workerFactory,p=r.workerUrl;if(d(this,o),this.defaultLayoutOptions=s,this.initialized=!1,typeof p>"u"&&typeof c>"u")throw new Error("Cannot construct an ELK without both 'workerUrl' and 'workerFactory'.");var k=c;typeof p<"u"&&typeof c>"u"&&(k=function(M){return new Worker(M)});var E=k(p);if(typeof E.postMessage!="function")throw new TypeError("Created worker does not provide the required 'postMessage' function.");this.worker=new h(E),this.worker.postMessage({cmd:"register",algorithms:v}).then(function(P){return e.initialized=!0}).catch(console.err)}return m(o,[{key:"layout",value:function(r){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},s=t.layoutOptions,l=s===void 0?this.defaultLayoutOptions:s,v=t.logging,c=v===void 0?!1:v,p=t.measureExecutionTime,k=p===void 0?!1:p;return r?this.worker.postMessage({cmd:"layout",graph:r,layoutOptions:l,options:{logging:c,measureExecutionTime:k}}):Promise.reject(new Error("Missing mandatory parameter 'graph'."))}},{key:"knownLayoutAlgorithms",value:function(){return this.worker.postMessage({cmd:"algorithms"})}},{key:"knownLayoutOptions",value:function(){return this.worker.postMessage({cmd:"options"})}},{key:"knownLayoutCategories",value:function(){return this.worker.postMessage({cmd:"categories"})}},{key:"terminateWorker",value:function(){this.worker&&this.worker.terminate()}}])})();var h=(function(){function o(e){var r=this;if(d(this,o),e===void 0)throw new Error("Missing mandatory parameter 'worker'.");this.resolvers={},this.worker=e,this.worker.onmessage=function(t){setTimeout(function(){r.receive(r,t)},0)}}return m(o,[{key:"postMessage",value:function(r){var t=this.id||0;this.id=t+1,r.id=t;var s=this;return new Promise(function(l,v){s.resolvers[t]=function(c,p){c?(s.convertGwtStyleError(c),v(c)):l(p)},s.worker.postMessage(r)})}},{key:"receive",value:function(r,t){var s=t.data,l=r.resolvers[s.id];l&&(delete r.resolvers[s.id],s.error?l(s.error):l(null,s.data))}},{key:"terminate",value:function(){this.worker&&this.worker.terminate()}},{key:"convertGwtStyleError",value:function(r){if(r){var t=r.__java$exception;t&&(t.cause&&t.cause.backingJsObject&&(r.cause=t.cause.backingJsObject,this.convertGwtStyleError(r.cause)),delete r.__java$exception)}}}])})()},{}],2:[function(u,a,i){var n=u("./elk-api.js").default;Object.defineProperty(a.exports,"__esModule",{value:!0}),a.exports=n,n.default=n},{"./elk-api.js":1}]},{},[2])(2)})})(O)),O.exports}var x=C();const A=L(x),q=S({__proto__:null,default:A},[x]);export{q as e};