@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.11
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/README.md +23 -12
- package/dist/astrale.js +48048 -56798
- package/dist/public/connect-core.js +2011 -30765
- package/dist/public/keys/index.js +884 -29483
- package/dist/public/paths/index.js +13101 -41459
- package/dist/types/admin/binding.d.ts +19 -0
- package/dist/types/admin/instance/client.d.ts +22 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +5 -12
- package/dist/types/connection/auth.d.ts +3 -0
- package/dist/types/connection/call.d.ts +3 -0
- package/dist/types/connection/command.d.ts +27 -0
- package/dist/types/connection/credential.d.ts +14 -0
- package/dist/types/connection/errors.d.ts +1 -0
- package/dist/types/connection/exchange.d.ts +9 -0
- package/dist/types/connection/failure/classify.d.ts +2 -0
- package/dist/types/connection/failure/index.d.ts +5 -0
- package/dist/types/connection/failure/model.d.ts +25 -0
- package/dist/types/connection/failure/render.d.ts +3 -0
- package/dist/types/connection/index.d.ts +8 -0
- package/dist/types/connection/reasons.d.ts +37 -0
- package/dist/types/connection/self.d.ts +37 -0
- package/dist/types/connection/session.d.ts +31 -0
- package/dist/types/connection/target.d.ts +38 -0
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/admin-instance.d.ts +17 -0
- package/dist/types/lib/admin-target.d.ts +11 -36
- package/dist/types/lib/config.d.ts +7 -59
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +26 -168
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +30 -88
- package/dist/types/lib/log.d.ts +5 -3
- package/dist/types/lib/meta.d.ts +2 -2
- package/dist/types/lib/proc.d.ts +43 -0
- package/dist/types/lib/self.d.ts +9 -0
- package/dist/types/lib/update.d.ts +91 -0
- package/dist/types/lib/validation.d.ts +4 -1
- package/package.json +16 -19
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/__tests__/binding.test.ts +31 -0
- package/src/admin/__tests__/fixture.ts +101 -0
- package/src/admin/binding.ts +98 -0
- package/src/admin/catalog/.spec/api.d.ts +2 -2
- package/src/admin/catalog/__tests__/client.test.ts +20 -49
- package/src/admin/catalog/client.ts +36 -32
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/graph/.spec/api.d.ts +4 -10
- package/src/admin/graph/nodes.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +2 -2
- package/src/admin/instance/__tests__/client.test.ts +39 -164
- package/src/admin/instance/client.ts +35 -124
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +11 -20
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/auth-login.test.ts +54 -1
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +5 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
- package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
- package/src/commands/__tests__/domain-list.test.ts +63 -4
- package/src/commands/__tests__/install-identity-override.test.ts +19 -19
- package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
- package/src/commands/__tests__/instance-status.test.ts +139 -0
- package/src/commands/__tests__/read-commands.test.ts +35 -10
- package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
- package/src/commands/__tests__/update.test.ts +48 -19
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +43 -56
- package/src/commands/domain/install.ts +13 -17
- package/src/commands/domain/list.ts +18 -13
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +14 -15
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +5 -4
- package/src/commands/instance/create.ts +8 -16
- package/src/commands/instance/delete.ts +12 -9
- package/src/commands/instance/list.ts +2 -2
- package/src/commands/instance/status.ts +66 -20
- package/src/commands/instance/use.ts +16 -12
- package/src/commands/introspect.ts +9 -16
- package/src/commands/logs.ts +24 -25
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +27 -30
- package/src/commands/studio.ts +20 -0
- package/src/commands/token.ts +8 -7
- package/src/commands/update.ts +38 -14
- package/src/commands/view.ts +7 -6
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +5 -4
- package/src/connection/.spec/flows/session.ts +2 -2
- package/src/connection/.spec/laws/connection.ts +6 -2
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +20 -1
- package/src/connection/__tests__/errors.test.ts +43 -32
- package/src/connection/__tests__/exchange.test.ts +36 -6
- package/src/connection/__tests__/failure-fixtures.ts +39 -0
- package/src/connection/__tests__/failure-safety.test.ts +75 -0
- package/src/connection/__tests__/self.test.ts +31 -11
- package/src/connection/__tests__/session.test.ts +3 -3
- package/src/connection/__tests__/target.test.ts +1 -0
- package/src/connection/auth.ts +13 -0
- package/src/connection/call.ts +1 -1
- package/src/connection/credential.ts +2 -2
- package/src/connection/errors.ts +1 -355
- package/src/connection/exchange.ts +52 -27
- package/src/connection/failure/classify.ts +110 -0
- package/src/connection/failure/index.ts +18 -0
- package/src/connection/failure/model.ts +28 -0
- package/src/connection/failure/render.ts +116 -0
- package/src/connection/reasons.ts +16 -0
- package/src/connection/self.ts +39 -21
- package/src/connection/session.ts +19 -8
- package/src/errors.ts +3 -2
- package/src/graph/.spec/api.d.ts +12 -5
- package/src/graph/.spec/layout.ts +9 -1
- package/src/graph/__tests__/mutation.test.ts +2 -2
- package/src/graph/__tests__/query.test.ts +25 -6
- package/src/graph/class.ts +30 -0
- package/src/graph/index.ts +1 -0
- package/src/graph/query.ts +21 -32
- package/src/identity/.spec/api.d.ts +2 -2
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/registration.ts +3 -3
- package/src/keys/__tests__/keys.test.ts +4 -0
- package/src/keys/credential.ts +1 -0
- package/src/lib/__tests__/admin-target.test.ts +12 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/domain-publication.test.ts +138 -0
- package/src/lib/__tests__/idp-session.test.ts +22 -0
- package/src/lib/__tests__/idp.test.ts +36 -4
- package/src/lib/__tests__/instance-candidates.test.ts +1 -3
- package/src/lib/__tests__/instance-target.test.ts +7 -39
- package/src/lib/__tests__/instance.test.ts +6 -0
- package/src/lib/__tests__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
- package/src/lib/__tests__/update.test.ts +113 -8
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +18 -3
- package/src/lib/__tests__/view-session.test.ts +1 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/domain-publication.ts +104 -0
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +39 -4
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +22 -12
- package/src/lib/log.ts +16 -9
- package/src/lib/login-flow.ts +13 -3
- package/src/lib/meta.ts +2 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/update.ts +124 -30
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/resolve.ts +1 -1
- package/src/lib/view/server.ts +17 -3
- package/src/program/__tests__/program.test.ts +20 -4
- package/src/program/build.ts +2 -2
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
- package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
- package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
- package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
- package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
- package/studio/client/dist/index.html +3 -2
- package/studio/package.json +10 -14
- package/studio/server/agent/ask.test.ts +1 -1
- package/studio/server/agent/bridge/grant.test.ts +1 -1
- package/studio/server/agent/bridge/routes.test.ts +1 -1
- package/studio/server/agent/bridge/stdio.ts +8 -1
- package/studio/server/agent/conversation.test.ts +10 -5
- package/studio/server/agent/conversation.ts +56 -8
- package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/harness/codex/loadout.ts +18 -21
- package/studio/server/agent/harness/gateway/config.ts +20 -18
- package/studio/server/agent/harness/selection.ts +9 -1
- package/studio/server/agent/prompts/anchors.test.ts +32 -36
- package/studio/server/agent/prompts/anchors.ts +20 -49
- package/studio/server/agent/prompts/system.test.ts +3 -2
- package/studio/server/agent/prompts/system.ts +6 -5
- package/studio/server/agent/prompts/turn.ts +13 -4
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/completion.ts +2 -2
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/agent/run/preparation.ts +6 -5
- package/studio/server/agent/run/transcript.test.ts +35 -1
- package/studio/server/agent/run/transcript.ts +169 -3
- package/studio/server/agent/run/usage.test.ts +24 -0
- package/studio/server/agent/run/usage.ts +26 -1
- package/studio/server/api/canvas.ts +43 -0
- package/studio/server/api/comments.ts +85 -0
- package/studio/server/api/context.ts +61 -0
- package/studio/server/api/deployment.ts +39 -0
- package/studio/server/api/documents.ts +55 -0
- package/studio/server/api/domain.ts +59 -0
- package/studio/server/api/http.ts +45 -0
- package/studio/server/api/project.ts +57 -0
- package/studio/server/api/schema.ts +12 -0
- package/studio/server/api/views.ts +57 -0
- package/studio/server/api/workspace.ts +60 -0
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +147 -0
- package/studio/server/api.ts +20 -422
- package/studio/server/cache.test.ts +122 -0
- package/studio/server/cache.ts +162 -11
- package/studio/server/cli-consumers.test.ts +494 -0
- package/studio/server/cli.test.ts +88 -0
- package/studio/server/cli.ts +240 -0
- package/studio/server/client-package.ts +25 -1
- package/studio/server/domain.test.ts +50 -40
- package/studio/server/domain.ts +83 -52
- package/studio/server/environment/dotenv-preview.test.ts +39 -0
- package/studio/server/environment/dotenv-preview.ts +27 -0
- package/studio/server/environment/files.test.ts +53 -0
- package/studio/server/{state/env.ts → environment/files.ts} +7 -48
- package/studio/server/{state → handoff}/copy.ts +21 -6
- package/studio/server/handoff/service.test.ts +22 -0
- package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/active.ts +72 -0
- package/studio/server/instances/deploy-record.test.ts +31 -0
- package/studio/server/instances/deploy-record.ts +37 -0
- package/studio/server/instances/deploy.ts +56 -0
- package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
- package/studio/server/instances/probe.ts +67 -0
- package/studio/server/instances/status.test.ts +12 -0
- package/studio/server/instances/status.ts +49 -0
- package/studio/server/introspect/anatomy/client-tree.ts +57 -0
- package/studio/server/introspect/anatomy/env-fields.ts +66 -0
- package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
- package/studio/server/introspect/anatomy/source.ts +181 -0
- package/studio/server/introspect/anatomy/views/routes.ts +114 -0
- package/studio/server/introspect/anatomy/views.ts +53 -0
- package/studio/server/introspect/anatomy-extras.test.ts +106 -35
- package/studio/server/introspect/anatomy-extras.ts +7 -726
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +52 -28
- package/studio/server/introspect/bundle.ts +15 -9
- package/studio/server/introspect/canonical-schema.test.ts +93 -309
- package/studio/server/introspect/canonical-schema.ts +257 -425
- package/studio/server/introspect/config-preview.test.ts +32 -0
- package/studio/server/introspect/config-preview.ts +119 -0
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +18 -18
- package/studio/server/introspect/diff.ts +44 -90
- package/studio/server/introspect/extractor.ts +23 -116
- package/studio/server/introspect/hash.ts +5 -2
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +54 -0
- package/studio/server/introspect/revision.ts +49 -0
- package/studio/server/introspect/runtime.test.ts +83 -143
- package/studio/server/introspect/runtime.ts +40 -11
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +151 -0
- package/studio/server/introspect/schema-refs.test.ts +43 -88
- package/studio/server/introspect/schema-refs.ts +14 -49
- package/studio/server/introspect/source-overlay/annotations.ts +14 -0
- package/studio/server/introspect/source-overlay/handlers.ts +161 -0
- package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
- package/studio/server/introspect/source-overlay/project.ts +248 -0
- package/studio/server/introspect/source-overlay/spans.ts +330 -0
- package/studio/server/json.ts +46 -0
- package/studio/server/lifecycle.ts +5 -1
- package/studio/server/sse.test.ts +26 -0
- package/studio/server/sse.ts +4 -1
- package/studio/server/state/baseline.test.ts +223 -7
- package/studio/server/state/baseline.ts +148 -34
- package/studio/server/state/comments.test.ts +66 -1
- package/studio/server/state/comments.ts +173 -10
- package/studio/server/state/context.ts +34 -2
- package/studio/server/state/documents.ts +42 -1
- package/studio/server/state/integrations.ts +27 -1
- package/studio/server/state/layout.test.ts +34 -0
- package/studio/server/state/layout.ts +44 -17
- package/studio/server/state/settings.ts +29 -22
- package/studio/server/state/store.test.ts +122 -0
- package/studio/server/state/store.ts +66 -17
- package/studio/server/state/visibility.ts +22 -11
- package/studio/server/views/model.test.ts +57 -0
- package/studio/server/views/model.ts +66 -0
- package/studio/server/views/runtime.ts +40 -0
- package/studio/server/views/selection-repository.test.ts +31 -0
- package/studio/server/views/selection-repository.ts +60 -0
- package/studio/server/views/session.test.ts +73 -0
- package/studio/server/views/session.ts +166 -0
- package/studio/server/views/target.test.ts +78 -0
- package/studio/server/views/target.ts +222 -0
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +9 -5
- package/studio/server/workspace/catalog.test.ts +17 -0
- package/studio/server/workspace/catalog.ts +43 -0
- package/studio/server/{state → workspace}/create.test.ts +1 -1
- package/studio/server/{state → workspace}/create.ts +1 -1
- package/studio/server/{state → workspace}/git.ts +1 -1
- package/studio/server/workspace/updates.ts +83 -0
- package/studio/server/workspace-state.ts +1 -1
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/README.md +16 -0
- package/studio/shared/contracts/agent.ts +270 -0
- package/studio/shared/contracts/runtime.ts +50 -0
- package/studio/shared/contracts/schema.ts +393 -0
- package/studio/shared/contracts/surface.test.ts +317 -0
- package/studio/shared/contracts/workspace.ts +265 -0
- package/studio/shared/layout.test.ts +116 -0
- package/studio/shared/schema/identity.test.ts +34 -0
- package/studio/shared/schema/identity.ts +68 -0
- package/studio/shared/types.ts +11 -1099
- package/studio/tsconfig.json +1 -1
- package/viewer/dist/main.js +40 -61
- package/.check-workspace.cjs +0 -40
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- package/studio/client/dist/assets/elk-api-D2xgMJvi.js +0 -1
- package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
- package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
- package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
- package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
- package/studio/server/state/catalog.ts +0 -117
- package/studio/server/state/instance.ts +0 -280
- package/studio/server/state/updates.ts +0 -63
- package/studio/server/state/views.test.ts +0 -286
- package/studio/server/state/views.ts +0 -535
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { decodeSchemaIR } from './schema-ir-json'
|
|
4
|
+
|
|
5
|
+
function canonicalIr() {
|
|
6
|
+
return {
|
|
7
|
+
format: 'astrale.dsl',
|
|
8
|
+
version: 'v1',
|
|
9
|
+
domain: 'example.test',
|
|
10
|
+
classes: {
|
|
11
|
+
Document: {
|
|
12
|
+
type: 'node',
|
|
13
|
+
name: 'Document',
|
|
14
|
+
origin: 'example.test',
|
|
15
|
+
ref: { origin: 'example.test', kind: 'class', name: 'Document' },
|
|
16
|
+
properties: {},
|
|
17
|
+
methods: {
|
|
18
|
+
rename: {
|
|
19
|
+
name: 'rename',
|
|
20
|
+
input: { type: 'object', properties: {} },
|
|
21
|
+
output: { mode: 'value', schema: { type: 'boolean' } },
|
|
22
|
+
static: false,
|
|
23
|
+
inheritance: 'default',
|
|
24
|
+
auth: 'authorized',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
importsByKey: {},
|
|
30
|
+
importedClassesByKey: {},
|
|
31
|
+
functions: {
|
|
32
|
+
search: {
|
|
33
|
+
name: 'search',
|
|
34
|
+
input: { type: 'object', properties: {} },
|
|
35
|
+
output: { mode: 'stream', item: { type: 'string' } },
|
|
36
|
+
auth: 'authenticated',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
views: {},
|
|
40
|
+
policies: {},
|
|
41
|
+
dependencies: [],
|
|
42
|
+
core: {},
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test('admits the complete canonical Studio projection', () => {
|
|
47
|
+
expect(decodeSchemaIR(canonicalIr())).toMatchObject({
|
|
48
|
+
format: 'astrale.dsl',
|
|
49
|
+
domain: 'example.test',
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('rejects stale projections that lost canonical callable contracts', () => {
|
|
54
|
+
const input = canonicalIr()
|
|
55
|
+
const rename = input.classes.Document.methods.rename as Record<string, unknown>
|
|
56
|
+
delete rename.input
|
|
57
|
+
delete rename.output
|
|
58
|
+
Object.assign(rename, { params: {}, returns: { type: 'boolean' } })
|
|
59
|
+
expect(decodeSchemaIR(input)).toBeUndefined()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('rejects unversioned and incomplete projection roots', () => {
|
|
63
|
+
const withoutFormat = canonicalIr() as Record<string, unknown>
|
|
64
|
+
delete withoutFormat.format
|
|
65
|
+
expect(decodeSchemaIR(withoutFormat)).toBeUndefined()
|
|
66
|
+
|
|
67
|
+
const withoutViews = canonicalIr() as Record<string, unknown>
|
|
68
|
+
delete withoutViews.views
|
|
69
|
+
expect(decodeSchemaIR(withoutViews)).toBeUndefined()
|
|
70
|
+
})
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/** Structural admission for persisted Studio render IR. SDK admission is separate. */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
IrClass,
|
|
5
|
+
IrClassRef,
|
|
6
|
+
IrFunction,
|
|
7
|
+
IrMethod,
|
|
8
|
+
IrSchemaRef,
|
|
9
|
+
JsonSchema,
|
|
10
|
+
SchemaIR,
|
|
11
|
+
} from '../../shared/types'
|
|
12
|
+
|
|
13
|
+
import { asJsonRecord, asStringArray } from '../json'
|
|
14
|
+
|
|
15
|
+
function recordOf(value: unknown, guard: (candidate: unknown) => boolean): boolean {
|
|
16
|
+
const record = asJsonRecord(value)
|
|
17
|
+
return !!record && Object.values(record).every(guard)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isJsonSchema(value: unknown): value is JsonSchema {
|
|
21
|
+
return !!asJsonRecord(value)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isSchemaRef(value: unknown): value is IrSchemaRef {
|
|
25
|
+
const record = asJsonRecord(value)
|
|
26
|
+
return (
|
|
27
|
+
typeof record?.origin === 'string' &&
|
|
28
|
+
typeof record.name === 'string' &&
|
|
29
|
+
['class', 'function', 'policy', 'view', 'core'].includes(String(record.kind))
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isClassRef(value: unknown): value is IrClassRef {
|
|
34
|
+
return isSchemaRef(value) && value.kind === 'class'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isOutput(value: unknown): boolean {
|
|
38
|
+
const record = asJsonRecord(value)
|
|
39
|
+
if (!record) return false
|
|
40
|
+
if (record.mode === 'binary') return true
|
|
41
|
+
if (record.mode === 'value') return isJsonSchema(record.schema)
|
|
42
|
+
if (record.mode === 'stream') return isJsonSchema(record.item)
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isMethod(value: unknown): value is IrMethod {
|
|
47
|
+
const record = asJsonRecord(value)
|
|
48
|
+
return (
|
|
49
|
+
typeof record?.name === 'string' &&
|
|
50
|
+
isJsonSchema(record.input) &&
|
|
51
|
+
isOutput(record.output) &&
|
|
52
|
+
typeof record.static === 'boolean' &&
|
|
53
|
+
['default', 'abstract', 'sealed'].includes(String(record.inheritance))
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isEndpoint(value: unknown): boolean {
|
|
58
|
+
const record = asJsonRecord(value)
|
|
59
|
+
if (!record || typeof record.name !== 'string' || !asStringArray(record.types)) return false
|
|
60
|
+
if (
|
|
61
|
+
record.refs !== undefined &&
|
|
62
|
+
(!Array.isArray(record.refs) || !record.refs.every(isSchemaRef))
|
|
63
|
+
) {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
if (record.cardinality !== undefined) {
|
|
67
|
+
const cardinality = asJsonRecord(record.cardinality)
|
|
68
|
+
if (
|
|
69
|
+
!cardinality ||
|
|
70
|
+
typeof cardinality.min !== 'number' ||
|
|
71
|
+
!Number.isFinite(cardinality.min) ||
|
|
72
|
+
(cardinality.max !== null &&
|
|
73
|
+
(typeof cardinality.max !== 'number' || !Number.isFinite(cardinality.max)))
|
|
74
|
+
) {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isClass(value: unknown): value is IrClass {
|
|
82
|
+
const record = asJsonRecord(value)
|
|
83
|
+
return (
|
|
84
|
+
(record?.type === 'node' || record?.type === 'edge') &&
|
|
85
|
+
typeof record.name === 'string' &&
|
|
86
|
+
typeof record.origin === 'string' &&
|
|
87
|
+
isClassRef(record.ref) &&
|
|
88
|
+
recordOf(record.properties, isJsonSchema) &&
|
|
89
|
+
recordOf(record.methods, isMethod) &&
|
|
90
|
+
(record.required === undefined || asStringArray(record.required) !== undefined) &&
|
|
91
|
+
(record.extends === undefined || asStringArray(record.extends) !== undefined) &&
|
|
92
|
+
(record.extendsRefs === undefined ||
|
|
93
|
+
(Array.isArray(record.extendsRefs) && record.extendsRefs.every(isClassRef))) &&
|
|
94
|
+
(record.endpoints === undefined ||
|
|
95
|
+
(Array.isArray(record.endpoints) && record.endpoints.every(isEndpoint)))
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isFunction(value: unknown): value is IrFunction {
|
|
100
|
+
const record = asJsonRecord(value)
|
|
101
|
+
return typeof record?.name === 'string' && isJsonSchema(record.input) && isOutput(record.output)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isImport(value: unknown): boolean {
|
|
105
|
+
const record = asJsonRecord(value)
|
|
106
|
+
return (
|
|
107
|
+
typeof record?.origin === 'string' &&
|
|
108
|
+
isClassRef(record.ref) &&
|
|
109
|
+
typeof record.key === 'string' &&
|
|
110
|
+
record.key === `${record.ref.origin}:class.${record.ref.name}`
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isView(value: unknown): boolean {
|
|
115
|
+
const record = asJsonRecord(value)
|
|
116
|
+
const target = asJsonRecord(record?.target)
|
|
117
|
+
return (
|
|
118
|
+
typeof record?.name === 'string' &&
|
|
119
|
+
['required', 'optional', 'public'].includes(String(record.auth)) &&
|
|
120
|
+
!!target &&
|
|
121
|
+
(target.kind === 'domain' ||
|
|
122
|
+
(target.kind === 'definition' &&
|
|
123
|
+
Array.isArray(target.definitions) &&
|
|
124
|
+
target.definitions.every(isSchemaRef)))
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function decodeSchemaIR(value: unknown): SchemaIR | undefined {
|
|
129
|
+
const record = asJsonRecord(value)
|
|
130
|
+
if (
|
|
131
|
+
!record ||
|
|
132
|
+
record.format !== 'astrale.dsl' ||
|
|
133
|
+
typeof record.version !== 'string' ||
|
|
134
|
+
typeof record.domain !== 'string' ||
|
|
135
|
+
!recordOf(record.classes, isClass) ||
|
|
136
|
+
!recordOf(record.functions, isFunction) ||
|
|
137
|
+
!recordOf(record.importsByKey, isImport) ||
|
|
138
|
+
!recordOf(record.importedClassesByKey, isClass) ||
|
|
139
|
+
!recordOf(record.views, isView) ||
|
|
140
|
+
!asJsonRecord(record.policies) ||
|
|
141
|
+
!Array.isArray(record.dependencies) ||
|
|
142
|
+
!record.dependencies.every((dependency) => {
|
|
143
|
+
const item = asJsonRecord(dependency)
|
|
144
|
+
return typeof item?.origin === 'string' && typeof item.revision === 'string'
|
|
145
|
+
}) ||
|
|
146
|
+
!('core' in record)
|
|
147
|
+
) {
|
|
148
|
+
return undefined
|
|
149
|
+
}
|
|
150
|
+
return record as unknown as SchemaIR
|
|
151
|
+
}
|
|
@@ -1,100 +1,55 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { StudioSchemaBundle } from '../../shared/types'
|
|
4
4
|
|
|
5
5
|
import { schemaRefs } from './schema-refs'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type: 'interface',
|
|
15
|
-
name,
|
|
16
|
-
origin,
|
|
17
|
-
ref: { origin, kind: 'interface', name },
|
|
18
|
-
properties: Object.fromEntries(properties.map((property) => [property, { type: 'string' }])),
|
|
19
|
-
methods: Object.fromEntries(
|
|
20
|
-
methods.map((method) => [
|
|
21
|
-
method,
|
|
22
|
-
{
|
|
23
|
-
name: method,
|
|
24
|
-
params: {},
|
|
25
|
-
returns: {},
|
|
26
|
-
static: false,
|
|
27
|
-
inheritance: 'default',
|
|
28
|
-
},
|
|
29
|
-
]),
|
|
30
|
-
),
|
|
7
|
+
test('enumerates local and exact imported Class anchors without collapsing homonyms', () => {
|
|
8
|
+
const method = {
|
|
9
|
+
name: 'rename',
|
|
10
|
+
params: {},
|
|
11
|
+
returns: {},
|
|
12
|
+
static: false,
|
|
13
|
+
inheritance: 'default' as const,
|
|
31
14
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
function bundleWithImports(
|
|
35
|
-
exact: NonNullable<StudioSchemaBundle['ir']>['importedInterfacesByKey'],
|
|
36
|
-
legacy: Record<string, IrInterface>,
|
|
37
|
-
): StudioSchemaBundle {
|
|
38
|
-
return {
|
|
15
|
+
const bundle = {
|
|
39
16
|
ir: {
|
|
17
|
+
version: 'v1',
|
|
40
18
|
domain: 'root.example.dev',
|
|
41
|
-
|
|
42
|
-
|
|
19
|
+
classes: {
|
|
20
|
+
Document: {
|
|
21
|
+
type: 'node',
|
|
22
|
+
name: 'Document',
|
|
23
|
+
properties: { title: { type: 'string' } },
|
|
24
|
+
methods: { rename: method },
|
|
25
|
+
},
|
|
26
|
+
},
|
|
43
27
|
functions: {},
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
test('enumerates every exact homonym without selecting a name-keyed winner', () => {
|
|
52
|
-
const refs = schemaRefs(
|
|
53
|
-
bundleWithImports(
|
|
54
|
-
{
|
|
55
|
-
'directory.example.dev:interface.Named': importedInterface(
|
|
56
|
-
'Named',
|
|
57
|
-
'directory.example.dev',
|
|
58
|
-
['directoryName'],
|
|
59
|
-
),
|
|
60
|
-
'people.example.dev:interface.Named': importedInterface(
|
|
61
|
-
'Named',
|
|
62
|
-
'people.example.dev',
|
|
63
|
-
[],
|
|
64
|
-
['rename'],
|
|
65
|
-
),
|
|
28
|
+
importsByKey: {},
|
|
29
|
+
importedClassesByKey: {
|
|
30
|
+
'directory.example.dev:class.Named': {
|
|
31
|
+
type: 'node',
|
|
32
|
+
name: 'Named',
|
|
33
|
+
properties: { directoryName: { type: 'string' } },
|
|
34
|
+
methods: {},
|
|
66
35
|
},
|
|
67
|
-
{
|
|
68
|
-
|
|
36
|
+
'people.example.dev:class.Named': {
|
|
37
|
+
type: 'node',
|
|
38
|
+
name: 'Named',
|
|
39
|
+
properties: {},
|
|
40
|
+
methods: { rename: method },
|
|
69
41
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
expect(refs).toContain('interface.directory.example.dev:interface.Named')
|
|
74
|
-
expect(refs).toContain('interface.directory.example.dev:interface.Named.property.directoryName')
|
|
75
|
-
expect(refs).toContain('interface.people.example.dev:interface.Named.method.rename')
|
|
76
|
-
expect(refs).not.toContain('interface.Named')
|
|
77
|
-
expect(refs).not.toContain('interface.Named.property.legacyOnly')
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test('uses the name-keyed imported interface only when the exact index is absent', () => {
|
|
81
|
-
expect(
|
|
82
|
-
schemaRefs(
|
|
83
|
-
bundleWithImports(undefined, {
|
|
84
|
-
Named: importedInterface('Named', 'legacy.example.dev', ['name']),
|
|
85
|
-
}),
|
|
86
|
-
),
|
|
87
|
-
).toEqual(['interface.Named', 'interface.Named.property.name'])
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
} as unknown as StudioSchemaBundle
|
|
88
45
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
).toEqual([])
|
|
99
|
-
})
|
|
46
|
+
expect(schemaRefs(bundle)).toEqual([
|
|
47
|
+
'class.Document',
|
|
48
|
+
'class.Document.property.title',
|
|
49
|
+
'class.Document.method.rename',
|
|
50
|
+
'class.directory.example.dev:class.Named',
|
|
51
|
+
'class.directory.example.dev:class.Named.property.directoryName',
|
|
52
|
+
'class.people.example.dev:class.Named',
|
|
53
|
+
'class.people.example.dev:class.Named.method.rename',
|
|
54
|
+
])
|
|
100
55
|
})
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import type { StudioSchemaBundle } from '../../shared/types'
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* schema-refs.ts — enumerate every SCHEMA anchor ref a bundle can target, derived
|
|
5
|
-
* from the IR (the authority), NOT from `overlay.sourceSpans`.
|
|
6
|
-
*
|
|
7
|
-
* Source spans are a best-effort ts-morph source-location index: they cover only
|
|
8
|
-
* locally-authored members and only the declaration helpers the parser recognises.
|
|
9
|
-
* Using their keys as the set of "valid" targets falsely orphans real, commentable
|
|
10
|
-
* targets — most notably INHERITED members surfaced in the detail pane's Inherited
|
|
11
|
-
* section (kernel mixins / cross-domain interfaces, e.g. `interface.Named.property.name`),
|
|
12
|
-
* which live in another package and so have no local span. This walks the IR
|
|
13
|
-
* instead, mirroring exactly the refs the detail pane stamps.
|
|
14
|
-
*/
|
|
3
|
+
/** Enumerate every schema anchor represented by the admitted render IR. */
|
|
15
4
|
export function schemaRefs(bundle: StudioSchemaBundle): string[] {
|
|
16
5
|
const ir = bundle.ir
|
|
17
6
|
if (!ir) return []
|
|
@@ -22,46 +11,22 @@ export function schemaRefs(bundle: StudioSchemaBundle): string[] {
|
|
|
22
11
|
methods: Record<string, unknown> | undefined,
|
|
23
12
|
) => {
|
|
24
13
|
refs.add(base)
|
|
25
|
-
for (const
|
|
26
|
-
for (const
|
|
14
|
+
for (const property of Object.keys(properties ?? {})) refs.add(`${base}.property.${property}`)
|
|
15
|
+
for (const method of Object.keys(methods ?? {})) refs.add(`${base}.method.${method}`)
|
|
27
16
|
}
|
|
28
17
|
|
|
29
|
-
for (const [name,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
for (const ep of cls.endpoints ?? [])
|
|
37
|
-
if (ep.name) refs.add(`edge.${name}.endpoint.${ep.name}`)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
for (const name of Object.keys(ir.functions ?? {})) refs.add(`function.${name}`)
|
|
41
|
-
|
|
42
|
-
// Imported interfaces (kernel mixins + cross-domain) are rendered — and so are
|
|
43
|
-
// commentable — in the Inherited section, keyed under the `interface.` namespace.
|
|
44
|
-
// Canonical bundles retain every exact `(origin, kind, name)` identity. Iterate
|
|
45
|
-
// all of them: a short-name map must never pick one homonym by traversal order.
|
|
46
|
-
// The name-keyed bundle field remains a legacy-only fallback.
|
|
47
|
-
if (ir.importedInterfacesByKey !== undefined) {
|
|
48
|
-
for (const [key, iface] of Object.entries(ir.importedInterfacesByKey)) {
|
|
49
|
-
const anchor = importedInterfaceAnchor(key)
|
|
50
|
-
if (anchor) addMember(anchor, iface.properties, iface.methods)
|
|
18
|
+
for (const [name, definition] of Object.entries(ir.classes)) {
|
|
19
|
+
const namespace = definition.type === 'edge' ? 'edge' : 'class'
|
|
20
|
+
addMember(`${namespace}.${name}`, definition.properties, definition.methods)
|
|
21
|
+
if (definition.type === 'edge') {
|
|
22
|
+
for (const endpoint of definition.endpoints ?? []) {
|
|
23
|
+
if (endpoint.name) refs.add(`edge.${name}.endpoint.${endpoint.name}`)
|
|
24
|
+
}
|
|
51
25
|
}
|
|
52
|
-
} else {
|
|
53
|
-
for (const [name, iface] of Object.entries(bundle.importedInterfaces ?? {}))
|
|
54
|
-
addMember(`interface.${name}`, iface.properties, iface.methods)
|
|
55
26
|
}
|
|
56
|
-
|
|
27
|
+
for (const name of Object.keys(ir.functions)) refs.add(`function.${name}`)
|
|
28
|
+
for (const [key, definition] of Object.entries(ir.importedClassesByKey)) {
|
|
29
|
+
addMember(`class.${key}`, definition.properties, definition.methods)
|
|
30
|
+
}
|
|
57
31
|
return [...refs]
|
|
58
32
|
}
|
|
59
|
-
|
|
60
|
-
function importedInterfaceAnchor(key: string): string | null {
|
|
61
|
-
const separator = key.lastIndexOf(':')
|
|
62
|
-
if (separator <= 0) return null
|
|
63
|
-
const ref = key.slice(separator + 1)
|
|
64
|
-
if (!ref.startsWith('interface.')) return null
|
|
65
|
-
const name = ref.slice('interface.'.length)
|
|
66
|
-
return /^[A-Za-z_$][\w$]*$/.test(name) ? `interface.${key}` : null
|
|
67
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility owner for Studio schema annotations.
|
|
3
|
+
*
|
|
4
|
+
* The current Kernel admits enum updates against their exact Value Schema, so
|
|
5
|
+
* there are no source-derived annotations today. The serialized overlay field
|
|
6
|
+
* remains consumed by the client and shared contract.
|
|
7
|
+
*/
|
|
8
|
+
import type { SchemaAnnotation, SchemaIR } from '../../../shared/types'
|
|
9
|
+
|
|
10
|
+
export function buildSchemaAnnotations(_args: { ir: SchemaIR | null }): SchemaAnnotation[] {
|
|
11
|
+
// The current Kernel admits updated values against their exact Value Schema;
|
|
12
|
+
// enum properties therefore need no Studio-specific compatibility warning.
|
|
13
|
+
return []
|
|
14
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/** Source links for SDK V1 Action and Workflow declarations. */
|
|
2
|
+
import { readdirSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { Node, SyntaxKind, type CallExpression } from 'ts-morph'
|
|
5
|
+
|
|
6
|
+
import type { HandlerLink, SchemaIR } from '../../../shared/types'
|
|
7
|
+
|
|
8
|
+
import { scanKernelCalls } from './kernel-calls'
|
|
9
|
+
import { newProject, relToRoot, unwrapExpression, valueOfIdentifier } from './project'
|
|
10
|
+
|
|
11
|
+
const IGNORED_DIRECTORIES = new Set([
|
|
12
|
+
'.git',
|
|
13
|
+
'.history',
|
|
14
|
+
'.astrale',
|
|
15
|
+
'.cache',
|
|
16
|
+
'__tests__',
|
|
17
|
+
'dist',
|
|
18
|
+
'node_modules',
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
function authoredTypeScriptFiles(root: string): string[] {
|
|
22
|
+
const files: string[] = []
|
|
23
|
+
const visit = (directory: string) => {
|
|
24
|
+
let entries
|
|
25
|
+
try {
|
|
26
|
+
entries = readdirSync(directory, { withFileTypes: true })
|
|
27
|
+
} catch {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.isSymbolicLink()) continue
|
|
32
|
+
const path = join(directory, entry.name)
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
if (!IGNORED_DIRECTORIES.has(entry.name)) visit(path)
|
|
35
|
+
} else if (entry.isFile() && path.endsWith('.ts') && !path.endsWith('.test.ts')) {
|
|
36
|
+
files.push(path)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
visit(root)
|
|
41
|
+
return files
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface CallableOwner {
|
|
45
|
+
owner: string
|
|
46
|
+
ownerKind: 'class' | 'function'
|
|
47
|
+
method: string
|
|
48
|
+
static: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function declaredCallable(ir: SchemaIR | null, key: string): CallableOwner | undefined {
|
|
52
|
+
if (!ir) return undefined
|
|
53
|
+
const standalone = ir.functions[key]
|
|
54
|
+
if (standalone) {
|
|
55
|
+
return {
|
|
56
|
+
owner: ir.domain,
|
|
57
|
+
ownerKind: 'function',
|
|
58
|
+
method: key,
|
|
59
|
+
static: true,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const separator = key.lastIndexOf('.')
|
|
63
|
+
if (separator <= 0) return undefined
|
|
64
|
+
const owner = key.slice(0, separator)
|
|
65
|
+
const method = key.slice(separator + 1)
|
|
66
|
+
const definition = ir.classes[owner]?.methods[method]
|
|
67
|
+
return definition
|
|
68
|
+
? {
|
|
69
|
+
owner,
|
|
70
|
+
ownerKind: 'class',
|
|
71
|
+
method,
|
|
72
|
+
static: definition.static,
|
|
73
|
+
}
|
|
74
|
+
: undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function declarationKind(call: CallExpression): 'action' | 'workflow' | undefined {
|
|
78
|
+
const expression = unwrapExpression(call.getExpression())
|
|
79
|
+
if (!Node.isCallExpression(expression)) return undefined
|
|
80
|
+
const factory = unwrapExpression(expression.getExpression())
|
|
81
|
+
if (!Node.isIdentifier(factory)) return undefined
|
|
82
|
+
if (factory.getText() === 'defineAction') return 'action'
|
|
83
|
+
if (factory.getText() === 'defineWorkflow') return 'workflow'
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function stringArgument(call: CallExpression, index: number): string | undefined {
|
|
88
|
+
const argument = call.getArguments()[index]
|
|
89
|
+
return argument &&
|
|
90
|
+
(Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument))
|
|
91
|
+
? argument.getLiteralText()
|
|
92
|
+
: undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveHandler(node: Node): Node {
|
|
96
|
+
let current = unwrapExpression(node)
|
|
97
|
+
if (Node.isIdentifier(current)) current = unwrapExpression(valueOfIdentifier(current) ?? current)
|
|
98
|
+
return current
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isStubHandler(node: Node): boolean {
|
|
102
|
+
if (
|
|
103
|
+
!Node.isArrowFunction(node) &&
|
|
104
|
+
!Node.isFunctionExpression(node) &&
|
|
105
|
+
!Node.isFunctionDeclaration(node) &&
|
|
106
|
+
!Node.isMethodDeclaration(node)
|
|
107
|
+
) {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
const body = node.getBody()
|
|
111
|
+
if (!Node.isBlock(body)) return false
|
|
112
|
+
const statements = body.getStatements()
|
|
113
|
+
return statements.length > 0 && statements.every(Node.isThrowStatement)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Discover modular `defineAction()` and `defineWorkflow()` declarations. Runtime
|
|
118
|
+
* admission remains authoritative for exhaustiveness; this is a source overlay.
|
|
119
|
+
*/
|
|
120
|
+
export function buildHandlerLinks(args: {
|
|
121
|
+
ir: SchemaIR | null
|
|
122
|
+
domainRoot: string
|
|
123
|
+
}): HandlerLink[] {
|
|
124
|
+
const { ir, domainRoot } = args
|
|
125
|
+
if (!domainRoot) return []
|
|
126
|
+
const project = newProject()
|
|
127
|
+
for (const file of authoredTypeScriptFiles(domainRoot)) project.addSourceFileAtPath(file)
|
|
128
|
+
const links = new Map<string, HandlerLink>()
|
|
129
|
+
|
|
130
|
+
for (const source of project.getSourceFiles()) {
|
|
131
|
+
for (const call of source.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
132
|
+
const kind = declarationKind(call)
|
|
133
|
+
if (!kind) continue
|
|
134
|
+
const key = stringArgument(call, 0)
|
|
135
|
+
const handlerArgument = call.getArguments()[1]
|
|
136
|
+
if (!key || !handlerArgument) continue
|
|
137
|
+
const callable = declaredCallable(ir, key)
|
|
138
|
+
if (!callable) continue
|
|
139
|
+
const handler = resolveHandler(handlerArgument)
|
|
140
|
+
const handlerFile = relToRoot(domainRoot, handler.getSourceFile().getFilePath())
|
|
141
|
+
const link: HandlerLink = {
|
|
142
|
+
...callable,
|
|
143
|
+
kind,
|
|
144
|
+
wiringFile: handlerFile,
|
|
145
|
+
wiringLine: call.getStartLineNumber(),
|
|
146
|
+
handlerFile,
|
|
147
|
+
handlerLine: handler.getStartLineNumber(),
|
|
148
|
+
implemented: !isStubHandler(handler),
|
|
149
|
+
}
|
|
150
|
+
const kernelCalls = scanKernelCalls(handler.getSourceFile().getFilePath())
|
|
151
|
+
if (kernelCalls.length > 0) link.kernelCalls = kernelCalls
|
|
152
|
+
links.set(`${callable.ownerKind}:${callable.owner}.${callable.method}`, link)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return [...links.values()].sort((left, right) =>
|
|
157
|
+
left.owner === right.owner
|
|
158
|
+
? left.method.localeCompare(right.method)
|
|
159
|
+
: left.owner.localeCompare(right.owner),
|
|
160
|
+
)
|
|
161
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel-operation token scan for resolved handler source files.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from 'node:fs'
|
|
5
|
+
|
|
6
|
+
type KernelToken = { token: string; label?: string }
|
|
7
|
+
|
|
8
|
+
/** Kernel-op idioms surfaced as `kernelCalls`; entries are longest-first. */
|
|
9
|
+
const KERNEL_TOKENS: KernelToken[] = [
|
|
10
|
+
{ token: 'graph.createEdge' },
|
|
11
|
+
{ token: 'graph.removeEdge' },
|
|
12
|
+
{ token: 'function.mutate' },
|
|
13
|
+
{ token: 'graph.children' },
|
|
14
|
+
{ token: 'function.get' },
|
|
15
|
+
{ token: 'graph.create' },
|
|
16
|
+
{ token: 'graph.update' },
|
|
17
|
+
{ token: 'graph.remove' },
|
|
18
|
+
{ token: 'graph.mutate' },
|
|
19
|
+
{ token: 'auth.revoke' },
|
|
20
|
+
{ token: 'graph.links' },
|
|
21
|
+
{ token: 'auth.grant' },
|
|
22
|
+
{ token: 'auth.check' },
|
|
23
|
+
{ token: 'graph.tree' },
|
|
24
|
+
{ token: 'graph.node' },
|
|
25
|
+
{ token: 'graph.get' },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
/** Scan handler file text for kernel-op tokens. */
|
|
29
|
+
export function scanKernelCalls(file: string): string[] {
|
|
30
|
+
let text: string
|
|
31
|
+
try {
|
|
32
|
+
text = readFileSync(file, 'utf8')
|
|
33
|
+
} catch {
|
|
34
|
+
return []
|
|
35
|
+
}
|
|
36
|
+
const found: string[] = []
|
|
37
|
+
let work = text
|
|
38
|
+
for (const entry of KERNEL_TOKENS) {
|
|
39
|
+
if (work.includes(entry.token)) {
|
|
40
|
+
found.push(entry.label ?? entry.token)
|
|
41
|
+
// Blank out matches so `::getLinks` doesn't also count as `::getLink`.
|
|
42
|
+
work = work.split(entry.token).join(' '.repeat(entry.token.length))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return found
|
|
46
|
+
}
|