@astrale-os/cli 0.8.1-alpha.0 → 0.8.1-alpha.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.
- package/dist/astrale.js +62959 -41238
- package/dist/public/connect-core.js +55030 -0
- package/dist/public/keys/index.js +45631 -0
- package/dist/public/paths/index.js +43990 -0
- package/dist/types/admin/instance/model.d.ts +40 -0
- package/dist/types/connect-core.d.ts +22 -0
- package/dist/types/connection/auth.d.ts +33 -0
- package/dist/types/errors.d.ts +23 -0
- package/dist/types/identity/registry.d.ts +25 -0
- package/dist/types/keys/algorithm.d.ts +6 -0
- package/dist/types/keys/credential.d.ts +21 -0
- package/dist/types/keys/index.d.ts +4 -0
- package/dist/types/keys/pair.d.ts +34 -0
- package/dist/types/lib/admin-target.d.ts +74 -0
- package/dist/types/lib/ca-fetch.d.ts +2 -0
- package/dist/types/lib/config.d.ts +71 -0
- package/dist/types/lib/format.d.ts +1 -0
- package/dist/types/lib/idp-session.d.ts +33 -0
- package/dist/types/lib/idp.d.ts +397 -0
- package/dist/types/lib/instance-target.d.ts +36 -0
- package/dist/types/lib/instance.d.ts +186 -0
- package/dist/types/lib/log.d.ts +30 -0
- package/dist/types/lib/login-flow.d.ts +49 -0
- package/dist/types/lib/meta.d.ts +36 -0
- package/dist/types/lib/output.d.ts +83 -0
- package/dist/types/lib/table.d.ts +18 -0
- package/dist/types/lib/validation.d.ts +11 -0
- package/dist/types/paths/index.d.ts +2 -0
- package/dist/types/state/exchange-credentials.d.ts +25 -0
- package/dist/types/state/files.d.ts +9 -0
- package/dist/types/state/identities.d.ts +42 -0
- package/dist/types/state/index.d.ts +8 -0
- package/dist/types/state/paths.d.ts +32 -0
- package/package.json +22 -7
- package/src/admin/.spec/api.d.ts +3 -0
- package/src/admin/.spec/architecture.md +15 -0
- package/src/admin/.spec/icon.svg +3 -0
- package/src/admin/.spec/layout.ts +6 -0
- package/src/admin/catalog/.spec/api.d.ts +65 -0
- package/src/admin/catalog/.spec/architecture.md +6 -0
- package/src/admin/catalog/.spec/icon.svg +3 -0
- package/src/admin/catalog/.spec/layout.ts +6 -0
- package/src/admin/catalog/__tests__/client.test.ts +171 -0
- package/src/admin/catalog/client.ts +214 -0
- package/src/admin/catalog/index.ts +13 -0
- package/src/admin/catalog/model.ts +42 -0
- package/src/admin/catalog/tsconfig.json +8 -0
- package/src/admin/graph/.spec/api.d.ts +28 -0
- package/src/admin/graph/.spec/architecture.md +5 -0
- package/src/admin/graph/.spec/icon.svg +3 -0
- package/src/admin/graph/.spec/layout.ts +3 -0
- package/src/admin/graph/index.ts +6 -0
- package/src/admin/graph/nodes.ts +56 -0
- package/src/admin/index.ts +2 -0
- package/src/admin/instance/.spec/api.d.ts +66 -0
- package/src/admin/instance/.spec/architecture.md +10 -0
- package/src/admin/instance/.spec/icon.svg +3 -0
- package/src/admin/instance/.spec/layout.ts +6 -0
- package/src/admin/instance/__tests__/client.test.ts +289 -0
- package/src/admin/instance/client.ts +298 -0
- package/src/admin/instance/index.ts +16 -0
- package/src/admin/instance/model.ts +65 -0
- package/src/admin/instance/tsconfig.json +8 -0
- package/src/commands/__tests__/admin-instance.test.ts +3 -36
- package/src/commands/__tests__/call.test.ts +21 -68
- package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
- package/src/commands/__tests__/domain-list.test.ts +2 -6
- package/src/commands/__tests__/logs.test.ts +50 -98
- package/src/commands/__tests__/ls.test.ts +21 -17
- package/src/commands/__tests__/read-commands.test.ts +171 -101
- package/src/commands/__tests__/view.test.ts +100 -51
- package/src/commands/admin/status.ts +3 -3
- package/src/commands/admin/use.ts +29 -6
- package/src/commands/auth/login.ts +1 -1
- package/src/commands/auth/logout.ts +4 -2
- package/src/commands/auth/status.ts +2 -2
- package/src/commands/auth/token.ts +2 -2
- package/src/commands/browser.ts +1 -1
- package/src/commands/call.ts +55 -91
- package/src/commands/describe.ts +52 -147
- package/src/commands/domain/install.ts +81 -50
- package/src/commands/domain/list.ts +4 -8
- package/src/commands/domain/publish.ts +9 -31
- package/src/commands/get.ts +32 -65
- package/src/commands/identity/__tests__/register.test.ts +93 -0
- package/src/commands/identity/create.ts +2 -2
- package/src/commands/identity/delete.ts +2 -2
- package/src/commands/identity/export.ts +7 -33
- package/src/commands/identity/import.ts +21 -70
- package/src/commands/identity/list.ts +2 -2
- package/src/commands/identity/register.ts +148 -117
- package/src/commands/identity/sync.ts +2 -2
- package/src/commands/identity/unsync.ts +2 -2
- package/src/commands/identity/use.ts +2 -2
- package/src/commands/identity/whoami.ts +2 -2
- package/src/commands/idp/add.ts +1 -1
- package/src/commands/idp/list.ts +1 -1
- package/src/commands/idp/refresh.ts +1 -1
- package/src/commands/idp/remove.ts +2 -2
- package/src/commands/idp/show.ts +1 -1
- package/src/commands/instance/active.ts +2 -2
- package/src/commands/instance/bookmark.ts +8 -2
- package/src/commands/instance/create.ts +1 -1
- package/src/commands/instance/delete.ts +4 -10
- package/src/commands/instance/forget.ts +1 -1
- package/src/commands/instance/list.ts +3 -3
- package/src/commands/instance/status.ts +5 -7
- package/src/commands/instance/use.ts +3 -3
- package/src/commands/logs.ts +152 -279
- package/src/commands/ls.ts +78 -204
- package/src/commands/mutate.ts +70 -139
- package/src/commands/query.ts +100 -278
- package/src/commands/session/analyze.ts +1 -1
- package/src/commands/session/list.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/status.ts +1 -1
- package/src/commands/studio.ts +1 -1
- package/src/commands/token.ts +26 -21
- package/src/commands/update.ts +1 -1
- package/src/commands/use.ts +2 -3
- package/src/commands/view-serve.ts +1 -1
- package/src/commands/view.ts +67 -109
- package/src/connect-core.test.ts +38 -0
- package/src/connect-core.ts +42 -0
- package/src/connection/.spec/api.d.ts +111 -0
- package/src/connection/.spec/architecture.md +41 -0
- package/src/connection/.spec/capabilities/connection.ts +13 -0
- package/src/connection/.spec/examples/call.ts +7 -0
- package/src/connection/.spec/flows/command.ts +20 -0
- package/src/connection/.spec/flows/session.ts +61 -0
- package/src/connection/.spec/laws/connection.ts +181 -0
- package/src/connection/.spec/layout.ts +19 -0
- package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
- package/src/connection/__tests__/ca-fetch.test.ts +53 -0
- package/src/connection/__tests__/call.test.ts +17 -0
- package/src/connection/__tests__/credential.test.ts +109 -0
- package/src/connection/__tests__/errors.test.ts +96 -0
- package/src/connection/__tests__/exchange.test.ts +224 -0
- package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
- package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
- package/src/connection/__tests__/managed.test.ts +39 -0
- package/src/connection/__tests__/self.test.ts +51 -0
- package/src/connection/__tests__/session.test.ts +140 -0
- package/src/connection/__tests__/target.test.ts +93 -0
- package/src/{kernel → connection}/auth.ts +8 -8
- package/src/connection/call.ts +7 -0
- package/src/{kernel/run.ts → connection/command.ts} +22 -13
- package/src/connection/credential.ts +114 -0
- package/src/{kernel → connection}/errors.ts +32 -9
- package/src/connection/exchange.ts +274 -0
- package/src/connection/index.ts +8 -0
- package/src/connection/self.ts +120 -0
- package/src/connection/session.ts +176 -0
- package/src/connection/target.ts +111 -0
- package/src/connection/tsconfig.json +6 -0
- package/src/graph/.spec/api.d.ts +34 -0
- package/src/graph/.spec/laws/documents.ts +24 -0
- package/src/graph/.spec/laws/projection.ts +13 -0
- package/src/graph/.spec/layout.ts +6 -0
- package/src/graph/__tests__/mutation.test.ts +43 -0
- package/src/graph/__tests__/projection.test.ts +22 -0
- package/src/graph/__tests__/query.test.ts +154 -0
- package/src/graph/index.ts +3 -0
- package/src/graph/mutation.ts +18 -0
- package/src/graph/projection.ts +21 -0
- package/src/graph/query.ts +108 -0
- package/src/graph/tsconfig.json +6 -0
- package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
- package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
- package/src/identity/.spec/api.d.ts +124 -0
- package/src/identity/.spec/architecture.md +33 -0
- package/src/identity/.spec/capabilities/identity.ts +7 -0
- package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
- package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
- package/src/identity/.spec/flows/import.ts +29 -0
- package/src/identity/.spec/laws/identity.ts +56 -0
- package/src/identity/.spec/layout.ts +13 -0
- package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
- package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
- package/src/identity/__tests__/registration.test.ts +64 -0
- package/src/identity/__tests__/registry.test.ts +70 -0
- package/src/identity/__tests__/transfer.test.ts +198 -0
- package/src/identity/index.ts +23 -0
- package/src/identity/registration.ts +72 -0
- package/src/identity/registry.ts +172 -0
- package/src/identity/transfer.ts +187 -0
- package/src/identity/tsconfig.json +6 -0
- package/src/index.ts +1 -0
- package/src/keys/.spec/api.d.ts +74 -0
- package/src/keys/.spec/architecture.md +13 -0
- package/src/keys/.spec/laws/keys.ts +43 -0
- package/src/keys/.spec/layout.ts +6 -0
- package/src/keys/__tests__/algorithm.test.ts +39 -0
- package/src/keys/__tests__/keys.test.ts +168 -0
- package/src/keys/algorithm.ts +33 -0
- package/src/keys/credential.ts +112 -0
- package/src/keys/index.ts +14 -0
- package/src/keys/pair.ts +219 -0
- package/src/keys/tsconfig.json +6 -0
- package/src/lib/__tests__/admin-target.test.ts +67 -7
- package/src/lib/__tests__/binary.test.ts +2 -3
- package/src/lib/__tests__/command-dx.test.ts +2 -2
- package/src/lib/__tests__/config.test.ts +3 -2
- package/src/lib/__tests__/idp.test.ts +4 -6
- package/src/lib/__tests__/instance-target.test.ts +7 -4
- package/src/lib/__tests__/local-status.test.ts +1 -1
- package/src/lib/__tests__/log.test.ts +31 -0
- package/src/lib/__tests__/meta.test.ts +55 -0
- package/src/lib/__tests__/sdk-deps.test.ts +1 -1
- package/src/lib/__tests__/self.test.ts +8 -230
- package/src/lib/__tests__/use-target.test.ts +1 -1
- package/src/lib/__tests__/view-open-intent.test.ts +78 -104
- package/src/lib/__tests__/view-session.test.ts +99 -0
- package/src/lib/admin-domain.ts +39 -25
- package/src/lib/admin-instance.ts +38 -43
- package/src/lib/admin-target.ts +67 -13
- package/src/lib/binary.ts +13 -34
- package/src/lib/browser.ts +1 -1
- package/src/{kernel → lib}/ca-fetch.ts +35 -27
- package/src/lib/config.ts +1 -1
- package/src/lib/idp-session.ts +1 -1
- package/src/lib/idp.ts +3 -8
- package/src/lib/instance-target.ts +12 -8
- package/src/lib/instance.ts +34 -2
- package/src/lib/local-status.ts +2 -2
- package/src/lib/log.ts +3 -1
- package/src/lib/login-flow.ts +32 -5
- package/src/lib/meta.ts +15 -11
- package/src/lib/provision-instance.ts +4 -12
- package/src/lib/self.ts +5 -147
- package/src/lib/update.ts +1 -1
- package/src/lib/use-target.ts +1 -1
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/open-intent.ts +7 -37
- package/src/lib/view/port-allocation.ts +1 -1
- package/src/lib/view/resolve.ts +52 -45
- package/src/lib/view/server.ts +16 -33
- package/src/lib/view/session.ts +51 -22
- package/src/paths/.spec/api.d.ts +13 -0
- package/src/paths/.spec/architecture.md +5 -0
- package/src/paths/.spec/layout.ts +3 -0
- package/src/paths/index.ts +13 -0
- package/src/program/.spec/api.d.ts +42 -0
- package/src/program/.spec/architecture.md +18 -0
- package/src/program/.spec/laws/program.ts +29 -0
- package/src/program/.spec/layout.ts +14 -0
- package/src/program/__tests__/program.test.ts +359 -0
- package/src/program/build.ts +190 -0
- package/src/program/command.ts +42 -0
- package/src/program/index.ts +2 -0
- package/src/program/options.ts +40 -0
- package/src/{registry.ts → program/registry.ts} +5 -1
- package/src/program/tsconfig.json +6 -0
- package/src/setup/steps/admin.ts +18 -3
- package/src/setup/steps/instance.ts +1 -1
- package/src/state/.spec/api.d.ts +133 -0
- package/src/state/.spec/architecture.md +31 -0
- package/src/state/.spec/capabilities/state.ts +7 -0
- package/src/state/.spec/flows/identity-update.ts +22 -0
- package/src/state/.spec/laws/state.ts +73 -0
- package/src/state/.spec/layout.ts +14 -0
- package/src/state/__tests__/exchange-credentials.test.ts +188 -0
- package/src/state/__tests__/files.test.ts +113 -0
- package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
- package/src/state/__tests__/identities.test.ts +142 -0
- package/src/state/__tests__/paths.test.ts +27 -0
- package/src/state/exchange-credentials.ts +219 -0
- package/src/state/files.ts +129 -0
- package/src/state/identities.ts +236 -0
- package/src/state/index.ts +29 -0
- package/src/state/paths.ts +61 -0
- package/src/state/tsconfig.json +6 -0
- package/src/telemetry/__tests__/redact.test.ts +3 -3
- package/src/telemetry/__tests__/trigger.test.ts +1 -1
- package/src/telemetry/settings.ts +1 -1
- package/src/telemetry/store.ts +1 -1
- package/src/test-utils.ts +1 -1
- package/studio/client/dist/assets/{elk-api-DVw5WDTH.js → elk-api-D0cBetPW.js} +1 -1
- package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
- package/studio/client/dist/assets/{index-Dl709Ra1.js → index-bVD2KJgz.js} +4 -4
- package/studio/client/dist/index.html +1 -1
- package/studio/package.json +10 -1
- package/studio/server/introspect/extractor.ts +1 -1
- package/studio/server/state/views.test.ts +3 -7
- package/studio/server/state/views.ts +23 -30
- package/tsconfig.json +1 -1
- package/viewer/dist/main.js +65 -39
- package/src/command.ts +0 -42
- package/src/commands/__tests__/help-contract.test.ts +0 -164
- package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
- package/src/kernel/__tests__/errors.test.ts +0 -43
- package/src/kernel/__tests__/expand.test.ts +0 -123
- package/src/kernel/client.ts +0 -214
- package/src/kernel/expand.ts +0 -192
- package/src/kernel/graph.ts +0 -96
- package/src/kernel/index.ts +0 -29
- package/src/kernel/options.ts +0 -22
- package/src/kernel/types.ts +0 -14
- package/src/lib/__tests__/domain-identity.test.ts +0 -60
- package/src/lib/__tests__/fs-atomic.test.ts +0 -104
- package/src/lib/__tests__/identity.test.ts +0 -79
- package/src/lib/__tests__/keys.test.ts +0 -129
- package/src/lib/domain-identity.ts +0 -49
- package/src/lib/env.ts +0 -49
- package/src/lib/fs-atomic.ts +0 -126
- package/src/lib/identity.ts +0 -256
- package/src/lib/keys.ts +0 -294
- package/src/lib/paths.ts +0 -11
- package/src/program.ts +0 -213
- package/studio/client/dist/assets/index-CTbkDp3z.js +0 -81
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { AuthApi } from '@astrale-os/kernel-client/auth'
|
|
2
|
+
import type { GraphApi } from '@astrale-os/kernel-client/graph'
|
|
3
|
+
import type { ClientSessionOptions, SessionAuth } from '@astrale-os/kernel-client/session'
|
|
4
|
+
|
|
5
|
+
import { call } from '@astrale-os/kernel-client'
|
|
6
|
+
import { createAuth } from '@astrale-os/kernel-client/auth'
|
|
7
|
+
import { createGraph } from '@astrale-os/kernel-client/graph'
|
|
8
|
+
import { ClientSession } from '@astrale-os/kernel-client/session'
|
|
9
|
+
|
|
10
|
+
import type { AstraleConfig } from '../lib/config'
|
|
11
|
+
import type { AdminConnectionOptions, ConnectionOptions, ConnectionTarget } from './target'
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
AdminInstanceNotFoundError,
|
|
15
|
+
connectAdminInstances,
|
|
16
|
+
findOwnedInstance,
|
|
17
|
+
type InstanceInfo,
|
|
18
|
+
} from '../admin/instance'
|
|
19
|
+
import { AstraleError } from '../errors'
|
|
20
|
+
import { fetchWithCaFile } from '../lib/ca-fetch'
|
|
21
|
+
import { readConfig } from '../lib/config'
|
|
22
|
+
import { createCliCredential, validateCredentialSelection } from './credential'
|
|
23
|
+
import { resolveAdminConnectionTarget, resolveConnectionTarget } from './target'
|
|
24
|
+
|
|
25
|
+
const DEFAULT_TIMEOUT_MS = 30_000
|
|
26
|
+
const MAXIMUM_ROUTE_AGE_MS = 5 * 60_000
|
|
27
|
+
|
|
28
|
+
export interface ConnectionContext {
|
|
29
|
+
readonly session: ClientSession
|
|
30
|
+
readonly graph: GraphApi
|
|
31
|
+
readonly auth: AuthApi
|
|
32
|
+
readonly target: ConnectionTarget
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface OwnedConnection {
|
|
36
|
+
readonly context: ConnectionContext
|
|
37
|
+
close(): void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type ConnectionFactory = (
|
|
41
|
+
target: ConnectionTarget,
|
|
42
|
+
timeoutMs: number,
|
|
43
|
+
options: ConnectionOptions,
|
|
44
|
+
config: AstraleConfig,
|
|
45
|
+
) => OwnedConnection
|
|
46
|
+
|
|
47
|
+
/** Resolve one ordinary target, run a command action, then close terminally. */
|
|
48
|
+
export async function withClientSession<Value>(
|
|
49
|
+
options: ConnectionOptions,
|
|
50
|
+
action: (context: ConnectionContext) => Promise<Value>,
|
|
51
|
+
): Promise<Value> {
|
|
52
|
+
validateCredentialSelection(options)
|
|
53
|
+
const timeoutMs = resolveTimeoutMs(options.timeout)
|
|
54
|
+
const config = await readConfig()
|
|
55
|
+
const target = await resolveConnectionTarget(options, config, {
|
|
56
|
+
managed: (slug) => lookupManagedInstance(slug, options),
|
|
57
|
+
})
|
|
58
|
+
return runResolvedClientSession(target, timeoutMs, options, config, action, openConnection)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Resolve the configured Admin Domain target under the same terminal lifecycle. */
|
|
62
|
+
export async function withAdminClientSession<Value>(
|
|
63
|
+
options: AdminConnectionOptions,
|
|
64
|
+
action: (context: ConnectionContext) => Promise<Value>,
|
|
65
|
+
): Promise<Value> {
|
|
66
|
+
validateCredentialSelection(options)
|
|
67
|
+
const timeoutMs = resolveTimeoutMs(options.timeout)
|
|
68
|
+
const config = await readConfig()
|
|
69
|
+
const target = await resolveAdminConnectionTarget(options, config)
|
|
70
|
+
return runResolvedClientSession(target, timeoutMs, options, config, action, openConnection)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Owner-private seam used to prove validation order and cleanup without network I/O. */
|
|
74
|
+
export async function withResolvedClientSession<Value>(
|
|
75
|
+
target: ConnectionTarget,
|
|
76
|
+
options: ConnectionOptions,
|
|
77
|
+
config: AstraleConfig,
|
|
78
|
+
action: (context: ConnectionContext) => Promise<Value>,
|
|
79
|
+
open: ConnectionFactory = openConnection,
|
|
80
|
+
): Promise<Value> {
|
|
81
|
+
validateCredentialSelection(options)
|
|
82
|
+
const timeoutMs = resolveTimeoutMs(options.timeout)
|
|
83
|
+
return runResolvedClientSession(target, timeoutMs, options, config, action, open)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function runResolvedClientSession<Value>(
|
|
87
|
+
target: ConnectionTarget,
|
|
88
|
+
timeoutMs: number,
|
|
89
|
+
options: ConnectionOptions,
|
|
90
|
+
config: AstraleConfig,
|
|
91
|
+
action: (context: ConnectionContext) => Promise<Value>,
|
|
92
|
+
open: ConnectionFactory,
|
|
93
|
+
): Promise<Value> {
|
|
94
|
+
const connection = open(target, timeoutMs, options, config)
|
|
95
|
+
try {
|
|
96
|
+
return await action(connection.context)
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof Error) (error as Error & { url?: string }).url = target.url
|
|
99
|
+
throw error
|
|
100
|
+
} finally {
|
|
101
|
+
connection.close()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function resolveTimeoutMs(raw: string | undefined): number {
|
|
106
|
+
if (raw === undefined) return DEFAULT_TIMEOUT_MS
|
|
107
|
+
if (!/^\d+$/.test(raw)) {
|
|
108
|
+
throw new AstraleError(
|
|
109
|
+
'INVALID_FLAG',
|
|
110
|
+
`Invalid --timeout value "${raw}" — expected a positive integer (milliseconds)`,
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
const value = Number.parseInt(raw, 10)
|
|
114
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
115
|
+
throw new AstraleError(
|
|
116
|
+
'INVALID_FLAG',
|
|
117
|
+
`Invalid --timeout value "${raw}" — must be a positive integer`,
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
return value
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function openConnection(
|
|
124
|
+
target: ConnectionTarget,
|
|
125
|
+
timeoutMs: number,
|
|
126
|
+
options: ConnectionOptions,
|
|
127
|
+
config: AstraleConfig,
|
|
128
|
+
): OwnedConnection {
|
|
129
|
+
const fetch = target.caFile === undefined ? globalThis.fetch : fetchWithCaFile(target.caFile)
|
|
130
|
+
const auth = createCliCredential(target, options, config, fetch, timeoutMs)
|
|
131
|
+
const session = new ClientSession(createClientSessionOptions(target, fetch, auth, timeoutMs))
|
|
132
|
+
const graph = createGraph((call, request) => session.call(call, request))
|
|
133
|
+
const authApi = createAuth((path, input, request) => session.call(call(path, input), request))
|
|
134
|
+
return {
|
|
135
|
+
context: Object.freeze({ session, graph, auth: authApi, target }),
|
|
136
|
+
close() {
|
|
137
|
+
session.close()
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Owner-private construction seam proving that transport and source identity stay distinct. */
|
|
143
|
+
export function createClientSessionOptions(
|
|
144
|
+
target: ConnectionTarget,
|
|
145
|
+
fetch: NonNullable<ClientSessionOptions['fetch']>,
|
|
146
|
+
auth: SessionAuth | undefined,
|
|
147
|
+
timeoutMs: number,
|
|
148
|
+
): ClientSessionOptions {
|
|
149
|
+
return {
|
|
150
|
+
kernel: target.kernelIssuer,
|
|
151
|
+
fetch,
|
|
152
|
+
...(auth === undefined ? {} : { auth }),
|
|
153
|
+
policy: {
|
|
154
|
+
maximumRouteAgeMs: MAXIMUM_ROUTE_AGE_MS,
|
|
155
|
+
...(new URL(target.url).protocol === 'http:' ? { allowInsecureHttp: true } : {}),
|
|
156
|
+
},
|
|
157
|
+
envelopeTransport: 'http',
|
|
158
|
+
timeoutMs,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function lookupManagedInstance(
|
|
163
|
+
slug: string,
|
|
164
|
+
options: ConnectionOptions,
|
|
165
|
+
openAdmin: typeof withAdminClientSession = withAdminClientSession,
|
|
166
|
+
connect: typeof connectAdminInstances = connectAdminInstances,
|
|
167
|
+
): Promise<InstanceInfo> {
|
|
168
|
+
return openAdmin(
|
|
169
|
+
Object.freeze(options.timeout === undefined ? {} : { timeout: options.timeout }),
|
|
170
|
+
async (context) => {
|
|
171
|
+
const found = findOwnedInstance(await (await connect(context)).list(), slug)
|
|
172
|
+
if (found === undefined) throw new AdminInstanceNotFoundError(slug)
|
|
173
|
+
return found
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { IssuerId } from '@astrale-os/sdk/auth'
|
|
2
|
+
|
|
3
|
+
import { issuer } from '@astrale-os/sdk/auth'
|
|
4
|
+
|
|
5
|
+
import type { InstanceInfo } from '../lib/admin-instance'
|
|
6
|
+
import type { AdminTargetCommandOpts } from '../lib/admin-target'
|
|
7
|
+
import type { AstraleConfig } from '../lib/config'
|
|
8
|
+
import type { InstanceStore } from '../lib/instance'
|
|
9
|
+
import type { ResolvedInstanceTarget } from '../lib/instance-target'
|
|
10
|
+
|
|
11
|
+
import { resolveInstanceTarget } from '../lib/instance-target'
|
|
12
|
+
|
|
13
|
+
export interface ConnectionOptions {
|
|
14
|
+
readonly url?: string
|
|
15
|
+
readonly instance?: string
|
|
16
|
+
readonly timeout?: string
|
|
17
|
+
readonly as?: string
|
|
18
|
+
readonly creds?: string
|
|
19
|
+
readonly anonymous?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AdminConnectionOptions extends ConnectionOptions {
|
|
23
|
+
readonly admin?: string
|
|
24
|
+
readonly adminUrl?: string
|
|
25
|
+
readonly domainIssuer?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ConnectionTarget {
|
|
29
|
+
readonly url: string
|
|
30
|
+
readonly kernelIssuer: IssuerId
|
|
31
|
+
readonly domainIssuer?: IssuerId
|
|
32
|
+
readonly slug?: string
|
|
33
|
+
readonly defaultIdentity?: string
|
|
34
|
+
readonly caFile?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TargetDependencies {
|
|
38
|
+
readonly instances?: InstanceStore
|
|
39
|
+
readonly managed?: (slug: string) => Promise<InstanceInfo>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Stable local identity-registration key for the exact selected source Kernel. */
|
|
43
|
+
export function registrationKeyForTarget(target: ConnectionTarget): string {
|
|
44
|
+
return target.slug ?? target.url
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Resolve the existing URL / instance / active precedence into one exact source Kernel. */
|
|
48
|
+
export async function resolveConnectionTarget(
|
|
49
|
+
options: ConnectionOptions,
|
|
50
|
+
config: AstraleConfig,
|
|
51
|
+
dependencies: TargetDependencies = {},
|
|
52
|
+
): Promise<ConnectionTarget> {
|
|
53
|
+
const request =
|
|
54
|
+
options.url !== undefined && options.instance === undefined
|
|
55
|
+
? ({ source: 'url', url: options.url } as const)
|
|
56
|
+
: options.instance !== undefined
|
|
57
|
+
? ({ source: 'name', name: options.instance } as const)
|
|
58
|
+
: ({ source: 'active' } as const)
|
|
59
|
+
const resolved = await resolveInstanceTarget(request, {
|
|
60
|
+
config,
|
|
61
|
+
...(dependencies.instances === undefined ? {} : { instances: dependencies.instances }),
|
|
62
|
+
admin: {},
|
|
63
|
+
...(dependencies.managed === undefined ? {} : { managed: dependencies.managed }),
|
|
64
|
+
})
|
|
65
|
+
return connectionTarget(resolved, options.url)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Resolve the configured Admin Domain target without entering managed-instance discovery. */
|
|
69
|
+
export async function resolveAdminConnectionTarget(
|
|
70
|
+
options: AdminConnectionOptions,
|
|
71
|
+
config: AstraleConfig,
|
|
72
|
+
instances?: InstanceStore,
|
|
73
|
+
): Promise<ConnectionTarget> {
|
|
74
|
+
const resolved = await resolveInstanceTarget(
|
|
75
|
+
{ source: 'admin' },
|
|
76
|
+
{
|
|
77
|
+
config,
|
|
78
|
+
admin: adminLookupOptions(options),
|
|
79
|
+
...(instances === undefined ? {} : { instances }),
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
return connectionTarget(resolved)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function adminLookupOptions(options: AdminConnectionOptions): AdminTargetCommandOpts {
|
|
86
|
+
return {
|
|
87
|
+
...(options.admin === undefined ? {} : { admin: options.admin }),
|
|
88
|
+
...(options.adminUrl === undefined ? {} : { adminUrl: options.adminUrl }),
|
|
89
|
+
...(options.domainIssuer === undefined ? {} : { domainIssuer: options.domainIssuer }),
|
|
90
|
+
...(options.instance === undefined ? {} : { instance: options.instance }),
|
|
91
|
+
...(options.url === undefined ? {} : { url: options.url }),
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function connectionTarget(
|
|
96
|
+
resolved: ResolvedInstanceTarget,
|
|
97
|
+
urlOverride?: string,
|
|
98
|
+
): ConnectionTarget {
|
|
99
|
+
return Object.freeze({
|
|
100
|
+
url: urlOverride ?? resolved.url,
|
|
101
|
+
kernelIssuer: issuer.accept(resolved.kernelIssuer),
|
|
102
|
+
...(resolved.domainIssuer === undefined
|
|
103
|
+
? {}
|
|
104
|
+
: { domainIssuer: issuer.accept(resolved.domainIssuer) }),
|
|
105
|
+
...(resolved.name === undefined ? {} : { slug: resolved.name }),
|
|
106
|
+
...(resolved.defaultIdentity === undefined
|
|
107
|
+
? {}
|
|
108
|
+
: { defaultIdentity: resolved.defaultIdentity }),
|
|
109
|
+
...(resolved.caFile === undefined ? {} : { caFile: resolved.caFile }),
|
|
110
|
+
})
|
|
111
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MutationAST } from '@astrale-os/sdk/mutation'
|
|
2
|
+
import type { QueryAST, QueryDirection } from '@astrale-os/sdk/query'
|
|
3
|
+
|
|
4
|
+
/** Untrusted CLI fields used to author or admit one exact Query V5 request. */
|
|
5
|
+
export interface QueryCommandInput {
|
|
6
|
+
readonly sources: readonly string[]
|
|
7
|
+
readonly definition?: string
|
|
8
|
+
readonly ast?: unknown
|
|
9
|
+
readonly edge?: string
|
|
10
|
+
readonly direction?: QueryDirection
|
|
11
|
+
readonly limit?: string
|
|
12
|
+
readonly cursor?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Canonical query plus the caller-bound continuation token kept outside the AST. */
|
|
16
|
+
export interface PreparedQuery {
|
|
17
|
+
readonly ast: QueryAST
|
|
18
|
+
readonly cursor?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Admit canonical Query V5 or author its supported Path/Definition/one-edge CLI subset. */
|
|
22
|
+
export function prepareQuery(input: QueryCommandInput): PreparedQuery
|
|
23
|
+
|
|
24
|
+
/** Admit canonical Mutation V2 or its exact authoring input; legacy PatchData is rejected. */
|
|
25
|
+
export function prepareMutation(input: unknown): MutationAST
|
|
26
|
+
|
|
27
|
+
/** Return the user-facing leaf of one exact qualified Property key. */
|
|
28
|
+
export function unqualifyProperty(key: string): string
|
|
29
|
+
|
|
30
|
+
/** Read one property by exact key first, then by its qualified leaf name. */
|
|
31
|
+
export function nodeProperty(
|
|
32
|
+
node: { readonly props: Readonly<Record<string, unknown>> } | null | undefined,
|
|
33
|
+
name: string,
|
|
34
|
+
): unknown
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineLaw } from '@astrale-os/spec/authoring'
|
|
2
|
+
|
|
3
|
+
export const CLI_GRAPH_QUERY_V5 = defineLaw({
|
|
4
|
+
id: 'CLI-GRAPH-QUERY-V5',
|
|
5
|
+
statement:
|
|
6
|
+
'CLI query input becomes exactly one canonical Query V5 document with an explicit finite limit; exact Property ordering and Node or Edge projection profiles are admitted through the canonical AST surface, while legacy versions and unsupported selector combinations fail before a Graph call.',
|
|
7
|
+
tests: [
|
|
8
|
+
{ file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-AUTHORS-QUERY-V5' },
|
|
9
|
+
{ file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-AUTHORS-DEFINITION-QUERY' },
|
|
10
|
+
{ file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-ADMITS-QUERY-V5-ORDERING' },
|
|
11
|
+
{ file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-ADMITS-QUERY-V5-PROJECTIONS' },
|
|
12
|
+
{ file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-REJECTS-LEGACY-QUERY' },
|
|
13
|
+
],
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const CLI_GRAPH_MUTATION_V2 = defineLaw({
|
|
17
|
+
id: 'CLI-GRAPH-MUTATION-V2',
|
|
18
|
+
statement:
|
|
19
|
+
'CLI mutation input is admitted as canonical Mutation V2 or its exact authoring input; PatchData and other historical shapes never reach GraphApi.mutate.',
|
|
20
|
+
tests: [
|
|
21
|
+
{ file: '__tests__/mutation.test.ts', id: 'TEST-CLI-GRAPH-ADMITS-MUTATION-V2' },
|
|
22
|
+
{ file: '__tests__/mutation.test.ts', id: 'TEST-CLI-GRAPH-REJECTS-PATCH-DATA' },
|
|
23
|
+
],
|
|
24
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineLaw } from '@astrale-os/spec/authoring'
|
|
2
|
+
|
|
3
|
+
export const CLI_GRAPH_PROPERTY_PROJECTION = defineLaw({
|
|
4
|
+
id: 'CLI-GRAPH-PROPERTY-PROJECTION',
|
|
5
|
+
statement:
|
|
6
|
+
'An exact property key wins over a qualified leaf-name match; otherwise the first canonical property with that leaf is presented without rewriting the Node.',
|
|
7
|
+
tests: [
|
|
8
|
+
{
|
|
9
|
+
file: '__tests__/projection.test.ts',
|
|
10
|
+
id: 'TEST-CLI-GRAPH-PROJECTS-QUALIFIED-PROPERTIES',
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { prepareMutation } from '../mutation'
|
|
4
|
+
|
|
5
|
+
describe('prepareMutation', () => {
|
|
6
|
+
/** @evidence TEST-CLI-GRAPH-ADMITS-MUTATION-V3 */
|
|
7
|
+
test('creates one canonical Mutation V3 document from exact authoring input', () => {
|
|
8
|
+
expect(
|
|
9
|
+
JSON.parse(
|
|
10
|
+
JSON.stringify(
|
|
11
|
+
prepareMutation({
|
|
12
|
+
preconditions: [],
|
|
13
|
+
operations: [
|
|
14
|
+
{
|
|
15
|
+
op: 'node.create',
|
|
16
|
+
as: 'note',
|
|
17
|
+
class: '/:notes.example.dev:class.Note',
|
|
18
|
+
props: {},
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}),
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
).toEqual({
|
|
25
|
+
format: 'astrale.graph.mutation',
|
|
26
|
+
version: 'v3',
|
|
27
|
+
preconditions: [],
|
|
28
|
+
operations: [
|
|
29
|
+
{
|
|
30
|
+
op: 'node.create',
|
|
31
|
+
as: 'note',
|
|
32
|
+
class: '/:notes.example.dev:class.Note',
|
|
33
|
+
props: {},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/** @evidence TEST-CLI-GRAPH-REJECTS-PATCH-DATA */
|
|
40
|
+
test('rejects legacy PatchData instead of narrowing it', () => {
|
|
41
|
+
expect(() => prepareMutation({ nodes: { create: [] }, edges: { create: [] } })).toThrow()
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { nodeProperty, unqualifyProperty } from '../projection'
|
|
4
|
+
|
|
5
|
+
describe('graph presentation properties', () => {
|
|
6
|
+
/** @evidence TEST-CLI-GRAPH-PROJECTS-QUALIFIED-PROPERTIES */
|
|
7
|
+
test('prefers exact keys and otherwise projects a qualified leaf', () => {
|
|
8
|
+
const node = {
|
|
9
|
+
props: {
|
|
10
|
+
name: 'exact',
|
|
11
|
+
'/:example.test:class.Widget.property.name': 'qualified',
|
|
12
|
+
'/:example.test:class.Widget.property.description': 'shown',
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
expect(nodeProperty(node, 'name')).toBe('exact')
|
|
17
|
+
expect(nodeProperty(node, 'description')).toBe('shown')
|
|
18
|
+
expect(unqualifyProperty('/:example.test:class.Widget.property.description')).toBe(
|
|
19
|
+
'description',
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { prepareQuery } from '../query'
|
|
4
|
+
|
|
5
|
+
describe('prepareQuery', () => {
|
|
6
|
+
/** @evidence TEST-CLI-GRAPH-AUTHORS-QUERY-V6 */
|
|
7
|
+
test('authors the supported Path and exact-edge subset as canonical Query V6', () => {
|
|
8
|
+
const prepared = prepareQuery({
|
|
9
|
+
sources: ['/:notes.example.dev:class.Note'],
|
|
10
|
+
edge: '/:notes.example.dev:class.references',
|
|
11
|
+
direction: 'incoming',
|
|
12
|
+
limit: '25',
|
|
13
|
+
cursor: 'next-page',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect(JSON.parse(JSON.stringify(prepared))).toEqual({
|
|
17
|
+
ast: {
|
|
18
|
+
format: 'astrale.graph.query',
|
|
19
|
+
version: 'v6',
|
|
20
|
+
source: {
|
|
21
|
+
kind: 'node',
|
|
22
|
+
terms: [{ kind: 'path', path: '/:notes.example.dev:class.Note' }],
|
|
23
|
+
binding: 'n0',
|
|
24
|
+
},
|
|
25
|
+
steps: [
|
|
26
|
+
{
|
|
27
|
+
op: 'expand',
|
|
28
|
+
from: 'n0',
|
|
29
|
+
via: [{ origin: 'notes.example.dev', kind: 'class', name: 'references' }],
|
|
30
|
+
direction: 'incoming',
|
|
31
|
+
bindings: { edge: 'e0', node: 'n1' },
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
select: { kind: 'graph', binding: 'n1' },
|
|
35
|
+
},
|
|
36
|
+
page: { size: 25, after: 'next-page' },
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
/** @evidence TEST-CLI-GRAPH-AUTHORS-DEFINITION-QUERY */
|
|
41
|
+
test('authors an exact Definition source without backend query text', () => {
|
|
42
|
+
const prepared = prepareQuery({
|
|
43
|
+
sources: [],
|
|
44
|
+
definition: '/:issues.astrale.ai:class.Issue',
|
|
45
|
+
limit: '201',
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
expect(JSON.parse(JSON.stringify(prepared.ast))).toEqual({
|
|
49
|
+
format: 'astrale.graph.query',
|
|
50
|
+
version: 'v6',
|
|
51
|
+
source: {
|
|
52
|
+
kind: 'node',
|
|
53
|
+
terms: [
|
|
54
|
+
{
|
|
55
|
+
kind: 'definition',
|
|
56
|
+
definition: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
binding: 'n0',
|
|
60
|
+
},
|
|
61
|
+
steps: [],
|
|
62
|
+
select: { kind: 'graph', binding: 'n0' },
|
|
63
|
+
})
|
|
64
|
+
expect(prepared.page).toEqual({ size: 201 })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
/** @evidence TEST-CLI-GRAPH-ADMITS-QUERY-V6-ORDERING */
|
|
68
|
+
test('admits one exact Property-ordered Query V6 document', () => {
|
|
69
|
+
const ast = {
|
|
70
|
+
format: 'astrale.graph.query',
|
|
71
|
+
version: 'v6',
|
|
72
|
+
source: {
|
|
73
|
+
kind: 'node',
|
|
74
|
+
terms: [
|
|
75
|
+
{
|
|
76
|
+
kind: 'definition',
|
|
77
|
+
definition: { origin: 'notes.example.dev', kind: 'class', name: 'Note' },
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
binding: 'n0',
|
|
81
|
+
},
|
|
82
|
+
steps: [],
|
|
83
|
+
select: {
|
|
84
|
+
kind: 'graph',
|
|
85
|
+
binding: 'n0',
|
|
86
|
+
order: {
|
|
87
|
+
property: 'notes.example.dev:class.Note.property.sequence',
|
|
88
|
+
direction: 'desc',
|
|
89
|
+
unranked: 'last',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const prepared = prepareQuery({ sources: [], ast, limit: '3' })
|
|
95
|
+
expect(JSON.parse(JSON.stringify(prepared.ast))).toEqual(ast)
|
|
96
|
+
expect(prepared.page).toEqual({ size: 3 })
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
/** @evidence TEST-CLI-GRAPH-ADMITS-QUERY-V6-PROJECTIONS */
|
|
100
|
+
test('admits exact Node and Edge projection profiles in Query V6 documents', () => {
|
|
101
|
+
const source = {
|
|
102
|
+
terms: [{ kind: 'path', path: '/:notes.example.dev:class.Note' }],
|
|
103
|
+
binding: 'n0',
|
|
104
|
+
kind: 'node',
|
|
105
|
+
} as const
|
|
106
|
+
const node = {
|
|
107
|
+
format: 'astrale.graph.query',
|
|
108
|
+
version: 'v6',
|
|
109
|
+
source,
|
|
110
|
+
steps: [],
|
|
111
|
+
select: { kind: 'nodes', binding: 'n0', projection: { kind: 'reference' } },
|
|
112
|
+
}
|
|
113
|
+
const edge = {
|
|
114
|
+
format: 'astrale.graph.query',
|
|
115
|
+
version: 'v6',
|
|
116
|
+
source,
|
|
117
|
+
steps: [
|
|
118
|
+
{
|
|
119
|
+
op: 'expand',
|
|
120
|
+
from: 'n0',
|
|
121
|
+
via: [{ origin: 'notes.example.dev', kind: 'class', name: 'references' }],
|
|
122
|
+
direction: 'outgoing',
|
|
123
|
+
bindings: { edge: 'e0', node: 'n1' },
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
select: {
|
|
127
|
+
kind: 'edges',
|
|
128
|
+
binding: 'e0',
|
|
129
|
+
projection: { edge: 'value', source: 'reference', target: 'value' },
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
expect(
|
|
134
|
+
JSON.parse(JSON.stringify(prepareQuery({ sources: [], ast: node, limit: '5' }).ast)),
|
|
135
|
+
).toEqual(node)
|
|
136
|
+
expect(
|
|
137
|
+
JSON.parse(JSON.stringify(prepareQuery({ sources: [], ast: edge, limit: '5' }).ast)),
|
|
138
|
+
).toEqual(edge)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
/** @evidence TEST-CLI-GRAPH-REJECTS-LEGACY-QUERY */
|
|
142
|
+
test('rejects V1 queries and unsupported combinations locally', () => {
|
|
143
|
+
expect(() => prepareQuery({ sources: [], ast: { version: 1, from: ['/'] } })).toThrow()
|
|
144
|
+
expect(() =>
|
|
145
|
+
prepareQuery({ sources: ['/:notes.example.dev:class.Note'], direction: 'incident' }),
|
|
146
|
+
).toThrow('--direction requires --edge')
|
|
147
|
+
expect(() =>
|
|
148
|
+
prepareQuery({ sources: ['/:notes.example.dev:class.Note'], limit: 'all' }),
|
|
149
|
+
).toThrow('--limit must be a positive integer')
|
|
150
|
+
expect(() =>
|
|
151
|
+
prepareQuery({ sources: [], definition: '/:notes.example.dev:view.note' }),
|
|
152
|
+
).toThrow('--definition must be one canonical Class or Interface Path')
|
|
153
|
+
})
|
|
154
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MutationAST as MutationASTValue, MutationInput } from '@astrale-os/sdk/mutation'
|
|
2
|
+
|
|
3
|
+
import { MutationAST } from '@astrale-os/sdk/mutation'
|
|
4
|
+
|
|
5
|
+
/** Admit the canonical document or Core's exact rich authoring input at the CLI JSON boundary. */
|
|
6
|
+
export function prepareMutation(input: unknown): MutationASTValue {
|
|
7
|
+
if (isCanonicalCandidate(input)) return MutationAST.decode(input)
|
|
8
|
+
return MutationAST.create(input as MutationInput)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isCanonicalCandidate(input: unknown): boolean {
|
|
12
|
+
return (
|
|
13
|
+
input !== null &&
|
|
14
|
+
typeof input === 'object' &&
|
|
15
|
+
!Array.isArray(input) &&
|
|
16
|
+
(Object.hasOwn(input, 'format') || Object.hasOwn(input, 'version'))
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Strip a fully-qualified Property key to the leaf used by CLI presentation. */
|
|
2
|
+
export function unqualifyProperty(key: string): string {
|
|
3
|
+
const property = key.lastIndexOf('.property.')
|
|
4
|
+
if (property >= 0) return key.slice(property + '.property.'.length)
|
|
5
|
+
const slash = key.lastIndexOf('/')
|
|
6
|
+
return slash >= 0 ? key.slice(slash + 1) : key
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Read an exact key first, then one canonical qualified key with the requested leaf. */
|
|
10
|
+
export function nodeProperty(
|
|
11
|
+
node: { readonly props: Readonly<Record<string, unknown>> } | null | undefined,
|
|
12
|
+
name: string,
|
|
13
|
+
): unknown {
|
|
14
|
+
const props = node?.props
|
|
15
|
+
if (props === undefined) return undefined
|
|
16
|
+
if (Object.hasOwn(props, name)) return props[name]
|
|
17
|
+
for (const [key, value] of Object.entries(props)) {
|
|
18
|
+
if (unqualifyProperty(key) === name) return value
|
|
19
|
+
}
|
|
20
|
+
return undefined
|
|
21
|
+
}
|