@wrenai/genbi 0.0.1
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/LICENSE +201 -0
- package/README.md +141 -0
- package/bin/genbi.mjs +31 -0
- package/dist/assets/echarts-LTFC-ysd.js +40 -0
- package/dist/assets/index-Bo--QMUN.js +441 -0
- package/dist/assets/index-Xs4q7pNo.css +1 -0
- package/dist/index.html +14 -0
- package/dist/wrenai_logo.png +0 -0
- package/dist/wrenai_logo_white.png +0 -0
- package/dist-server/harness/auth/detect.d.ts +18 -0
- package/dist-server/harness/auth/detect.js +37 -0
- package/dist-server/harness/auth/index.d.ts +4 -0
- package/dist-server/harness/auth/index.js +2 -0
- package/dist-server/harness/auth/probe.d.ts +18 -0
- package/dist-server/harness/auth/probe.js +52 -0
- package/dist-server/harness/auth/types.d.ts +42 -0
- package/dist-server/harness/auth/types.js +9 -0
- package/dist-server/harness/bundle/loader.d.ts +32 -0
- package/dist-server/harness/bundle/loader.js +43 -0
- package/dist-server/harness/bundle/schema.d.ts +199 -0
- package/dist-server/harness/bundle/schema.js +127 -0
- package/dist-server/harness/bundle/version.d.ts +10 -0
- package/dist-server/harness/bundle/version.js +43 -0
- package/dist-server/harness/capability/gate.d.ts +6 -0
- package/dist-server/harness/capability/gate.js +29 -0
- package/dist-server/harness/capability/registry.d.ts +6 -0
- package/dist-server/harness/capability/registry.js +17 -0
- package/dist-server/harness/cli-args.d.ts +123 -0
- package/dist-server/harness/cli-args.js +247 -0
- package/dist-server/harness/cli.d.ts +2 -0
- package/dist-server/harness/cli.js +151 -0
- package/dist-server/harness/compile/cache.d.ts +23 -0
- package/dist-server/harness/compile/cache.js +109 -0
- package/dist-server/harness/compile/compose-profile.d.ts +34 -0
- package/dist-server/harness/compile/compose-profile.js +95 -0
- package/dist-server/harness/compile/errors.d.ts +22 -0
- package/dist-server/harness/compile/errors.js +41 -0
- package/dist-server/harness/compile/fingerprint.d.ts +22 -0
- package/dist-server/harness/compile/fingerprint.js +84 -0
- package/dist-server/harness/compile/index.d.ts +8 -0
- package/dist-server/harness/compile/index.js +7 -0
- package/dist-server/harness/compile/pipeline.d.ts +66 -0
- package/dist-server/harness/compile/pipeline.js +176 -0
- package/dist-server/harness/compile/resolve-binary.d.ts +55 -0
- package/dist-server/harness/compile/resolve-binary.js +133 -0
- package/dist-server/harness/compile/types.d.ts +107 -0
- package/dist-server/harness/compile/types.js +1 -0
- package/dist-server/harness/compile/warble-identity.d.ts +12 -0
- package/dist-server/harness/compile/warble-identity.js +33 -0
- package/dist-server/harness/compliance/gate.d.ts +35 -0
- package/dist-server/harness/compliance/gate.js +48 -0
- package/dist-server/harness/compliance/index.d.ts +2 -0
- package/dist-server/harness/compliance/index.js +1 -0
- package/dist-server/harness/compliance/types.d.ts +16 -0
- package/dist-server/harness/compliance/types.js +1 -0
- package/dist-server/harness/events/emitter.d.ts +34 -0
- package/dist-server/harness/events/emitter.js +25 -0
- package/dist-server/harness/events/index.d.ts +4 -0
- package/dist-server/harness/events/index.js +2 -0
- package/dist-server/harness/events/sse.d.ts +20 -0
- package/dist-server/harness/events/sse.js +48 -0
- package/dist-server/harness/events/types.d.ts +131 -0
- package/dist-server/harness/events/types.js +1 -0
- package/dist-server/harness/exec/errors.d.ts +16 -0
- package/dist-server/harness/exec/errors.js +29 -0
- package/dist-server/harness/exec/index.d.ts +5 -0
- package/dist-server/harness/exec/index.js +2 -0
- package/dist-server/harness/exec/local.d.ts +32 -0
- package/dist-server/harness/exec/local.js +134 -0
- package/dist-server/harness/exec/policy.d.ts +12 -0
- package/dist-server/harness/exec/policy.js +1 -0
- package/dist-server/harness/exec/types.d.ts +63 -0
- package/dist-server/harness/exec/types.js +1 -0
- package/dist-server/harness/guardrails/index.d.ts +2 -0
- package/dist-server/harness/guardrails/index.js +1 -0
- package/dist-server/harness/guardrails/policy.d.ts +45 -0
- package/dist-server/harness/guardrails/policy.js +57 -0
- package/dist-server/harness/index.d.ts +37 -0
- package/dist-server/harness/index.js +19 -0
- package/dist-server/harness/loop/errors.d.ts +53 -0
- package/dist-server/harness/loop/errors.js +72 -0
- package/dist-server/harness/loop/executor.d.ts +123 -0
- package/dist-server/harness/loop/executor.js +380 -0
- package/dist-server/harness/loop/guard.d.ts +16 -0
- package/dist-server/harness/loop/guard.js +1 -0
- package/dist-server/harness/loop/index.d.ts +5 -0
- package/dist-server/harness/loop/index.js +3 -0
- package/dist-server/harness/native-producer/index.d.ts +109 -0
- package/dist-server/harness/native-producer/index.js +302 -0
- package/dist-server/harness/npm-package-resolve.d.ts +28 -0
- package/dist-server/harness/npm-package-resolve.js +47 -0
- package/dist-server/harness/providers/adapters/anthropic.d.ts +7 -0
- package/dist-server/harness/providers/adapters/anthropic.js +6 -0
- package/dist-server/harness/providers/adapters/mock.d.ts +4 -0
- package/dist-server/harness/providers/adapters/mock.js +5 -0
- package/dist-server/harness/providers/adapters/openai-compatible.d.ts +10 -0
- package/dist-server/harness/providers/adapters/openai-compatible.js +10 -0
- package/dist-server/harness/providers/binding.d.ts +23 -0
- package/dist-server/harness/providers/binding.js +13 -0
- package/dist-server/harness/providers/errors.d.ts +6 -0
- package/dist-server/harness/providers/errors.js +12 -0
- package/dist-server/harness/providers/index.d.ts +18 -0
- package/dist-server/harness/providers/index.js +22 -0
- package/dist-server/harness/providers/registry.d.ts +16 -0
- package/dist-server/harness/providers/registry.js +24 -0
- package/dist-server/harness/render/envelope.d.ts +145 -0
- package/dist-server/harness/render/envelope.js +428 -0
- package/dist-server/harness/render/errors.d.ts +33 -0
- package/dist-server/harness/render/errors.js +46 -0
- package/dist-server/harness/render/index.d.ts +5 -0
- package/dist-server/harness/render/index.js +3 -0
- package/dist-server/harness/render/validate.d.ts +13 -0
- package/dist-server/harness/render/validate.js +88 -0
- package/dist-server/harness/route/adapter-spec.d.ts +40 -0
- package/dist-server/harness/route/adapter-spec.js +61 -0
- package/dist-server/harness/route/agent-sdk-cli.d.ts +30 -0
- package/dist-server/harness/route/agent-sdk-cli.js +86 -0
- package/dist-server/harness/route/agent-sdk-manifest.d.ts +31 -0
- package/dist-server/harness/route/agent-sdk-manifest.js +56 -0
- package/dist-server/harness/route/artifact-content.d.ts +53 -0
- package/dist-server/harness/route/artifact-content.js +83 -0
- package/dist-server/harness/route/chat-event-mapper.d.ts +95 -0
- package/dist-server/harness/route/chat-event-mapper.js +164 -0
- package/dist-server/harness/route/codex-ask-events.d.ts +25 -0
- package/dist-server/harness/route/codex-ask-events.js +395 -0
- package/dist-server/harness/route/codex-ask.d.ts +15 -0
- package/dist-server/harness/route/codex-ask.js +242 -0
- package/dist-server/harness/route/codex-local-cli.d.ts +20 -0
- package/dist-server/harness/route/codex-local-cli.js +78 -0
- package/dist-server/harness/route/codex-local-manifest.d.ts +22 -0
- package/dist-server/harness/route/codex-local-manifest.js +180 -0
- package/dist-server/harness/route/describe.d.ts +54 -0
- package/dist-server/harness/route/describe.js +84 -0
- package/dist-server/harness/route/dispatched.d.ts +138 -0
- package/dist-server/harness/route/dispatched.js +339 -0
- package/dist-server/harness/route/in-process.d.ts +43 -0
- package/dist-server/harness/route/in-process.js +106 -0
- package/dist-server/harness/route/index.d.ts +19 -0
- package/dist-server/harness/route/index.js +12 -0
- package/dist-server/harness/route/profile-source.d.ts +37 -0
- package/dist-server/harness/route/profile-source.js +76 -0
- package/dist-server/harness/route/route.d.ts +33 -0
- package/dist-server/harness/route/route.js +114 -0
- package/dist-server/harness/route/tier-binding.d.ts +36 -0
- package/dist-server/harness/route/tier-binding.js +63 -0
- package/dist-server/harness/route/types.d.ts +286 -0
- package/dist-server/harness/route/types.js +1 -0
- package/dist-server/harness/session/errors.d.ts +4 -0
- package/dist-server/harness/session/errors.js +7 -0
- package/dist-server/harness/session/gate.d.ts +11 -0
- package/dist-server/harness/session/gate.js +13 -0
- package/dist-server/harness/session/index.d.ts +5 -0
- package/dist-server/harness/session/index.js +3 -0
- package/dist-server/harness/session/run.d.ts +60 -0
- package/dist-server/harness/session/run.js +333 -0
- package/dist-server/harness/session/types.d.ts +29 -0
- package/dist-server/harness/session/types.js +1 -0
- package/dist-server/harness/setup/codex-events.d.ts +12 -0
- package/dist-server/harness/setup/codex-events.js +108 -0
- package/dist-server/harness/setup/runner.d.ts +497 -0
- package/dist-server/harness/setup/runner.js +1314 -0
- package/dist-server/harness/tools/errors.d.ts +107 -0
- package/dist-server/harness/tools/errors.js +141 -0
- package/dist-server/harness/tools/index.d.ts +13 -0
- package/dist-server/harness/tools/index.js +8 -0
- package/dist-server/harness/tools/mcp.d.ts +26 -0
- package/dist-server/harness/tools/mcp.js +15 -0
- package/dist-server/harness/tools/native.d.ts +179 -0
- package/dist-server/harness/tools/native.js +322 -0
- package/dist-server/harness/tools/parse.d.ts +14 -0
- package/dist-server/harness/tools/parse.js +18 -0
- package/dist-server/harness/tools/resolve-wren-binary.d.ts +12 -0
- package/dist-server/harness/tools/resolve-wren-binary.js +21 -0
- package/dist-server/harness/tools/resolve.d.ts +51 -0
- package/dist-server/harness/tools/resolve.js +79 -0
- package/dist-server/harness/tools/setup-native.d.ts +84 -0
- package/dist-server/harness/tools/setup-native.js +353 -0
- package/dist-server/harness/tools/wiring.d.ts +10 -0
- package/dist-server/harness/tools/wiring.js +16 -0
- package/dist-server/providers/setup.provider.yaml +31 -0
- package/dist-server/providers/wren.provider.yaml +33 -0
- package/dist-server/server/adopt.d.ts +87 -0
- package/dist-server/server/adopt.js +263 -0
- package/dist-server/server/app.d.ts +6 -0
- package/dist-server/server/app.js +2521 -0
- package/dist-server/server/auth-choice.d.ts +16 -0
- package/dist-server/server/auth-choice.js +36 -0
- package/dist-server/server/bin.d.ts +2 -0
- package/dist-server/server/bin.js +601 -0
- package/dist-server/server/bootstrap-project-binding.d.ts +31 -0
- package/dist-server/server/bootstrap-project-binding.js +71 -0
- package/dist-server/server/clarify.d.ts +10 -0
- package/dist-server/server/clarify.js +17 -0
- package/dist-server/server/codex-setup-mcp.d.ts +2 -0
- package/dist-server/server/codex-setup-mcp.js +147 -0
- package/dist-server/server/compose.d.ts +89 -0
- package/dist-server/server/compose.js +271 -0
- package/dist-server/server/conn-config.d.ts +30 -0
- package/dist-server/server/conn-config.js +253 -0
- package/dist-server/server/context-files.d.ts +12 -0
- package/dist-server/server/context-files.js +117 -0
- package/dist-server/server/context-map.d.ts +30 -0
- package/dist-server/server/context-map.js +95 -0
- package/dist-server/server/context-source.d.ts +68 -0
- package/dist-server/server/context-source.js +85 -0
- package/dist-server/server/db.d.ts +862 -0
- package/dist-server/server/db.js +1839 -0
- package/dist-server/server/enrichment-runner.d.ts +184 -0
- package/dist-server/server/enrichment-runner.js +213 -0
- package/dist-server/server/enrichment-verify.d.ts +73 -0
- package/dist-server/server/enrichment-verify.js +290 -0
- package/dist-server/server/enrichment.d.ts +232 -0
- package/dist-server/server/enrichment.js +199 -0
- package/dist-server/server/env-detect.d.ts +8 -0
- package/dist-server/server/env-detect.js +17 -0
- package/dist-server/server/env-field-hints.d.ts +46 -0
- package/dist-server/server/env-field-hints.js +48 -0
- package/dist-server/server/fold.d.ts +133 -0
- package/dist-server/server/fold.js +407 -0
- package/dist-server/server/harness-profile-sources.d.ts +3 -0
- package/dist-server/server/harness-profile-sources.js +24 -0
- package/dist-server/server/harness.d.ts +19 -0
- package/dist-server/server/harness.js +439 -0
- package/dist-server/server/impact.d.ts +18 -0
- package/dist-server/server/impact.js +85 -0
- package/dist-server/server/interactive-terminal.d.ts +175 -0
- package/dist-server/server/interactive-terminal.js +336 -0
- package/dist-server/server/native-artifacts.d.ts +1004 -0
- package/dist-server/server/native-artifacts.js +564 -0
- package/dist-server/server/native-dispatch-registry.d.ts +42 -0
- package/dist-server/server/native-dispatch-registry.js +45 -0
- package/dist-server/server/native-producer-fence.d.ts +14 -0
- package/dist-server/server/native-producer-fence.js +48 -0
- package/dist-server/server/native-session-resume.d.ts +6 -0
- package/dist-server/server/native-session-resume.js +103 -0
- package/dist-server/server/native-session-workspace.d.ts +40 -0
- package/dist-server/server/native-session-workspace.js +175 -0
- package/dist-server/server/native-sessions.d.ts +374 -0
- package/dist-server/server/native-sessions.js +1517 -0
- package/dist-server/server/native-wren-runtime.d.ts +20 -0
- package/dist-server/server/native-wren-runtime.js +259 -0
- package/dist-server/server/node-pty-host.d.ts +31 -0
- package/dist-server/server/node-pty-host.js +100 -0
- package/dist-server/server/route-intent.d.ts +40 -0
- package/dist-server/server/route-intent.js +23 -0
- package/dist-server/server/runtime-binding.d.ts +52 -0
- package/dist-server/server/runtime-binding.js +258 -0
- package/dist-server/server/source-catalog.d.ts +84 -0
- package/dist-server/server/source-catalog.js +221 -0
- package/dist-server/server/source-driver.d.ts +35 -0
- package/dist-server/server/source-driver.js +124 -0
- package/dist-server/server/spa.d.ts +2 -0
- package/dist-server/server/spa.js +51 -0
- package/dist-server/server/subscription-model-catalog.d.ts +27 -0
- package/dist-server/server/subscription-model-catalog.js +203 -0
- package/dist-server/server/turn.d.ts +249 -0
- package/dist-server/server/turn.js +1074 -0
- package/dist-server/server/wire-types.d.ts +799 -0
- package/dist-server/server/wire-types.js +1 -0
- package/dist-server/server/wren-home.d.ts +19 -0
- package/dist-server/server/wren-home.js +33 -0
- package/dist-server/server/wren-profiles.d.ts +43 -0
- package/dist-server/server/wren-profiles.js +123 -0
- package/package.json +93 -0
- package/profiles/genbi-default/context/binding.yml +9 -0
- package/profiles/genbi-default/ir.golden.json +506 -0
- package/profiles/genbi-default/profile.yml +16 -0
- package/profiles/genbi-enrich-context/components/apply_enrichment/component.yml +42 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/component.yml +30 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/steps/draft.md +34 -0
- package/profiles/genbi-enrich-context/components/inspect_context/component.yml +32 -0
- package/profiles/genbi-enrich-context/components/inspect_context/steps/inspect.md +13 -0
- package/profiles/genbi-enrich-context/context/binding.yml +4 -0
- package/profiles/genbi-enrich-context/ir.golden.json +302 -0
- package/profiles/genbi-enrich-context/profile.yml +15 -0
- package/profiles/genbi-monitor/context/binding.yml +7 -0
- package/profiles/genbi-monitor/ir.golden.json +188 -0
- package/profiles/genbi-monitor/profile.yml +27 -0
- package/profiles/genbi-setup/components/build_context/component.yml +44 -0
- package/profiles/genbi-setup/components/build_context/steps/build.md +14 -0
- package/profiles/genbi-setup/components/connect_source/component.yml +51 -0
- package/profiles/genbi-setup/components/connect_source/steps/connect.md +23 -0
- package/profiles/genbi-setup/context/binding.yml +8 -0
- package/profiles/genbi-setup/ir.golden.json +140 -0
- package/profiles/genbi-setup/profile.yml +18 -0
- package/profiles/genbi-setup/raw/schema.json +5 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface ConnConfig {
|
|
2
|
+
readonly datasource?: string;
|
|
3
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
4
|
+
}
|
|
5
|
+
export interface ConnectionDetail {
|
|
6
|
+
readonly type: string;
|
|
7
|
+
readonly location: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function loadConnConfig(projectDir: string): ConnConfig | undefined;
|
|
10
|
+
export interface ProjectManifest {
|
|
11
|
+
/** `wren_project.yml`'s `data_source:` — always present on a real wren project; this is the connection panel's `type`. */
|
|
12
|
+
readonly dataSource?: string;
|
|
13
|
+
/** `wren_project.yml`'s `profile:` pin, if the project has bound one — the key into `~/.wren/profiles.yml`'s `profiles:` map. */
|
|
14
|
+
readonly profile?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Reads `<projectDir>/wren_project.yml`'s persistent `data_source:`/`profile:` scalars. Unlike `conn.yml`, this file always exists on a bound wren project. Returns `undefined` only when the manifest itself is missing/unreadable — never throws. */
|
|
17
|
+
export declare function loadProjectManifest(projectDir: string): ProjectManifest | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves the `(datasource, fields)` pair `describeConnection` needs, per
|
|
20
|
+
* the precedence documented in this module's doc comment: `conn.yml` first
|
|
21
|
+
* if it happens to exist and declares a `datasource:`, otherwise
|
|
22
|
+
* `wren_project.yml`'s `data_source:` for `type` plus its pinned `profile:`
|
|
23
|
+
* (looked up in `~/.wren/profiles.yml`) for the location `fields`. A project
|
|
24
|
+
* with no pin still reports its real `data_source`, with empty fields (→
|
|
25
|
+
* `describeConnection` renders an honest "—" location) — it never
|
|
26
|
+
* substitutes the profiles store's global `active:` profile.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveConnectionSource(projectDir: string): ConnConfig;
|
|
29
|
+
/** Maps a resolved `(datasource, fields)` pair to the connection panel's `type`/`location` — never fabricates a label and never surfaces a secret field. */
|
|
30
|
+
export declare function describeConnection(datasource: string | undefined, fields: Readonly<Record<string, string>>): ConnectionDetail;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the REAL datasource + connection fields behind the Harness page's
|
|
3
|
+
* connection panel — see `buildConnection` in `server/harness.ts` — and
|
|
4
|
+
* describes them into a safe, non-secret display shape.
|
|
5
|
+
*
|
|
6
|
+
* `<project>/conn.yml` is NOT a persistent part of a wren project: the wren
|
|
7
|
+
* CLI's onboarding wizard writes a *temporary* scratch file inside the
|
|
8
|
+
* project directory (under a name deliberately not `conn.yml`, e.g.
|
|
9
|
+
* `conn.profile.yml`) and feeds it to `wren profile add --from-file`, then
|
|
10
|
+
* deletes it once the profile is created — so it never leaves a `conn.yml`
|
|
11
|
+
* in the project directory itself. So `loadConnConfig` below only ever finds
|
|
12
|
+
* something to read on hand-authored fixtures (or a project laid out that
|
|
13
|
+
* way on purpose) — never on a project the CLI actually onboarded. The two
|
|
14
|
+
* files that ARE always there:
|
|
15
|
+
* - `<project>/wren_project.yml` — its `data_source:` field is the real,
|
|
16
|
+
* always-present datasource, and its `profile:` field (if set) pins which
|
|
17
|
+
* stored profile the project actually connects through.
|
|
18
|
+
* - `~/.wren/profiles.yml` (or `$WREN_HOME/profiles.yml`) — the pinned
|
|
19
|
+
* profile's own field map (duckdb `url`, DB `host`/`port`/`database`, …),
|
|
20
|
+
* read via `server/wren-profiles.ts`'s shared parser.
|
|
21
|
+
* `resolveConnectionSource` is the entry point every caller should use: it
|
|
22
|
+
* prefers `conn.yml` when one happens to exist (kept only so a fixture/hand
|
|
23
|
+
* laid out project isn't silently ignored), and otherwise resolves through
|
|
24
|
+
* the manifest + profiles store above — WITHOUT ever falling back to the
|
|
25
|
+
* profiles store's global `active:` profile when the project has no pin of
|
|
26
|
+
* its own, since a project must never display a connection it isn't bound
|
|
27
|
+
* to.
|
|
28
|
+
*
|
|
29
|
+
* `conn.yml`, when present, is a flat `key: value` file (no nesting) — e.g.
|
|
30
|
+
* ```
|
|
31
|
+
* datasource: duckdb
|
|
32
|
+
* url: ${DUCKDB_URL}
|
|
33
|
+
* format: ${DUCKDB_FORMAT}
|
|
34
|
+
* ```
|
|
35
|
+
* so a small line-based parser is enough here; pulling in a full YAML
|
|
36
|
+
* library for one flat file would be overkill. `wren_project.yml` only ever
|
|
37
|
+
* needs two scalar fields read out of it, so it gets the same small-parser
|
|
38
|
+
* treatment (mirrors `server/adopt.ts`'s own `readYamlScalarField`).
|
|
39
|
+
*
|
|
40
|
+
* SECURITY: `describeConnection` only ever reads from a fixed ALLOWLIST of
|
|
41
|
+
* non-secret field names (host/port/database/project id/…) when building the
|
|
42
|
+
* displayed location string. It never touches or forwards arbitrary
|
|
43
|
+
* `conn.yml`/`.env`/profile keys, so a field named
|
|
44
|
+
* `password`/`user`/`token`/`credentials`/`private_key` etc. can never reach
|
|
45
|
+
* the DTO even if present in the parsed config — there is no denylist to
|
|
46
|
+
* keep in sync as new datasources are added.
|
|
47
|
+
*/
|
|
48
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
49
|
+
import path from "node:path";
|
|
50
|
+
import { loadProfileStore } from "./wren-profiles.js";
|
|
51
|
+
function stripQuotes(value) {
|
|
52
|
+
if (value.length >= 2) {
|
|
53
|
+
const first = value[0];
|
|
54
|
+
const last = value[value.length - 1];
|
|
55
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'"))
|
|
56
|
+
return value.slice(1, -1);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
/** Parses a flat `key: value` YAML file — one mapping per line, no nested structures. */
|
|
61
|
+
function parseFlatYaml(text) {
|
|
62
|
+
const result = {};
|
|
63
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
64
|
+
const line = rawLine.trim();
|
|
65
|
+
if (!line || line.startsWith("#"))
|
|
66
|
+
continue;
|
|
67
|
+
const idx = line.indexOf(":");
|
|
68
|
+
if (idx === -1)
|
|
69
|
+
continue;
|
|
70
|
+
const key = line.slice(0, idx).trim();
|
|
71
|
+
if (!key)
|
|
72
|
+
continue;
|
|
73
|
+
result[key] = stripQuotes(line.slice(idx + 1).trim());
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
/** Parses a `.env` file — `KEY=value` per line, optional `export ` prefix, `#` comments. */
|
|
78
|
+
function parseDotEnv(text) {
|
|
79
|
+
const result = {};
|
|
80
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
81
|
+
const line = rawLine.trim();
|
|
82
|
+
if (!line || line.startsWith("#"))
|
|
83
|
+
continue;
|
|
84
|
+
const withoutExport = line.startsWith("export ") ? line.slice("export ".length) : line;
|
|
85
|
+
const idx = withoutExport.indexOf("=");
|
|
86
|
+
if (idx === -1)
|
|
87
|
+
continue;
|
|
88
|
+
const key = withoutExport.slice(0, idx).trim();
|
|
89
|
+
if (!key)
|
|
90
|
+
continue;
|
|
91
|
+
result[key] = stripQuotes(withoutExport.slice(idx + 1).trim());
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
const TEMPLATE_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
96
|
+
/** Resolves `${VAR}` references via `lookup`; returns `undefined` (rather than a literal, broken `${VAR}`) if any reference can't be resolved. */
|
|
97
|
+
function resolveTemplate(value, lookup) {
|
|
98
|
+
let unresolved = false;
|
|
99
|
+
const resolved = value.replace(TEMPLATE_RE, (_match, name) => {
|
|
100
|
+
const resolvedValue = lookup(name);
|
|
101
|
+
if (resolvedValue === undefined) {
|
|
102
|
+
unresolved = true;
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
return resolvedValue;
|
|
106
|
+
});
|
|
107
|
+
return unresolved ? undefined : resolved;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Reads `<projectDir>/conn.yml`, resolving any `${VAR}` values against
|
|
111
|
+
* `<projectDir>/.env` (a real environment variable of the same name, if set,
|
|
112
|
+
* wins over the project's `.env` — mirroring the wren CLI's own dotenv
|
|
113
|
+
* loading, so a shell-exported override still applies). Returns `undefined`
|
|
114
|
+
* when there's no `conn.yml` to read (unbound project, or a project laid out
|
|
115
|
+
* without one) rather than throwing — callers fall back to an honest "—".
|
|
116
|
+
*/
|
|
117
|
+
/**
|
|
118
|
+
* Resolves `${VAR}` references against a real environment variable, else the
|
|
119
|
+
* project's own `.env`. Shared by both connection sources: a stored profile
|
|
120
|
+
* holds the same `${VAR}` references `conn.yml` does — that is how wren keeps
|
|
121
|
+
* credentials out of `~/.wren/profiles.yml` — so a display that skipped this
|
|
122
|
+
* step showed the reference instead of the value.
|
|
123
|
+
*/
|
|
124
|
+
function envLookup(projectDir) {
|
|
125
|
+
const dotenvPath = path.join(projectDir, ".env");
|
|
126
|
+
let dotenv = {};
|
|
127
|
+
if (existsSync(dotenvPath)) {
|
|
128
|
+
try {
|
|
129
|
+
dotenv = parseDotEnv(readFileSync(dotenvPath, "utf-8"));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
dotenv = {};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return (name) => process.env[name] ?? dotenv[name];
|
|
136
|
+
}
|
|
137
|
+
export function loadConnConfig(projectDir) {
|
|
138
|
+
const connPath = path.join(projectDir, "conn.yml");
|
|
139
|
+
if (!existsSync(connPath))
|
|
140
|
+
return undefined;
|
|
141
|
+
let raw;
|
|
142
|
+
try {
|
|
143
|
+
raw = parseFlatYaml(readFileSync(connPath, "utf-8"));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const lookup = envLookup(projectDir);
|
|
149
|
+
const fields = {};
|
|
150
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
151
|
+
if (key === "datasource")
|
|
152
|
+
continue;
|
|
153
|
+
const resolved = resolveTemplate(value, lookup);
|
|
154
|
+
if (resolved !== undefined)
|
|
155
|
+
fields[key] = resolved;
|
|
156
|
+
}
|
|
157
|
+
return raw.datasource !== undefined ? { datasource: raw.datasource, fields } : { fields };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Matches a `wren_project.yml` top-level `field: value` scalar line — a
|
|
161
|
+
* second small instance of `server/adopt.ts`'s own `readYamlScalarField`
|
|
162
|
+
* rather than an import from it: this module only ever needs the same two
|
|
163
|
+
* fields (`data_source`, `profile`), and adopt.ts's function isn't exported
|
|
164
|
+
* for reuse, so a four-line regex duplicated here is cheaper than adding a
|
|
165
|
+
* cross-module export just for it.
|
|
166
|
+
*/
|
|
167
|
+
function readManifestScalar(content, field) {
|
|
168
|
+
const re = new RegExp(`^${field}:\\s*(.+?)\\s*$`, "m");
|
|
169
|
+
const match = re.exec(content);
|
|
170
|
+
return match ? stripQuotes(match[1].trim()) : undefined;
|
|
171
|
+
}
|
|
172
|
+
/** Reads `<projectDir>/wren_project.yml`'s persistent `data_source:`/`profile:` scalars. Unlike `conn.yml`, this file always exists on a bound wren project. Returns `undefined` only when the manifest itself is missing/unreadable — never throws. */
|
|
173
|
+
export function loadProjectManifest(projectDir) {
|
|
174
|
+
const manifestPath = path.join(projectDir, "wren_project.yml");
|
|
175
|
+
if (!existsSync(manifestPath))
|
|
176
|
+
return undefined;
|
|
177
|
+
try {
|
|
178
|
+
const content = readFileSync(manifestPath, "utf-8");
|
|
179
|
+
const dataSource = readManifestScalar(content, "data_source");
|
|
180
|
+
const profile = readManifestScalar(content, "profile");
|
|
181
|
+
return { ...(dataSource !== undefined ? { dataSource } : {}), ...(profile !== undefined ? { profile } : {}) };
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Resolves the `(datasource, fields)` pair `describeConnection` needs, per
|
|
189
|
+
* the precedence documented in this module's doc comment: `conn.yml` first
|
|
190
|
+
* if it happens to exist and declares a `datasource:`, otherwise
|
|
191
|
+
* `wren_project.yml`'s `data_source:` for `type` plus its pinned `profile:`
|
|
192
|
+
* (looked up in `~/.wren/profiles.yml`) for the location `fields`. A project
|
|
193
|
+
* with no pin still reports its real `data_source`, with empty fields (→
|
|
194
|
+
* `describeConnection` renders an honest "—" location) — it never
|
|
195
|
+
* substitutes the profiles store's global `active:` profile.
|
|
196
|
+
*/
|
|
197
|
+
export function resolveConnectionSource(projectDir) {
|
|
198
|
+
const connConfig = loadConnConfig(projectDir);
|
|
199
|
+
if (connConfig?.datasource !== undefined)
|
|
200
|
+
return connConfig;
|
|
201
|
+
const manifest = loadProjectManifest(projectDir);
|
|
202
|
+
if (!manifest?.dataSource)
|
|
203
|
+
return { fields: {} };
|
|
204
|
+
if (!manifest.profile)
|
|
205
|
+
return { datasource: manifest.dataSource, fields: {} };
|
|
206
|
+
const profileFields = loadProfileStore().profiles.get(manifest.profile);
|
|
207
|
+
if (!profileFields)
|
|
208
|
+
return { datasource: manifest.dataSource, fields: {} };
|
|
209
|
+
// A stored profile's values are `${VAR}` references into the project's
|
|
210
|
+
// `.env` — that is how wren keeps credentials out of `~/.wren/profiles.yml`.
|
|
211
|
+
// They need the same resolution `conn.yml` already gets; without it the
|
|
212
|
+
// connection panel rendered the literal `${BIGQUERY_PROJECT_ID}`.
|
|
213
|
+
const lookup = envLookup(projectDir);
|
|
214
|
+
const fields = {};
|
|
215
|
+
for (const [key, value] of profileFields) {
|
|
216
|
+
if (key === "datasource")
|
|
217
|
+
continue; // the manifest's data_source is authoritative for `type`; profiles.yml's own field is redundant and excluded from `fields`.
|
|
218
|
+
const resolved = resolveTemplate(value, lookup);
|
|
219
|
+
// An unresolved reference is dropped rather than shown: the panel's job is
|
|
220
|
+
// to say where this project connects, and `${SOMETHING}` answers that no
|
|
221
|
+
// better than showing nothing.
|
|
222
|
+
if (resolved !== undefined)
|
|
223
|
+
fields[key] = resolved;
|
|
224
|
+
}
|
|
225
|
+
return { datasource: manifest.dataSource, fields };
|
|
226
|
+
}
|
|
227
|
+
/** Filesystem-backed datasources — `url` IS the connection location (a local path, or a bucket/object URL with no embedded credentials in these connection info shapes). */
|
|
228
|
+
const FILE_DATASOURCES = new Set(["duckdb", "local_file", "gcs_file", "minio_file", "s3_file"]);
|
|
229
|
+
/**
|
|
230
|
+
* ALLOWLIST of non-secret field names used to build the displayed location
|
|
231
|
+
* string for DB-type datasources, in display order. Deliberately excludes
|
|
232
|
+
* every credential-shaped field (password, user, credentials, token,
|
|
233
|
+
* private_key, access_key/secret_key, client_id/client_secret, dsn, kwargs,
|
|
234
|
+
* …) — those are simply never read here, regardless of datasource.
|
|
235
|
+
*/
|
|
236
|
+
const LOCATION_FIELD_ORDER = ["database", "project_id", "dataset_id", "account", "schema", "warehouse", "region_name", "schema_name"];
|
|
237
|
+
/** Maps a resolved `(datasource, fields)` pair to the connection panel's `type`/`location` — never fabricates a label and never surfaces a secret field. */
|
|
238
|
+
export function describeConnection(datasource, fields) {
|
|
239
|
+
if (!datasource)
|
|
240
|
+
return { type: "—", location: "—" };
|
|
241
|
+
if (FILE_DATASOURCES.has(datasource)) {
|
|
242
|
+
return { type: datasource, location: fields.url ?? "—" };
|
|
243
|
+
}
|
|
244
|
+
const segments = [];
|
|
245
|
+
if (fields.host)
|
|
246
|
+
segments.push(fields.port ? `${fields.host}:${fields.port}` : fields.host);
|
|
247
|
+
for (const key of LOCATION_FIELD_ORDER) {
|
|
248
|
+
const value = fields[key];
|
|
249
|
+
if (value)
|
|
250
|
+
segments.push(value);
|
|
251
|
+
}
|
|
252
|
+
return { type: datasource, location: segments.length > 0 ? segments.join("/") : "—" };
|
|
253
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WrenContextShow } from "./context-source.js";
|
|
2
|
+
import type { ContextFileNode, KnowledgeStatus } from "./wire-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* `instructionsPresent` from the real presence of at least one authored
|
|
5
|
+
* `knowledge/rules/*.md` file (as opposed to the placeholder-only
|
|
6
|
+
* subdirectories, e.g. `caveats/`/`glossary/`). `verifiedPairCount` is always
|
|
7
|
+
* 0 — there is no live Q-SQL verified-pair source over a real project yet
|
|
8
|
+
* (see `server/impact.ts`'s doc comment).
|
|
9
|
+
*/
|
|
10
|
+
export declare function computeKnowledgeStatus(projectDir: string): KnowledgeStatus;
|
|
11
|
+
/** Builds the whole `GET /api/context/files` tree — one top-level group per entity kind, skipping any group that ends up with no readable files rather than emitting an empty section. */
|
|
12
|
+
export declare function buildContextFileTree(projectDir: string, contextShow: WrenContextShow): ContextFileNode[];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the Context page's file tree by reading the
|
|
3
|
+
* REAL bound project directory (model/cube/knowledge files, relationships.yml,
|
|
4
|
+
* wren_project.yml) rather than a seeded fixture. Each leaf's `content` is the
|
|
5
|
+
* actual file text on disk, so `FileViewer.tsx` renders real project source.
|
|
6
|
+
*
|
|
7
|
+
* Model/relationship/cube NAMES come from an already-loaded
|
|
8
|
+
* `WrenContextShow` (see `server/context-source.ts`) rather than re-parsing
|
|
9
|
+
* YAML here — `wren context show` is the authoritative parse of that data;
|
|
10
|
+
* this module only needs it to know which per-entity files exist and how to
|
|
11
|
+
* label them, then reads each file's raw bytes straight from disk.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
function readFileSafe(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync(filePath, "utf-8");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function buildModelNodes(projectDir, contextShow) {
|
|
24
|
+
return contextShow.models.flatMap((model) => {
|
|
25
|
+
const relPath = path.join("models", model.name, "metadata.yml");
|
|
26
|
+
const content = readFileSafe(path.join(projectDir, relPath));
|
|
27
|
+
if (content === undefined)
|
|
28
|
+
return [];
|
|
29
|
+
return [{ key: `file-model-${model.name}`, title: `${model.name}/metadata.yml`, kind: "model", path: relPath, entityKey: model.name, content }];
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** `relationships.yml` is a single file holding every relationship — one leaf per relationship, all sharing that same real file text, distinguished by `entityKey`. */
|
|
33
|
+
function buildRelationshipNodes(projectDir, contextShow) {
|
|
34
|
+
const relPath = "relationships.yml";
|
|
35
|
+
const content = readFileSafe(path.join(projectDir, relPath));
|
|
36
|
+
if (content === undefined)
|
|
37
|
+
return [];
|
|
38
|
+
return contextShow.relationships.map((rel) => ({
|
|
39
|
+
key: `file-relationship-${rel.name}`,
|
|
40
|
+
title: rel.name,
|
|
41
|
+
kind: "relationship",
|
|
42
|
+
path: relPath,
|
|
43
|
+
entityKey: rel.name,
|
|
44
|
+
content,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
function buildCubeNodes(projectDir, contextShow) {
|
|
48
|
+
return contextShow.cubes.flatMap((cube) => {
|
|
49
|
+
const relPath = path.join("cubes", cube.name, "metadata.yml");
|
|
50
|
+
const content = readFileSafe(path.join(projectDir, relPath));
|
|
51
|
+
if (content === undefined)
|
|
52
|
+
return [];
|
|
53
|
+
return [{ key: `file-cube-${cube.name}`, title: `${cube.name}/metadata.yml`, kind: "cube", path: relPath, entityKey: cube.name, content }];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** `knowledge/rules/*.md` are the only knowledge files with real authored content today (`caveats/`, `glossary/`, `metrics/`, `sql/` are placeholders) — plus `knowledge/knowledge.yml` when present. */
|
|
57
|
+
function buildKnowledgeNodes(projectDir) {
|
|
58
|
+
const rulesDir = path.join(projectDir, "knowledge", "rules");
|
|
59
|
+
const nodes = [];
|
|
60
|
+
if (existsSync(rulesDir)) {
|
|
61
|
+
const entries = readdirSync(rulesDir)
|
|
62
|
+
.filter((name) => name.endsWith(".md"))
|
|
63
|
+
.sort();
|
|
64
|
+
for (const name of entries) {
|
|
65
|
+
const content = readFileSafe(path.join(rulesDir, name));
|
|
66
|
+
if (content === undefined)
|
|
67
|
+
continue;
|
|
68
|
+
const relPath = path.join("knowledge", "rules", name);
|
|
69
|
+
const entityKey = name.replace(/\.md$/, "");
|
|
70
|
+
nodes.push({ key: `file-knowledge-rule-${entityKey}`, title: name, kind: "knowledge", path: relPath, entityKey, content });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const knowledgeYamlPath = path.join(projectDir, "knowledge", "knowledge.yml");
|
|
74
|
+
const knowledgeYamlContent = readFileSafe(knowledgeYamlPath);
|
|
75
|
+
if (knowledgeYamlContent !== undefined) {
|
|
76
|
+
nodes.push({ key: "file-knowledge-config", title: "knowledge.yml", kind: "knowledge", path: path.join("knowledge", "knowledge.yml"), entityKey: "knowledge-config", content: knowledgeYamlContent });
|
|
77
|
+
}
|
|
78
|
+
return nodes;
|
|
79
|
+
}
|
|
80
|
+
function buildProjectNode(projectDir) {
|
|
81
|
+
const content = readFileSafe(path.join(projectDir, "wren_project.yml"));
|
|
82
|
+
if (content === undefined)
|
|
83
|
+
return [];
|
|
84
|
+
return [{ key: "file-wren-project", title: "wren_project.yml", path: "wren_project.yml", content }];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* `instructionsPresent` from the real presence of at least one authored
|
|
88
|
+
* `knowledge/rules/*.md` file (as opposed to the placeholder-only
|
|
89
|
+
* subdirectories, e.g. `caveats/`/`glossary/`). `verifiedPairCount` is always
|
|
90
|
+
* 0 — there is no live Q-SQL verified-pair source over a real project yet
|
|
91
|
+
* (see `server/impact.ts`'s doc comment).
|
|
92
|
+
*/
|
|
93
|
+
export function computeKnowledgeStatus(projectDir) {
|
|
94
|
+
const rulesDir = path.join(projectDir, "knowledge", "rules");
|
|
95
|
+
const instructionsPresent = existsSync(rulesDir) && readdirSync(rulesDir).some((name) => name.endsWith(".md"));
|
|
96
|
+
return { instructionsPresent, verifiedPairCount: 0 };
|
|
97
|
+
}
|
|
98
|
+
/** Builds the whole `GET /api/context/files` tree — one top-level group per entity kind, skipping any group that ends up with no readable files rather than emitting an empty section. */
|
|
99
|
+
export function buildContextFileTree(projectDir, contextShow) {
|
|
100
|
+
const groups = [];
|
|
101
|
+
const models = buildModelNodes(projectDir, contextShow);
|
|
102
|
+
if (models.length > 0)
|
|
103
|
+
groups.push({ key: "models", title: "Models", children: models });
|
|
104
|
+
const relationships = buildRelationshipNodes(projectDir, contextShow);
|
|
105
|
+
if (relationships.length > 0)
|
|
106
|
+
groups.push({ key: "relationships", title: "Relationships", children: relationships });
|
|
107
|
+
const cubes = buildCubeNodes(projectDir, contextShow);
|
|
108
|
+
if (cubes.length > 0)
|
|
109
|
+
groups.push({ key: "cubes", title: "Cubes", children: cubes });
|
|
110
|
+
const knowledge = buildKnowledgeNodes(projectDir);
|
|
111
|
+
if (knowledge.length > 0)
|
|
112
|
+
groups.push({ key: "knowledge", title: "Knowledge", children: knowledge });
|
|
113
|
+
const project = buildProjectNode(projectDir);
|
|
114
|
+
if (project.length > 0)
|
|
115
|
+
groups.push({ key: "project", title: "Project", children: project });
|
|
116
|
+
return groups;
|
|
117
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure remap from `wren context show`'s output
|
|
3
|
+
* (`server/context-source.ts`) to the frozen UI wire contract
|
|
4
|
+
* (`server/wire-types.ts`). No I/O here; `server/app.ts`'s context routes are
|
|
5
|
+
* the only caller.
|
|
6
|
+
*
|
|
7
|
+
* `wren context show` carries no `key: pk/fk` on columns, no additivity flag
|
|
8
|
+
* on measures, and no ER layout positions — those are inferred/omitted
|
|
9
|
+
* heuristics, not authoritative facts from the semantic layer. Keep the
|
|
10
|
+
* inference simple and readable rather than over-fitting to one project's
|
|
11
|
+
* naming.
|
|
12
|
+
*/
|
|
13
|
+
import type { WrenContextCube, WrenContextModel, WrenContextRelationship, WrenContextShow } from "./context-source.js";
|
|
14
|
+
import type { ContextOverview, KnowledgeStatus, MeasureAdditivity, SemanticMeasure, SemanticModel, SemanticRelationship } from "./wire-types.js";
|
|
15
|
+
/** Maps `models[]` to `SemanticModel[]`, omitting `position` (no ER layout in `wren context show` — the UI's `ErDiagram` must lay itself out when position is absent). */
|
|
16
|
+
export declare function mapModelsToWire(models: readonly WrenContextModel[], relationships: readonly WrenContextRelationship[]): SemanticModel[];
|
|
17
|
+
/** Maps `relationships[]` (unordered `models:[a,b]` pair) to `SemanticRelationship[]` (`fromModel`/`toModel`). The wire type has no slot for the raw `condition` predicate — it's only consumed internally for fk inference above. */
|
|
18
|
+
export declare function mapRelationshipsToWire(relationships: readonly WrenContextRelationship[]): SemanticRelationship[];
|
|
19
|
+
/**
|
|
20
|
+
* `SUM`/`COUNT` aggregates are additive (safely re-summable across any
|
|
21
|
+
* dimension) — UNLESS the expression is itself a ratio of two such
|
|
22
|
+
* aggregates (e.g. `"SUM(revenue) / SUM(cost)"`), which is never additive
|
|
23
|
+
* regardless of what its parts are made of. `AVG` and anything else default
|
|
24
|
+
* to non-additive — the safer assumption when additivity can't be confirmed
|
|
25
|
+
* from the expression text alone.
|
|
26
|
+
*/
|
|
27
|
+
export declare function inferAdditivity(expression: string): MeasureAdditivity;
|
|
28
|
+
/** Maps `cubes[]` to `SemanticMeasure[]` — one entry per measure (a cube can declare several), keyed `"<cube>.<measure>"` so measures with the same name in different cubes don't collide. */
|
|
29
|
+
export declare function mapCubesToMeasures(cubes: readonly WrenContextCube[]): SemanticMeasure[];
|
|
30
|
+
export declare function mapContextShowToOverview(contextShow: WrenContextShow, projectName: string, projectPath: string, knowledge: KnowledgeStatus): ContextOverview;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const JOIN_TYPE_MAP = {
|
|
2
|
+
ONE_TO_ONE: "one-to-one",
|
|
3
|
+
ONE_TO_MANY: "one-to-many",
|
|
4
|
+
MANY_TO_ONE: "many-to-one",
|
|
5
|
+
MANY_TO_MANY: "many-to-many",
|
|
6
|
+
};
|
|
7
|
+
/** `wren`'s `joinType` is an upper-snake enum (`"MANY_TO_ONE"`, ...) — falls back to `"one-to-many"` for anything unrecognized rather than throwing, since this is display-only. */
|
|
8
|
+
function mapJoinType(joinType) {
|
|
9
|
+
return JOIN_TYPE_MAP[joinType] ?? "one-to-many";
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A column is inferred `pk` when it matches the model's declared
|
|
13
|
+
* `primaryKey`, or `fk` when some relationship's `condition` string
|
|
14
|
+
* references `<model.name>.<column.name>` on either side of the join
|
|
15
|
+
* predicate (e.g. `"orders.customer_id = customers.id"` marks
|
|
16
|
+
* `orders.customer_id` as an fk). Both are heuristics over plain-text data —
|
|
17
|
+
* not something `wren context show` asserts directly.
|
|
18
|
+
*/
|
|
19
|
+
function escapeRegExp(value) {
|
|
20
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
21
|
+
}
|
|
22
|
+
function inferColumnKey(modelName, columnName, primaryKey, relationships) {
|
|
23
|
+
if (primaryKey !== undefined && columnName === primaryKey)
|
|
24
|
+
return "pk";
|
|
25
|
+
// Match `<model>.<column>` as a whole identifier reference, not a loose
|
|
26
|
+
// substring: the boundaries stop a model whose name is a suffix of another
|
|
27
|
+
// (e.g. `old_customer.id` must NOT satisfy a lookup for `customer.id`) or a
|
|
28
|
+
// column that is a prefix of another (`id` vs `identifier`) from matching.
|
|
29
|
+
const ref = new RegExp(`(?<![A-Za-z0-9_])${escapeRegExp(modelName)}\\.${escapeRegExp(columnName)}(?![A-Za-z0-9_])`);
|
|
30
|
+
if (relationships.some((rel) => ref.test(rel.condition)))
|
|
31
|
+
return "fk";
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Maps `models[]` to `SemanticModel[]`, omitting `position` (no ER layout in `wren context show` — the UI's `ErDiagram` must lay itself out when position is absent). */
|
|
35
|
+
export function mapModelsToWire(models, relationships) {
|
|
36
|
+
return models.map((model) => {
|
|
37
|
+
const columns = model.columns.map((col) => {
|
|
38
|
+
const key = inferColumnKey(model.name, col.name, model.primaryKey, relationships);
|
|
39
|
+
return key !== undefined ? { name: col.name, type: col.type, key } : { name: col.name, type: col.type };
|
|
40
|
+
});
|
|
41
|
+
return { key: model.name, name: model.name, columns };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** Maps `relationships[]` (unordered `models:[a,b]` pair) to `SemanticRelationship[]` (`fromModel`/`toModel`). The wire type has no slot for the raw `condition` predicate — it's only consumed internally for fk inference above. */
|
|
45
|
+
export function mapRelationshipsToWire(relationships) {
|
|
46
|
+
return relationships.map((rel) => ({
|
|
47
|
+
key: rel.name,
|
|
48
|
+
name: rel.name,
|
|
49
|
+
fromModel: rel.models[0],
|
|
50
|
+
toModel: rel.models[1],
|
|
51
|
+
type: mapJoinType(rel.joinType),
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `SUM`/`COUNT` aggregates are additive (safely re-summable across any
|
|
56
|
+
* dimension) — UNLESS the expression is itself a ratio of two such
|
|
57
|
+
* aggregates (e.g. `"SUM(revenue) / SUM(cost)"`), which is never additive
|
|
58
|
+
* regardless of what its parts are made of. `AVG` and anything else default
|
|
59
|
+
* to non-additive — the safer assumption when additivity can't be confirmed
|
|
60
|
+
* from the expression text alone.
|
|
61
|
+
*/
|
|
62
|
+
export function inferAdditivity(expression) {
|
|
63
|
+
const trimmedUpper = expression.trim().toUpperCase();
|
|
64
|
+
if (trimmedUpper.includes("/"))
|
|
65
|
+
return "non-additive";
|
|
66
|
+
if (/^(SUM|COUNT)\s*\(/.test(trimmedUpper))
|
|
67
|
+
return "additive";
|
|
68
|
+
return "non-additive";
|
|
69
|
+
}
|
|
70
|
+
/** Maps `cubes[]` to `SemanticMeasure[]` — one entry per measure (a cube can declare several), keyed `"<cube>.<measure>"` so measures with the same name in different cubes don't collide. */
|
|
71
|
+
export function mapCubesToMeasures(cubes) {
|
|
72
|
+
const measures = [];
|
|
73
|
+
for (const cube of cubes) {
|
|
74
|
+
for (const measure of cube.measures) {
|
|
75
|
+
measures.push({
|
|
76
|
+
key: `${cube.name}.${measure.name}`,
|
|
77
|
+
name: measure.name,
|
|
78
|
+
baseModel: cube.baseObject,
|
|
79
|
+
expression: measure.expression,
|
|
80
|
+
additivity: inferAdditivity(measure.expression),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return measures;
|
|
85
|
+
}
|
|
86
|
+
export function mapContextShowToOverview(contextShow, projectName, projectPath, knowledge) {
|
|
87
|
+
return {
|
|
88
|
+
models: mapModelsToWire(contextShow.models, contextShow.relationships),
|
|
89
|
+
relationships: mapRelationshipsToWire(contextShow.relationships),
|
|
90
|
+
measures: mapCubesToMeasures(contextShow.cubes),
|
|
91
|
+
knowledge,
|
|
92
|
+
projectName,
|
|
93
|
+
projectPath,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface WrenContextColumn {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly type: string;
|
|
4
|
+
readonly properties?: {
|
|
5
|
+
readonly description?: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export interface WrenContextModel {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly properties?: {
|
|
11
|
+
readonly description?: string;
|
|
12
|
+
};
|
|
13
|
+
readonly tableReference?: unknown;
|
|
14
|
+
readonly primaryKey?: string;
|
|
15
|
+
readonly columns: readonly WrenContextColumn[];
|
|
16
|
+
}
|
|
17
|
+
/** `models` is an unordered pair — `condition` is the raw join predicate, e.g. `"orders.customer_id = customers.id"`. */
|
|
18
|
+
export interface WrenContextRelationship {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly models: readonly [string, string];
|
|
21
|
+
readonly joinType: string;
|
|
22
|
+
readonly condition: string;
|
|
23
|
+
}
|
|
24
|
+
export interface WrenContextMeasure {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly expression: string;
|
|
27
|
+
readonly type?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface WrenContextCube {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly baseObject: string;
|
|
32
|
+
readonly measures: readonly WrenContextMeasure[];
|
|
33
|
+
readonly dimensions?: readonly unknown[];
|
|
34
|
+
readonly timeDimensions?: readonly unknown[];
|
|
35
|
+
readonly properties?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
/** The full `wren context show -o json` document — a single JSON object, NOT JSONL (unlike `wren -o json` query output). */
|
|
38
|
+
export interface WrenContextShow {
|
|
39
|
+
readonly catalog?: string;
|
|
40
|
+
readonly schema?: string;
|
|
41
|
+
readonly models: readonly WrenContextModel[];
|
|
42
|
+
readonly relationships: readonly WrenContextRelationship[];
|
|
43
|
+
readonly views?: readonly unknown[];
|
|
44
|
+
readonly cubes: readonly WrenContextCube[];
|
|
45
|
+
readonly dataSource?: string;
|
|
46
|
+
readonly layoutVersion?: number;
|
|
47
|
+
}
|
|
48
|
+
export declare class WrenBinaryNotFoundError extends Error {
|
|
49
|
+
constructor(detail: string);
|
|
50
|
+
}
|
|
51
|
+
/** Covers both "project not built yet" (missing `target/mdl.json`, checked up front) and a non-zero/unparseable `wren context show` run. */
|
|
52
|
+
export declare class WrenContextShowError extends Error {
|
|
53
|
+
constructor(detail: string);
|
|
54
|
+
}
|
|
55
|
+
/** Evicts one cached project (or every entry, when called with no argument) — call after a context-changing operation (e.g. a setup "build context" turn). */
|
|
56
|
+
export declare function invalidateContextShowCache(projectDir?: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Runs `wren context show -o json` in `projectDir` and parses the single
|
|
59
|
+
* resulting JSON object. Throws {@link WrenBinaryNotFoundError} when `wren`
|
|
60
|
+
* isn't on PATH, or {@link WrenContextShowError} when the project hasn't
|
|
61
|
+
* been built (`target/mdl.json` missing — checked up front so the error names
|
|
62
|
+
* the real cause instead of a generic non-zero exit), the process exits
|
|
63
|
+
* non-zero, or stdout isn't valid JSON. Never auto-builds and never falls
|
|
64
|
+
* back to fabricated data.
|
|
65
|
+
*/
|
|
66
|
+
export declare function loadContextShow(projectDir: string, options?: {
|
|
67
|
+
readonly useCache?: boolean;
|
|
68
|
+
}): Promise<WrenContextShow>;
|