okengine 0.14.1 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -4
- package/site/content/docs/elements/ai.mdx +2 -1
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +3 -0
- package/site/content/docs/elements/flow.mdx +3 -0
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +4 -4
- package/site/content/docs/elements/store.mdx +86 -27
- package/site/content/docs/elements/vault.mdx +1 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +25 -21
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/bindings.ts +14 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/attach-host-console.test.ts +3 -2
- package/src/cli/attach-host-console.ts +3 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/extract.test.ts +409 -33
- package/src/compiler/extract.ts +318 -67
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +120 -40
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +8 -0
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
- package/src/console/ui-next/dist/assets/{cache-glyph-BanhLsEY.js → cache-glyph-F1FI122b.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
- package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
- package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
- package/src/console/ui-next/dist/assets/{observability-page-BEZDzyYh.js → observability-page-HK9-BO8z.js} +2 -2
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
- package/src/console/ui-next/dist/index.html +6 -6
- package/src/console/ui-next/src/client.ts +35 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +3 -0
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/types.ts +10 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +36 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +4 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +161 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-session.ts +81 -3
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +14 -0
- package/src/http.ts +20 -3
- package/src/index.ts +10 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +131 -14
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +49 -9
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +12 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx.ts +23 -0
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +4 -4
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/types.ts +13 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +8 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
|
@@ -17,6 +17,7 @@ oke --help # command list (also what bare `oke` prints whe
|
|
|
17
17
|
|
|
18
18
|
oke dev # watch · hot reload · Console :6533 · app :6530 · MCP :6535 · docs MCP :6536
|
|
19
19
|
# → always Docker Compose under docker/ + host Bun
|
|
20
|
+
# → regenerates flows/generated.ts when the flows tree changes
|
|
20
21
|
# → client types + domain schema push on save
|
|
21
22
|
# → Console UI hot-reloads from source (okengine checkout; no bun run build)
|
|
22
23
|
oke dev --no-db-push # opt out of auto oke db push on schema input save
|
|
@@ -48,7 +49,7 @@ oke schema generate # core + plugin stubs → .oke/schema/oke.ts (-
|
|
|
48
49
|
oke db push # domain schema.ts → live DB (dev; drizzle-kit)
|
|
49
50
|
oke db generate # versioned SQL under drizzle/ (review)
|
|
50
51
|
oke db migrate # apply migrations (explicit; never auto in prod)
|
|
51
|
-
oke db seed # this app's defineSeed (name + essential + env)
|
|
52
|
+
oke db seed # this app's defineSeed (name + essential + env) — standard CLI path
|
|
52
53
|
oke db seed --env prod --force # CI: skip prod confirmation prompt
|
|
53
54
|
oke db studio # drizzle-kit Studio (long-running)
|
|
54
55
|
oke vault set STRIPE_KEY # also: list · import .env · key rotate
|
|
@@ -112,24 +113,26 @@ not spawn a second stack. Non-TTY / CI keeps the classic help listing
|
|
|
112
113
|
`oke dev` always starts Compose (requires a running Docker daemon). Boot chrome
|
|
113
114
|
prints immediately (wordmark + Starting + profile), then streams background work
|
|
114
115
|
(compose up, vault, per-service health, AI probe). Status ● is green ready ·
|
|
115
|
-
yellow pending/loading · red error · dim idle.
|
|
116
|
+
yellow pending/loading · red error · dim idle. After bind, Backend · Console ·
|
|
117
|
+
MCP · Docs MCP each get a box (URL + one-line purpose). Compose health keeps polling
|
|
116
118
|
(`docker compose ps -a`); AI ● tracks model phase while the AI container is up.
|
|
117
119
|
Boot does not wait for the model to become ready. A successful session writes
|
|
118
120
|
`.oke/dev.json` (pid · ports · startedAt) and clears it on stop. Durable local
|
|
119
|
-
markers live in `.oke/state.json`
|
|
120
|
-
prompt for this app's `defineSeed({ name })`) — not in the session lock. Console session signing uses
|
|
121
|
+
markers live in `.oke/state.json` — not in the session lock. Console session signing uses
|
|
121
122
|
`.oke/console.secret` (or `OKE_CONSOLE_SECRET`); that is not a Vault secret.
|
|
122
123
|
|
|
123
124
|
On a TTY, Ink keyboard controls stay active after boot (`useInput` — same
|
|
124
125
|
shortcuts as before). Press `r` to clear the log pane and reprint the latest
|
|
125
126
|
board.
|
|
126
127
|
|
|
127
|
-
| Key
|
|
128
|
-
|
|
|
129
|
-
| `?`
|
|
130
|
-
| `r`
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
128
|
+
| Key | Action |
|
|
129
|
+
| --- | ----------------------------------- |
|
|
130
|
+
| `?` | help |
|
|
131
|
+
| `r` | refresh — clear logs, show latest ● |
|
|
132
|
+
| `s` | `oke db seed` |
|
|
133
|
+
| `q` | quit `oke dev` |
|
|
134
|
+
| `u` | compose up (whole stack) |
|
|
135
|
+
| `x` | compose stop (whole stack) |
|
|
133
136
|
|
|
134
137
|
Compose stays quiet unless it fails. Process-local boot notices print once under
|
|
135
138
|
the hero (not once per process).
|
|
@@ -5,7 +5,7 @@ icon: "MonitorSmartphone"
|
|
|
5
5
|
source: "docs/spec/unified-theory.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
`okengine/client` is how a browser, CLI, or another service calls your app's flows.
|
|
8
|
+
`okengine/client` is how a browser, CLI, or another service calls your app's flows. Import the generated barrel, take `typeof app`, and `api.notes.get({ id })` is fully typed — same contracts the server already has, no separate schema project.
|
|
9
9
|
|
|
10
10
|
<Callout title="The one rule">
|
|
11
11
|
Treat every call as a result envelope: `{ data, error }`. Flow failures are values you switch on
|
|
@@ -21,19 +21,22 @@ source: "docs/spec/unified-theory.md"
|
|
|
21
21
|
### Adopt flows and export `App`
|
|
22
22
|
|
|
23
23
|
```typescript title="src/app.ts"
|
|
24
|
-
import
|
|
25
|
-
import
|
|
24
|
+
import "@/core";
|
|
25
|
+
import "@/flows/generated";
|
|
26
|
+
import { oke } from "okengine/http";
|
|
26
27
|
|
|
27
|
-
export const app = oke({ name: "
|
|
28
|
+
export const app = oke({ name: "notes" });
|
|
28
29
|
export type App = typeof app;
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
`.adopt({ notes })` is optional and additive.
|
|
33
|
+
|
|
31
34
|
</Step>
|
|
32
35
|
|
|
33
36
|
<Step>
|
|
34
37
|
### Create the client
|
|
35
38
|
|
|
36
|
-
Same repo — pass the app value so HTTP triggers hit REST (method + path from
|
|
39
|
+
Same repo — pass the app value so HTTP triggers hit REST (method + path from `$routes`):
|
|
37
40
|
|
|
38
41
|
```typescript title="client"
|
|
39
42
|
import { createClient } from "okengine/client";
|
|
@@ -114,7 +117,7 @@ oke client add https://api.example.com --out ./types/oke-client.d.ts
|
|
|
114
117
|
| Incomplete proxy path (`api.notes()` with no flow) | Result error: `Incomplete path: api.notes(…)` |
|
|
115
118
|
|
|
116
119
|
```typescript
|
|
117
|
-
// REST —
|
|
120
|
+
// REST — method/path from the bound HTTP trigger (file tree or explicit)
|
|
118
121
|
await api.notes.get({ id: "n_1" }); // GET /notes/n_1
|
|
119
122
|
|
|
120
123
|
// RPC — untriggered flow named notes.stats
|
|
@@ -210,16 +213,16 @@ core today: cookie jars or plugin `.client()` decorations. Browser apps: also se
|
|
|
210
213
|
outcome by calling a Flow that uses it — or by handling a gate denial on that call.
|
|
211
214
|
</Callout>
|
|
212
215
|
|
|
213
|
-
| Element | On the client | How
|
|
214
|
-
| --------------------------------- | ------------- |
|
|
215
|
-
| [Flow](/docs/elements/flow) | Direct | `api.unit.flow(input)` — the only public surface
|
|
216
|
-
| [Gate](/docs/elements/gate) | Indirect | Bearer via `auth`; denials as `Unauthorized` / `Forbidden` / `RateLimited`
|
|
217
|
-
| [Store](/docs/elements/store) | Via Flows | `fx.store` inside Flows; `store.resource` + `on(http.resource…)` → five Flows
|
|
218
|
-
| [Signal](/docs/elements/signal) | Via Flows | Emit/consume server-side; no subscribe API on `okengine/client` yet
|
|
219
|
-
| [Clock](/docs/elements/clock) | Via Flows | Schedules fire on the server — the client never ticks a clock
|
|
220
|
-
| [Vault](/docs/elements/vault) | Via Flows | Secrets stay server-side; never ship them to the browser package
|
|
221
|
-
| [Channel](/docs/elements/channel) | Via Flows | `fx.send` in a Flow — the client does not send email/SMS/push
|
|
222
|
-
| [AI](/docs/elements/ai) | Via Flows | `fx.ask` / `fx.run` inside a Flow; the client gets that Flow’s `out`
|
|
216
|
+
| Element | On the client | How |
|
|
217
|
+
| --------------------------------- | ------------- | ------------------------------------------------------------------------------------------ |
|
|
218
|
+
| [Flow](/docs/elements/flow) | Direct | `api.unit.flow(input)` — the only public surface |
|
|
219
|
+
| [Gate](/docs/elements/gate) | Indirect | Bearer via `auth`; denials as `Unauthorized` / `Forbidden` / `RateLimited` |
|
|
220
|
+
| [Store](/docs/elements/store) | Via Flows | `fx.store` inside Flows; `store.resource` + `on(http.resource…)` → five Flows on `$routes` |
|
|
221
|
+
| [Signal](/docs/elements/signal) | Via Flows | Emit/consume server-side; no subscribe API on `okengine/client` yet |
|
|
222
|
+
| [Clock](/docs/elements/clock) | Via Flows | Schedules fire on the server — the client never ticks a clock |
|
|
223
|
+
| [Vault](/docs/elements/vault) | Via Flows | Secrets stay server-side; never ship them to the browser package |
|
|
224
|
+
| [Channel](/docs/elements/channel) | Via Flows | `fx.send` in a Flow — the client does not send email/SMS/push |
|
|
225
|
+
| [AI](/docs/elements/ai) | Via Flows | `fx.ask` / `fx.run` inside a Flow; the client gets that Flow’s `out` |
|
|
223
226
|
|
|
224
227
|
### Store resources
|
|
225
228
|
|
|
@@ -227,7 +230,7 @@ Mount a resource, adopt the returned ops, then call the five Flows like any othe
|
|
|
227
230
|
|
|
228
231
|
```typescript
|
|
229
232
|
const notesR = store.resource(db, notes, {/* in, out, list */});
|
|
230
|
-
const mounted = on(http.resource("/notes", notesR.all()).
|
|
233
|
+
const mounted = on(http.resource("/notes", notesR.all()).public().live());
|
|
231
234
|
// .adopt({ notes: mounted }) →
|
|
232
235
|
const page = await api.notes.list({ limit: 20 });
|
|
233
236
|
const more = await page.next();
|
|
@@ -280,9 +283,9 @@ Budget: the `./client` export stays under the measured client-runtime cap (hard
|
|
|
280
283
|
|
|
281
284
|
<Accordion title="api.main.health is not a function / type error">
|
|
282
285
|
|
|
283
|
-
Confirm the
|
|
284
|
-
|
|
285
|
-
`oke dev` after renaming exports.
|
|
286
|
+
Confirm the Flow is `export`ed from a generated unit (`import "@/flows/generated"` then `oke({ name })`), or from a module you still `.adopt({ main })`.
|
|
287
|
+
|
|
288
|
+
Type `createClient` with that `App` (or ambient `Register` after `oke-client.d.ts` regenerates). Restart `oke dev` after renaming exports.
|
|
286
289
|
|
|
287
290
|
</Accordion>
|
|
288
291
|
|
|
@@ -321,7 +324,8 @@ calls `api.auth.refresh({ refreshToken })`. Re-login when rotation fails or no r
|
|
|
321
324
|
|
|
322
325
|
## Learn more
|
|
323
326
|
|
|
324
|
-
- [
|
|
327
|
+
- [Project structure](/docs/get-started/project-structure) — folders are the URL; `$routes` without `.adopt()`
|
|
328
|
+
- [Basic usage](/docs/get-started/basic-usage) — generated barrel → client → test loop
|
|
325
329
|
- [Gate](/docs/elements/gate) — policies, `gate.public`, denials
|
|
326
330
|
- [Store](/docs/elements/store) — `store.resource` and list query language
|
|
327
331
|
- [Flow](/docs/elements/flow) — `in` / `out` / `errors` and `fx.fail`
|
|
@@ -41,7 +41,7 @@ Our path is concrete: a booking name containing "ignore previous instructions an
|
|
|
41
41
|
#### 10.4 Remaining closures
|
|
42
42
|
|
|
43
43
|
- **`invoke-as` is attenuated** exactly like an API key: an operator cannot assume a scope set they could not grant. Impersonating a real user is development-only. Console `POST /console/flows/invoke` runs the target flow on a bound host under the selected identity’s scopes (`trustedInvoke` is in-process only — never from public HTTP); when the host adapter is unbound, invoke fails closed.
|
|
44
|
-
- **Store SQL browse** masks classified PII by default. `QUERY /console/store/query` with `revealPii: true` returns cleartext for the current page and writes an audited `console.store.query.reveal` log (operator, ref, child). The SQL query console (`POST /console/store/sql`) stays masked unless `revealPii: true`, which writes `console.store.sql.reveal` (operator, ref). Call API (`POST /console/flows/invoke`) masks the same classified field names on the handler response unless `revealPii: true`, which writes `console.flows.invoke.reveal` and opens the host store session with cleartext so columns the handler reads (for example `ownerEmail`) are not already `[redacted]`. `asGate`
|
|
44
|
+
- **Store SQL browse** masks classified PII by default. `QUERY /console/store/query` with `revealPii: true` returns cleartext for the current page and writes an audited `console.store.query.reveal` log (operator, ref, child). The SQL query console (`POST /console/store/sql`) stays masked unless `revealPii: true`, which writes `console.store.sql.reveal` (operator, ref). Call API (`POST /console/flows/invoke`) masks the same classified field names on the handler response unless `revealPii: true`, which writes `console.flows.invoke.reveal` and opens the host store session with cleartext so columns the handler reads (for example `ownerEmail`) are not already `[redacted]`. `{ asGate, asUserId }` on SQL, browse, row edit, and Call API stamps `oke.gate()` / `oke.user()` / `oke.has_scope()` on postgres / pglite (one identity bag; policy-only leaves `oke.user()` empty — never `gate:${name}`); Operator / omit / `bypassGates` still bypasses RLS. The response may include `rls: { gate, userId, applied }`. Catalog DDL stays Operator. Per-cell `POST /console/store/reveal` remains available. The Console auth schema (`sql:oke_console` — `oke_operators`, sessions, roles, keys) is hidden from Store browse unless `OKE_CONSOLE_AUTH_STORE=1`. When listed it is **read-only**; edit / delete / raw SQL are refused so operator-plane rows are never joined to `public`.
|
|
45
45
|
- **Host WideEvent ingest** (`POST /console/runs/ingest`, `oke dev` only) is secret-gated and never echoes the event body. Operators read runs only through `projectRun` / `maskWideEventForConsole` on `GET /console/runs` and `/console/live` — the same PII boundary as Console-local runs.
|
|
46
46
|
- **Store SQL performance** (`QUERY /console/store/sql/stats`, `/locks`, `POST /console/store/sql/advise`) is engine-native query text — not Store browse masking. Statements show `pg_stat_statements` fingerprints (`$n`). Live `pg_stat_activity.query` is collapsed until `revealPii: true`, which writes `console.store.sql.stats.reveal` (operator, ref). Named limitation `StoreSqlStatsQueryTextGap`. This surface does not terminate sessions or call `pg_stat_statements_reset`.
|
|
47
47
|
- **Store KV performance** (`QUERY /console/store/kv/stats`) is Redis-wire `INFO` / `COMMANDSTATS` / `SLOWLOG` / `LATENCY` — not Store browse. INFO is instance-wide (`StoreKvStatsServerWideGap`). SLOWLOG args are keys and values — collapsed until `revealPii: true`, which writes `console.store.kv.stats.reveal` (operator, ref). Named limitation `StoreKvStatsSlowlogArgsGap`. `memory` returns `KvStatsUnsupported`. This surface does not run `MONITOR` and does not invent a hot-key table.
|
package/src/auth/bindings.ts
CHANGED
|
@@ -275,10 +275,13 @@ export function createAuthHttpBindings(
|
|
|
275
275
|
} = { refresh, revoke, me };
|
|
276
276
|
const bindings: Binding[] = [
|
|
277
277
|
bindAuthHttp(
|
|
278
|
-
http
|
|
278
|
+
http
|
|
279
|
+
.post(`${base}/refresh`)
|
|
280
|
+
.public()
|
|
281
|
+
.gate(...(refreshRate ? [refreshRate] : [])),
|
|
279
282
|
refresh,
|
|
280
283
|
),
|
|
281
|
-
bindAuthHttp(http.post(`${base}/revoke`).
|
|
284
|
+
bindAuthHttp(http.post(`${base}/revoke`).public(), revoke),
|
|
282
285
|
bindAuthHttp(http.get(`${base}/me`).gate(AUTH_SESSION_GATE), me),
|
|
283
286
|
];
|
|
284
287
|
|
|
@@ -371,11 +374,17 @@ export function createAuthHttpBindings(
|
|
|
371
374
|
flows.signUpEmail = signUpEmail;
|
|
372
375
|
bindings.push(
|
|
373
376
|
bindAuthHttp(
|
|
374
|
-
http
|
|
377
|
+
http
|
|
378
|
+
.post(`${base}/sign-in/email`)
|
|
379
|
+
.public()
|
|
380
|
+
.gate(...(signInRate ? [signInRate] : [])),
|
|
375
381
|
signInEmail,
|
|
376
382
|
),
|
|
377
383
|
bindAuthHttp(
|
|
378
|
-
http
|
|
384
|
+
http
|
|
385
|
+
.post(`${base}/sign-up/email`)
|
|
386
|
+
.public()
|
|
387
|
+
.gate(...(signUpRate ? [signUpRate] : [])),
|
|
379
388
|
signUpEmail,
|
|
380
389
|
),
|
|
381
390
|
);
|
|
@@ -406,7 +415,7 @@ export function isSessionFresh(
|
|
|
406
415
|
* @param flowDef - Flow
|
|
407
416
|
*/
|
|
408
417
|
export function bindAuthHttp(trigger: HttpTrigger, flowDef: AnyFlowDef): Binding {
|
|
409
|
-
const withPosture = trigger.gates.length > 0 ? trigger : trigger.
|
|
418
|
+
const withPosture = trigger.gates.length > 0 ? trigger : trigger.public();
|
|
410
419
|
const normalized = normalizeTrigger(withPosture);
|
|
411
420
|
const list = flowDef.triggers as Trigger[];
|
|
412
421
|
list.push(normalized);
|
package/src/auth/cross-plane.ts
CHANGED
|
@@ -86,9 +86,16 @@ export function assertCrossPlane(
|
|
|
86
86
|
|
|
87
87
|
function inferFlowFromPath(path: string): string | undefined {
|
|
88
88
|
// …/flows/bookings/create.ts → bookings.create
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
// …/flows/notes/[id]/get.ts → notes.get (never notes.id.get)
|
|
90
|
+
const posix = path.replace(/\\/g, "/");
|
|
91
|
+
const m = /(?:^|\/)flows\/([^/]+)\/(.+)\.(ts|tsx|js)$/.exec(posix);
|
|
92
|
+
if (!m) return undefined;
|
|
93
|
+
const unit = m[1]!;
|
|
94
|
+
const rest = m[2]!;
|
|
95
|
+
const leaf = rest.split("/").pop();
|
|
96
|
+
if (!leaf || leaf === "index" || leaf.startsWith("_")) return undefined;
|
|
97
|
+
if (leaf === "shapes" || leaf === "signals" || leaf.endsWith(".test")) return undefined;
|
|
98
|
+
return `${unit}.${leaf}`;
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
function inferPlaneFromSource(source: string): "user" | "operator" | undefined {
|
|
@@ -167,6 +167,68 @@ export default defineConfig({
|
|
|
167
167
|
rmSync(dir, { recursive: true, force: true });
|
|
168
168
|
}
|
|
169
169
|
});
|
|
170
|
+
|
|
171
|
+
test("applyAiSetup prefers an existing src/core/ai.ts over a thin core.ts barrel", () => {
|
|
172
|
+
const dir = mkdtempSync(join(tmpdir(), "oke-ai-setup-split-"));
|
|
173
|
+
try {
|
|
174
|
+
writeFileSync(
|
|
175
|
+
join(dir, "oke.config.ts"),
|
|
176
|
+
`import { defineConfig } from "okengine/config";
|
|
177
|
+
export default defineConfig({
|
|
178
|
+
drivers: {
|
|
179
|
+
channel: {
|
|
180
|
+
email: {
|
|
181
|
+
dev: "smtp",
|
|
182
|
+
test: "console",
|
|
183
|
+
prod: "smtp",
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
images: {
|
|
188
|
+
store: {
|
|
189
|
+
sql: "postgres:18-alpine",
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
`,
|
|
194
|
+
"utf8",
|
|
195
|
+
);
|
|
196
|
+
mkdirSync(join(dir, "src", "core"), { recursive: true });
|
|
197
|
+
writeFileSync(
|
|
198
|
+
join(dir, "src", "core.ts"),
|
|
199
|
+
`export * from "./core/store.ts";\nexport * from "./core/ai.ts";\n`,
|
|
200
|
+
"utf8",
|
|
201
|
+
);
|
|
202
|
+
writeFileSync(
|
|
203
|
+
join(dir, "src", "core", "store.ts"),
|
|
204
|
+
`import { store } from "okengine";\n\nexport const db = store.sql("app");\n`,
|
|
205
|
+
"utf8",
|
|
206
|
+
);
|
|
207
|
+
writeFileSync(join(dir, "src", "core", "ai.ts"), `import { ai } from "okengine";\n`, "utf8");
|
|
208
|
+
writeFileSync(
|
|
209
|
+
join(dir, "src", "app.ts"),
|
|
210
|
+
`import "@/core";\nexport const app = {};\n`,
|
|
211
|
+
"utf8",
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
applyAiSetup(dir, {
|
|
215
|
+
driver: "ollama",
|
|
216
|
+
baseUrl: "http://127.0.0.1:11434",
|
|
217
|
+
chatModel: "gemma4:e4b",
|
|
218
|
+
visionModel: "qwen3-vl:4b",
|
|
219
|
+
embedModel: "nomic-embed-text",
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const barrel = readFileSync(join(dir, "src", "core.ts"), "utf8");
|
|
223
|
+
expect(barrel).not.toContain("ai.model");
|
|
224
|
+
expect(barrel).toContain('export * from "./core/ai.ts"');
|
|
225
|
+
const aiTs = readFileSync(join(dir, "src", "core", "ai.ts"), "utf8");
|
|
226
|
+
expect(aiTs).toContain("smart");
|
|
227
|
+
expect(aiTs).toContain('ai.model("vision"');
|
|
228
|
+
} finally {
|
|
229
|
+
rmSync(dir, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
170
232
|
});
|
|
171
233
|
|
|
172
234
|
describe("parseAiSetupArgs", () => {
|
|
@@ -244,7 +244,9 @@ export function renderAiTs(input: AiSetupApplyInput): string {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
|
-
* Write AI model declarations into `src/core.ts`
|
|
247
|
+
* Write AI model declarations into `src/core/ai.ts` when that split exists
|
|
248
|
+
* (so a thin `src/core.ts` barrel stays a re-export), else `src/core.ts`,
|
|
249
|
+
* else legacy `src/core/index.ts` + sidecar.
|
|
248
250
|
*
|
|
249
251
|
* @param cwd - Project root
|
|
250
252
|
* @param input - Setup choices
|
|
@@ -252,9 +254,25 @@ export function renderAiTs(input: AiSetupApplyInput): string {
|
|
|
252
254
|
*/
|
|
253
255
|
function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
|
|
254
256
|
const rendered = renderAiTs(input);
|
|
257
|
+
const coreAiPath = join(cwd, "src", "core", "ai.ts");
|
|
255
258
|
const coreTsPath = join(cwd, "src", "core.ts");
|
|
256
259
|
const legacyIndex = join(cwd, "src", "core", "index.ts");
|
|
257
260
|
|
|
261
|
+
if (existsSync(coreAiPath)) {
|
|
262
|
+
const existing = readFileSync(coreAiPath, "utf8");
|
|
263
|
+
if (hasAiModels(existing)) {
|
|
264
|
+
const withPrompt = ensureSummarizeNotePrompt(existing);
|
|
265
|
+
if (withPrompt !== existing) {
|
|
266
|
+
writeFileSync(coreAiPath, withPrompt, "utf8");
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
writeFileSync(coreAiPath, mergeAiIntoCore(existing, rendered), "utf8");
|
|
270
|
+
}
|
|
271
|
+
ensureCoreBarrelExportsAi(cwd);
|
|
272
|
+
ensureCoreImported(cwd);
|
|
273
|
+
return coreAiPath;
|
|
274
|
+
}
|
|
275
|
+
|
|
258
276
|
// Folder layout still in the wild — keep writing a sidecar.
|
|
259
277
|
if (!existsSync(coreTsPath) && existsSync(legacyIndex)) {
|
|
260
278
|
const aiTsPath = join(cwd, "src", "core", "ai.ts");
|
|
@@ -364,6 +382,19 @@ export function ensureNamedOkengineImport(source: string, name: string): string
|
|
|
364
382
|
return source.replace(re, `import { ${sorted.join(", ")} } from "okengine";`);
|
|
365
383
|
}
|
|
366
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Keep a `src/core.ts` barrel re-exporting `./core/ai.ts` after a split write.
|
|
387
|
+
*
|
|
388
|
+
* @param cwd - Project root
|
|
389
|
+
*/
|
|
390
|
+
function ensureCoreBarrelExportsAi(cwd: string): void {
|
|
391
|
+
const coreTsPath = join(cwd, "src", "core.ts");
|
|
392
|
+
if (!existsSync(coreTsPath)) return;
|
|
393
|
+
const src = readFileSync(coreTsPath, "utf8");
|
|
394
|
+
if (/from\s+["']\.\/core\/ai/.test(src)) return;
|
|
395
|
+
writeFileSync(coreTsPath, `${src.trimEnd()}\nexport * from "./core/ai.ts";\n`, "utf8");
|
|
396
|
+
}
|
|
397
|
+
|
|
367
398
|
/**
|
|
368
399
|
* Ensure `src/app.ts` loads `@/core` / `./core` so merged AI registers.
|
|
369
400
|
*
|
|
@@ -28,7 +28,7 @@ describe("attachHostToConsole", () => {
|
|
|
28
28
|
import { oke, on, flow, http, gate, store } from ${JSON.stringify(OKE)};
|
|
29
29
|
const db = store.sql("db");
|
|
30
30
|
export const ping = on(
|
|
31
|
-
http.get("/ping").
|
|
31
|
+
http.get("/ping").public(),
|
|
32
32
|
flow("main.ping", { do: () => ({ ok: true }) }),
|
|
33
33
|
);
|
|
34
34
|
export const app = oke({
|
|
@@ -69,6 +69,7 @@ export const app = oke({
|
|
|
69
69
|
expect(ingested.some((row) => row.event?.flow === "main.ping")).toBe(true);
|
|
70
70
|
await attached!.stop();
|
|
71
71
|
expect(state.invokeUserFlow).toBeNull();
|
|
72
|
+
expect(state.storeRuntime).toBeNull();
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
test("binds host aiRuntime so Console projection sees a journaled ask", async () => {
|
|
@@ -81,7 +82,7 @@ import { oke, on, flow, http, gate, ai } from ${JSON.stringify(OKE)};
|
|
|
81
82
|
const smart = ai.model("smart", { provider: "mock" });
|
|
82
83
|
smart.prompt("ticket-triage", { version: 2 });
|
|
83
84
|
export const ping = on(
|
|
84
|
-
http.get("/ping").
|
|
85
|
+
http.get("/ping").public(),
|
|
85
86
|
flow("main.ping", {
|
|
86
87
|
effects: { asks: ["ticket-triage"] },
|
|
87
88
|
do: (_input, fx) => fx.ask("ticket-triage", { subject: "hello" }),
|
|
@@ -73,6 +73,9 @@ export async function attachHostToConsole(
|
|
|
73
73
|
return {
|
|
74
74
|
stop: async () => {
|
|
75
75
|
options.state.invokeUserFlow = null;
|
|
76
|
+
options.state.storeRuntime = null;
|
|
77
|
+
options.state.vaultRuntime = null;
|
|
78
|
+
options.state.aiRuntime = null;
|
|
76
79
|
await host.stop();
|
|
77
80
|
},
|
|
78
81
|
};
|
package/src/cli/build.test.ts
CHANGED
|
@@ -32,8 +32,10 @@ describe("oke build — .adopt() barrel regeneration", () => {
|
|
|
32
32
|
expect(code).toBe(0);
|
|
33
33
|
|
|
34
34
|
const after = await readFile(generatedPath, "utf8");
|
|
35
|
-
expect(after).toContain('
|
|
36
|
-
expect(after).toContain('
|
|
35
|
+
expect(after).toContain('import * as main from "./main/index.ts";');
|
|
36
|
+
expect(after).toContain('import * as notes from "./notes/index.ts";');
|
|
37
|
+
expect(after).toContain("export { main }");
|
|
38
|
+
expect(after).toContain("export { notes }");
|
|
37
39
|
} finally {
|
|
38
40
|
await rm(root, { recursive: true, force: true });
|
|
39
41
|
}
|
package/src/cli/db-seed.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `oke db seed` — load `defineSeed`, boot the app store, run essential +
|
|
3
|
-
* env-selected category (`dev` | `prod`).
|
|
3
|
+
* env-selected category (`dev` | `prod`). Tallies SQL upserts and `kv.set`.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
|
-
import { isCancel, text } from "@clack/prompts";
|
|
7
|
+
import { intro, isCancel, outro, text } from "@clack/prompts";
|
|
8
8
|
import type { ConfigEnv, OkeConfig } from "../config/index.ts";
|
|
9
9
|
import {
|
|
10
10
|
normalizeSeedFns,
|
|
@@ -25,11 +25,13 @@ import { EXIT_OK, EXIT_RUNTIME, EXIT_USAGE } from "./exit.ts";
|
|
|
25
25
|
import { loadOkeConfig } from "./load-config.ts";
|
|
26
26
|
import { resolveDevSqlEnv } from "./resolve-dev-sql-env.ts";
|
|
27
27
|
|
|
28
|
-
/** Per-function upsert outcome counts. */
|
|
28
|
+
/** Per-function upsert / KV-set outcome counts. */
|
|
29
29
|
export interface SeedTally {
|
|
30
30
|
upserted: number;
|
|
31
31
|
changed: number;
|
|
32
32
|
alreadyExisted: number;
|
|
33
|
+
/** `fx.store(kv).set` calls (cache or durable). */
|
|
34
|
+
keys: number;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/** Options for {@link runSeed}. */
|
|
@@ -56,6 +58,11 @@ export interface SeedOptions {
|
|
|
56
58
|
*/
|
|
57
59
|
readonly confirmEnv?: (env: ConfigEnv, target: string) => Promise<boolean>;
|
|
58
60
|
readonly stdinIsTTY?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Clack `intro` / `outro` for the standalone `oke db seed` path.
|
|
63
|
+
* Default: on when stdin is a TTY.
|
|
64
|
+
*/
|
|
65
|
+
readonly intro?: boolean;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
/**
|
|
@@ -127,12 +134,13 @@ export async function runSeedFns(
|
|
|
127
134
|
): Promise<void> {
|
|
128
135
|
for (let i = 0; i < fns.length; i++) {
|
|
129
136
|
const fn = fns[i]!;
|
|
130
|
-
const tally: SeedTally = { upserted: 0, changed: 0, alreadyExisted: 0 };
|
|
137
|
+
const tally: SeedTally = { upserted: 0, changed: 0, alreadyExisted: 0, keys: 0 };
|
|
131
138
|
const instrumented = instrumentFxUpserts(fx, tally);
|
|
132
139
|
await fn(instrumented);
|
|
133
140
|
const label = fn.name.length > 0 ? fn.name : String(i);
|
|
141
|
+
const kv = tally.keys > 0 ? ` · keys ${tally.keys}` : "";
|
|
134
142
|
write(
|
|
135
|
-
`oke db seed: ${category} ${label} — upserted ${tally.upserted} · changed ${tally.changed} · already-existed ${tally.alreadyExisted}\n`,
|
|
143
|
+
`oke db seed: ${category} ${label} — upserted ${tally.upserted} · changed ${tally.changed} · already-existed ${tally.alreadyExisted}${kv}\n`,
|
|
136
144
|
);
|
|
137
145
|
}
|
|
138
146
|
}
|
|
@@ -233,7 +241,13 @@ export async function bootSeedFx(
|
|
|
233
241
|
if (!app) {
|
|
234
242
|
throw new Error(`oke db seed: no oke() app export in ${entryAbs}`);
|
|
235
243
|
}
|
|
236
|
-
await app.boot({
|
|
244
|
+
await app.boot({
|
|
245
|
+
env,
|
|
246
|
+
startScheduler: false,
|
|
247
|
+
config: config ?? undefined,
|
|
248
|
+
rootDir: cwd,
|
|
249
|
+
docker: process.env.OKE_DOCKER === "1",
|
|
250
|
+
});
|
|
237
251
|
const storeRuntime = app.elements?.store ?? app.bootResult?.store;
|
|
238
252
|
if (!storeRuntime) {
|
|
239
253
|
throw new Error(
|
|
@@ -243,6 +257,7 @@ export async function bootSeedFx(
|
|
|
243
257
|
const fx = createFx({
|
|
244
258
|
flow: "oke.db.seed",
|
|
245
259
|
storeRuntime,
|
|
260
|
+
rlsBypass: true,
|
|
246
261
|
});
|
|
247
262
|
return {
|
|
248
263
|
fx,
|
|
@@ -262,6 +277,14 @@ export async function runSeed(options: SeedOptions = {}): Promise<number> {
|
|
|
262
277
|
const cwd = options.cwd ?? process.cwd();
|
|
263
278
|
const loaded = await loadOkeConfig(cwd).catch(() => null);
|
|
264
279
|
const env = options.env ?? (await resolveDevSqlEnv(cwd));
|
|
280
|
+
const tty = options.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
281
|
+
const useIntro = options.intro !== false && tty;
|
|
282
|
+
if (useIntro) intro("oke db seed");
|
|
283
|
+
|
|
284
|
+
const finish = (code: number, message: string): number => {
|
|
285
|
+
if (useIntro) outro(message);
|
|
286
|
+
return code;
|
|
287
|
+
};
|
|
265
288
|
|
|
266
289
|
let targetLabel = String(env);
|
|
267
290
|
try {
|
|
@@ -274,7 +297,6 @@ export async function runSeed(options: SeedOptions = {}): Promise<number> {
|
|
|
274
297
|
|
|
275
298
|
if (env === "dev" || env === "prod") {
|
|
276
299
|
if (options.force !== true) {
|
|
277
|
-
const tty = options.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
278
300
|
const confirm =
|
|
279
301
|
options.confirmEnv ??
|
|
280
302
|
(tty
|
|
@@ -288,7 +310,7 @@ export async function runSeed(options: SeedOptions = {}): Promise<number> {
|
|
|
288
310
|
const ok = await confirm(env, targetLabel);
|
|
289
311
|
if (!ok) {
|
|
290
312
|
write("oke db seed: cancelled\n");
|
|
291
|
-
return EXIT_USAGE;
|
|
313
|
+
return finish(EXIT_USAGE, "Cancelled.");
|
|
292
314
|
}
|
|
293
315
|
}
|
|
294
316
|
}
|
|
@@ -308,10 +330,10 @@ export async function runSeed(options: SeedOptions = {}): Promise<number> {
|
|
|
308
330
|
stop = session.stop;
|
|
309
331
|
await executeSeedDef(def, env, session.fx, write);
|
|
310
332
|
write("oke db seed: ok\n");
|
|
311
|
-
return EXIT_OK;
|
|
333
|
+
return finish(EXIT_OK, "oke db seed: ok");
|
|
312
334
|
} catch (err) {
|
|
313
335
|
write(`oke db seed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
314
|
-
return EXIT_RUNTIME;
|
|
336
|
+
return finish(EXIT_RUNTIME, "oke db seed: failed");
|
|
315
337
|
} finally {
|
|
316
338
|
if (stop) await stop().catch(() => {});
|
|
317
339
|
}
|
|
@@ -374,14 +396,18 @@ function instrumentFxUpserts(fx: Fx, tally: SeedTally): Fx {
|
|
|
374
396
|
|
|
375
397
|
function instrumentUpsertHandle<T>(handle: T, tally: SeedTally): T {
|
|
376
398
|
if (!handle || typeof handle !== "object") return handle;
|
|
377
|
-
const h = handle as {
|
|
378
|
-
|
|
379
|
-
|
|
399
|
+
const h = handle as {
|
|
400
|
+
upsert?: (...args: unknown[]) => Promise<{ status: UpsertStatus }>;
|
|
401
|
+
set?: (...args: unknown[]) => Promise<unknown>;
|
|
402
|
+
};
|
|
403
|
+
const origUpsert = typeof h.upsert === "function" ? h.upsert.bind(h) : undefined;
|
|
404
|
+
const origSet = typeof h.set === "function" ? h.set.bind(h) : undefined;
|
|
405
|
+
if (!origUpsert && !origSet) return handle;
|
|
380
406
|
return new Proxy(handle as object, {
|
|
381
407
|
get(target, prop, receiver) {
|
|
382
|
-
if (prop === "upsert") {
|
|
408
|
+
if (prop === "upsert" && origUpsert) {
|
|
383
409
|
return async (...args: unknown[]) => {
|
|
384
|
-
const result = await
|
|
410
|
+
const result = await origUpsert(...args);
|
|
385
411
|
const status =
|
|
386
412
|
result && typeof result === "object" && "status" in result
|
|
387
413
|
? (result as { status: UpsertStatus }).status
|
|
@@ -392,6 +418,13 @@ function instrumentUpsertHandle<T>(handle: T, tally: SeedTally): T {
|
|
|
392
418
|
return result;
|
|
393
419
|
};
|
|
394
420
|
}
|
|
421
|
+
if (prop === "set" && origSet) {
|
|
422
|
+
return async (...args: unknown[]) => {
|
|
423
|
+
const result = await origSet(...args);
|
|
424
|
+
tally.keys += 1;
|
|
425
|
+
return result;
|
|
426
|
+
};
|
|
427
|
+
}
|
|
395
428
|
return Reflect.get(target, prop, receiver);
|
|
396
429
|
},
|
|
397
430
|
}) as T;
|