okengine 0.9.1 → 0.10.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/AGENTS.md +1 -1
- package/manifest.v1.schema.json +9 -1
- package/package.json +3 -3
- package/site/content/docs/console/runs.mdx +10 -0
- package/site/content/docs/console/traces.mdx +4 -0
- package/site/content/docs/elements/ai.mdx +54 -32
- package/site/content/docs/elements/channel.mdx +97 -9
- package/site/content/docs/elements/clock.mdx +1 -0
- package/site/content/docs/elements/flow.mdx +42 -8
- package/site/content/docs/get-started/basic-usage.mdx +5 -1
- package/site/content/docs/get-started/installation.mdx +9 -5
- package/site/content/docs/meta.json +2 -0
- package/site/content/docs/providers/azure-redis.mdx +85 -0
- package/site/content/docs/providers/cockroachdb.mdx +86 -0
- package/site/content/docs/providers/digitalocean-caching.mdx +88 -0
- package/site/content/docs/providers/dragonfly-cloud.mdx +82 -0
- package/site/content/docs/providers/elasticache.mdx +92 -0
- package/site/content/docs/providers/index.mdx +101 -0
- package/site/content/docs/providers/memorystore.mdx +89 -0
- package/site/content/docs/providers/meta.json +20 -0
- package/site/content/docs/providers/neon.mdx +115 -0
- package/site/content/docs/providers/redis-cloud.mdx +93 -0
- package/site/content/docs/providers/supabase.mdx +117 -0
- package/site/content/docs/providers/upstash.mdx +91 -0
- package/site/content/docs/providers/yugabytedb.mdx +95 -0
- package/site/content/docs/recipes/caddy.mdx +128 -0
- package/site/content/docs/recipes/cockroachdb.mdx +127 -0
- package/site/content/docs/recipes/dragonfly.mdx +125 -0
- package/site/content/docs/recipes/index.mdx +146 -0
- package/site/content/docs/recipes/llama-cpp.mdx +153 -0
- package/site/content/docs/recipes/mailpit.mdx +136 -0
- package/site/content/docs/recipes/meilisearch.mdx +139 -0
- package/site/content/docs/recipes/meta.json +31 -0
- package/site/content/docs/recipes/ollama.mdx +142 -0
- package/site/content/docs/recipes/openbao.mdx +141 -0
- package/site/content/docs/recipes/pgdog.mdx +137 -0
- package/site/content/docs/recipes/postgres.mdx +136 -0
- package/site/content/docs/recipes/redis.mdx +130 -0
- package/site/content/docs/recipes/rustfs.mdx +148 -0
- package/site/content/docs/recipes/sglang.mdx +105 -0
- package/site/content/docs/recipes/supabase-docker.mdx +145 -0
- package/site/content/docs/recipes/timescale.mdx +129 -0
- package/site/content/docs/recipes/traefik.mdx +136 -0
- package/site/content/docs/recipes/valkey.mdx +121 -0
- package/site/content/docs/recipes/vllm.mdx +106 -0
- package/site/content/docs/recipes/yugabytedb.mdx +135 -0
- package/site/content/docs/reference/cli.md +42 -6
- package/site/content/docs/reference/configuration.mdx +5 -3
- package/site/content/docs/reference/environment-variables.mdx +12 -12
- package/site/content/docs/reference/fx.mdx +38 -4
- package/src/cli/ai-setup/ai-setup.test.ts +32 -12
- package/src/cli/ai-setup/apply.ts +65 -23
- package/src/cli/ai-setup/catalog.ts +1316 -35
- package/src/cli/ai-setup/detect-ollama.ts +47 -0
- package/src/cli/ai-setup/index.ts +44 -7
- package/src/cli/ai-setup/prompts.ts +430 -486
- package/src/cli/ai-setup/recommend.test.ts +150 -50
- package/src/cli/ai-setup/recommend.ts +118 -101
- package/src/cli/ask-seed.test.ts +68 -0
- package/src/cli/ask-seed.ts +79 -0
- package/src/cli/db-seed-redact.test.ts +25 -0
- package/src/cli/db-seed.ts +36 -7
- package/src/cli/db.test.ts +1 -1
- package/src/cli/db.ts +2 -2
- package/src/cli/dev-app-runner.ts +6 -0
- package/src/cli/dev-controls.test.ts +107 -0
- package/src/cli/dev-controls.ts +354 -0
- package/src/cli/dev-schema-sync.ts +12 -4
- package/src/cli/dev.test.ts +50 -30
- package/src/cli/dev.ts +633 -66
- package/src/cli/docker-cli.test.ts +15 -0
- package/src/cli/drizzle-env.test.ts +40 -6
- package/src/cli/drizzle-env.ts +22 -1
- package/src/cli/ensure-drizzle-config.ts +6 -4
- package/src/cli/hero-meta.test.ts +13 -1
- package/src/cli/hero-meta.ts +28 -6
- package/src/cli/index.ts +5 -0
- package/src/cli/registry.ts +24 -0
- package/src/cli/replay.test.ts +105 -0
- package/src/cli/replay.ts +206 -0
- package/src/cli/start.ts +12 -1
- package/src/compiler/effects-infer.ts +37 -8
- package/src/compiler/extract.test.ts +125 -0
- package/src/compiler/extract.ts +31 -6
- package/src/config/index.ts +3 -3
- package/src/console/server/claim.ts +3 -1
- package/src/console/ui/dist/assets/{index-ClX0ZCe5.js → index-D-9KFce5.js} +2 -2
- package/src/console/ui/dist/assets/panel-overview-CW0CQEc2.js +1 -0
- package/src/console/ui/dist/assets/panel-runs-BxuyDI3x.js +1 -0
- package/src/console/ui/dist/assets/{panel-signals-Dsxdu_AR.js → panel-signals-BXofbj7v.js} +1 -1
- package/src/console/ui/dist/assets/{panel-store-hy7O8HOs.js → panel-store-B8mH-arU.js} +1 -1
- package/src/console/ui/dist/assets/{panel-traces-CBiAe5go.js → panel-traces-DSA-ZWnf.js} +1 -1
- package/src/console/ui/dist/index.html +1 -1
- package/src/console/ui/overview/slo.ts +31 -3
- package/src/console/ui/overview/types.ts +6 -0
- package/src/console/ui/runs/errors.test.ts +53 -0
- package/src/console/ui/runs/errors.ts +88 -0
- package/src/console/ui/runs/index.ts +8 -0
- package/src/console/ui/runs/search.ts +16 -0
- package/src/console/ui/shell/panels/runs/RunsPanel.tsx +72 -1
- package/src/docker/ai-model-status.test.ts +101 -0
- package/src/docker/ai-model-status.ts +276 -0
- package/src/docker/compose-health.test.ts +115 -0
- package/src/docker/compose-health.ts +234 -0
- package/src/docker/compose.ts +24 -8
- package/src/docker/derive.ts +35 -9
- package/src/docker/docker.test.ts +304 -12
- package/src/docker/helpers.ts +56 -0
- package/src/docker/index.ts +52 -0
- package/src/docker/ollama-pull.ts +232 -0
- package/src/docker/recipes/cockroach.ts +24 -0
- package/src/docker/recipes/index.ts +32 -0
- package/src/docker/recipes/llama-cpp.ts +273 -0
- package/src/docker/recipes/ollama.ts +19 -23
- package/src/docker/recipes/pgdog.ts +4 -1
- package/src/docker/recipes/postgres.ts +5 -18
- package/src/docker/recipes/sglang.ts +55 -0
- package/src/docker/recipes/supabase.ts +21 -0
- package/src/docker/recipes/timescale.ts +18 -0
- package/src/docker/recipes/vllm.ts +44 -0
- package/src/docker/recipes/yugabyte.ts +23 -0
- package/src/docker/stack-id.test.ts +22 -0
- package/src/docker/stack-id.ts +38 -4
- package/src/docker/types.ts +15 -0
- package/src/drivers/ai-ollama.integration.test.ts +7 -4
- package/src/drivers/signal-engine.ts +10 -0
- package/src/drivers/signal-postgres.ts +24 -2
- package/src/drivers/signal-types.ts +10 -0
- package/src/elements/channel/fallback.test.ts +91 -0
- package/src/elements/channel/hard-bounce.test.ts +82 -0
- package/src/elements/channel/injection.test.ts +153 -0
- package/src/elements/channel/locale-ar.test.ts +102 -0
- package/src/elements/channel/otp-delivery.ts +5 -2
- package/src/elements/channel/receipts-ledger.test.ts +100 -0
- package/src/elements/channel/runtime.ts +24 -1
- package/src/elements/channel/test-helpers.ts +60 -0
- package/src/elements/channel/whatsapp-compliance.test.ts +124 -0
- package/src/elements/store/cache.test.ts +28 -0
- package/src/elements/store/cache.ts +1 -1
- package/src/elements/store/emit-drizzle.ts +43 -9
- package/src/elements/store/runtime.ts +1 -1
- package/src/elements/store/schema-decl.test.ts +2 -0
- package/src/elements/store/schema-decl.ts +15 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/index.ts +1 -0
- package/src/kernel/app.ts +131 -10
- package/src/kernel/boot-bind/signal.ts +6 -2
- package/src/kernel/boot-bind/store.ts +2 -1
- package/src/kernel/boot.test.ts +6 -5
- package/src/kernel/boot.ts +113 -10
- package/src/kernel/compensate.test.ts +131 -0
- package/src/kernel/correlation.test.ts +108 -0
- package/src/kernel/effect-timing.test.ts +82 -0
- package/src/kernel/effects-stamping.test.ts +207 -0
- package/src/kernel/errors.ts +11 -0
- package/src/kernel/flow.ts +38 -0
- package/src/kernel/fx-runs.test.ts +49 -0
- package/src/kernel/fx.test.ts +14 -0
- package/src/kernel/fx.ts +153 -14
- package/src/manifest/sql-resource.ts +41 -0
- package/src/manifest/types.ts +8 -2
- package/src/runs/collect.ts +3 -0
- package/src/runs/export-otlp.ts +64 -0
- package/src/runs/index.ts +16 -0
- package/src/runs/parquet.ts +9 -0
- package/src/runs/types.ts +5 -0
- package/src/runs/window.test.ts +68 -0
- package/src/runs/window.ts +148 -0
- package/src/runtime/boot-warn.test.ts +45 -0
- package/src/runtime/boot-warn.ts +17 -0
- package/src/runtime/dev-request-log.ts +5 -5
- package/src/term.test.ts +95 -10
- package/src/term.ts +369 -14
- package/src/console/ui/dist/assets/panel-overview-DONDzBd2.js +0 -1
- package/src/console/ui/dist/assets/panel-runs-D5zf-D9c.js +0 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsApp Business characterization (Known gap freeze):
|
|
3
|
+
* - drivers pick template vs free-text by presence of message.template only
|
|
4
|
+
* - Channel runtime has no 24h session-window API
|
|
5
|
+
*
|
|
6
|
+
* Meta requires free-form session messages inside a customer-initiated 24h
|
|
7
|
+
* window and pre-approved templates outside it. OKE does not enforce that
|
|
8
|
+
* window today — this file freezes the current payload-shape behavior so the
|
|
9
|
+
* gap cannot silently look fixed without a real design.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, test } from "bun:test";
|
|
13
|
+
import type {
|
|
14
|
+
ChannelDriver,
|
|
15
|
+
ChannelMessage,
|
|
16
|
+
ChannelSendResult,
|
|
17
|
+
} from "../../drivers/channel-types.ts";
|
|
18
|
+
import { channel, createChannelRuntime } from "../channel.ts";
|
|
19
|
+
|
|
20
|
+
type Captured =
|
|
21
|
+
| { kind: "template"; name: string; language: string; to: string }
|
|
22
|
+
| { kind: "text"; text: string; to: string };
|
|
23
|
+
|
|
24
|
+
function fakeWhatsAppDriver(captures: Captured[]): ChannelDriver {
|
|
25
|
+
const channelTransport = {
|
|
26
|
+
provider: "wa-cloud",
|
|
27
|
+
mediums: ["whatsapp" as const],
|
|
28
|
+
async send(message: ChannelMessage): Promise<ChannelSendResult> {
|
|
29
|
+
const templateName =
|
|
30
|
+
message.template ??
|
|
31
|
+
(typeof message.data?.template === "string" ? message.data.template : undefined);
|
|
32
|
+
const language =
|
|
33
|
+
message.locale ??
|
|
34
|
+
(typeof message.data?.language === "string" ? message.data.language : "en_US");
|
|
35
|
+
|
|
36
|
+
if (templateName) {
|
|
37
|
+
captures.push({
|
|
38
|
+
kind: "template",
|
|
39
|
+
name: templateName,
|
|
40
|
+
language,
|
|
41
|
+
to: message.to,
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
captures.push({
|
|
45
|
+
kind: "text",
|
|
46
|
+
text: message.text ?? "",
|
|
47
|
+
to: message.to,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
messageId: "wa-1",
|
|
54
|
+
driverId: "wa-cloud",
|
|
55
|
+
attempts: [{ driverId: "wa-cloud", ok: true, at: Date.now(), messageId: "wa-1" }],
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
id: "wa-cloud",
|
|
62
|
+
channel: channelTransport,
|
|
63
|
+
whatsappTransport: {
|
|
64
|
+
provider: "wa-cloud",
|
|
65
|
+
async send() {
|
|
66
|
+
return { messageId: "x", to: "", status: "sent", response: "" };
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe("WhatsApp session vs template (characterization)", () => {
|
|
73
|
+
test("template name on the send path selects template payload shape", async () => {
|
|
74
|
+
const captures: Captured[] = [];
|
|
75
|
+
const runtime = createChannelRuntime({
|
|
76
|
+
templates: [channel.template("order-update", { medium: "whatsapp" })],
|
|
77
|
+
drivers: [fakeWhatsAppDriver(captures)],
|
|
78
|
+
catalog: {
|
|
79
|
+
"order-update": { en: { text: "Your order shipped" } },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await runtime.send("order-update", {
|
|
84
|
+
to: "+966500000000",
|
|
85
|
+
locale: "en_US",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(captures).toEqual([
|
|
89
|
+
{
|
|
90
|
+
kind: "template",
|
|
91
|
+
name: "order-update",
|
|
92
|
+
language: "en_US",
|
|
93
|
+
to: "+966500000000",
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("missing template name sends free-text (no session-window gate)", async () => {
|
|
99
|
+
const captures: Captured[] = [];
|
|
100
|
+
// Medium-agnostic path that still hits channel transport without a template
|
|
101
|
+
// name on ChannelMessage — simulate driver-level choice when template unset.
|
|
102
|
+
const driver = fakeWhatsAppDriver(captures);
|
|
103
|
+
const result = await driver.channel!.send({
|
|
104
|
+
medium: "whatsapp",
|
|
105
|
+
to: "+966500000000",
|
|
106
|
+
text: "Hello from session",
|
|
107
|
+
// no template field — free-form
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(result.ok).toBe(true);
|
|
111
|
+
expect(captures).toEqual([{ kind: "text", text: "Hello from session", to: "+966500000000" }]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("ChannelRuntime has no session-window surface", () => {
|
|
115
|
+
const runtime = createChannelRuntime({});
|
|
116
|
+
const keys = Object.keys(runtime);
|
|
117
|
+
expect(keys).not.toContain("sessionWindow");
|
|
118
|
+
expect(keys).not.toContain("whatsappSession");
|
|
119
|
+
expect(keys).not.toContain("withinSessionWindow");
|
|
120
|
+
expect(typeof (runtime as { withinSessionWindow?: unknown }).withinSessionWindow).toBe(
|
|
121
|
+
"undefined",
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -43,4 +43,32 @@ describe("tier-1 cache — exact per-resource invalidation (path b)", () => {
|
|
|
43
43
|
expect(cache.invalidateFromEffects({ writes: ["sql:links"] }).keys).toEqual(keys);
|
|
44
44
|
expect(cache.get(keys[0]!)).toBeUndefined();
|
|
45
45
|
});
|
|
46
|
+
|
|
47
|
+
test("write to table A does not invalidate a cached read of table B in the same sql store", () => {
|
|
48
|
+
// Two tables under one `store.sql("app", …)` — the exact shape Direction
|
|
49
|
+
// B's per-table kernel resolution (fx.ts `gatedTable`) now produces:
|
|
50
|
+
// `sql:notes` / `sql:orders`, not the old coarse `sql:app` for both.
|
|
51
|
+
// This precision was already correct in cache.ts before Direction B —
|
|
52
|
+
// it simply never received per-table refs to prove it with. Confirmed
|
|
53
|
+
// here with the exact naming the kernel now emits.
|
|
54
|
+
const cache = createStoreCache();
|
|
55
|
+
const notesRead: Effects = { reads: ["sql:notes"] };
|
|
56
|
+
const keys = tier1KeysForReads(notesRead);
|
|
57
|
+
|
|
58
|
+
cache.set({
|
|
59
|
+
tier: 1,
|
|
60
|
+
key: keys[0]!,
|
|
61
|
+
value: [{ id: "n1" }],
|
|
62
|
+
resources: ["sql:notes"],
|
|
63
|
+
expiresAt: null,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// A write to "orders" — a different table, same store — must not touch it.
|
|
67
|
+
expect(cache.invalidateFromEffects({ writes: ["sql:orders"] }).keys).toEqual([]);
|
|
68
|
+
expect(cache.get(keys[0]!)).toBeDefined();
|
|
69
|
+
|
|
70
|
+
// A write to "notes" itself still invalidates.
|
|
71
|
+
expect(cache.invalidateFromEffects({ writes: ["sql:notes"] }).keys).toEqual(keys);
|
|
72
|
+
expect(cache.get(keys[0]!)).toBeUndefined();
|
|
73
|
+
});
|
|
46
74
|
});
|
|
@@ -53,7 +53,7 @@ export function tier1KeysForReads(
|
|
|
53
53
|
effects: Effects,
|
|
54
54
|
dimsByResource?: Readonly<Record<string, readonly string[]>>,
|
|
55
55
|
): string[] {
|
|
56
|
-
const reads = effects.reads ?? [];
|
|
56
|
+
const reads = (effects.reads ?? []).filter((r): r is ResourceRef => r !== "runs");
|
|
57
57
|
return reads.map((resource) => computedCacheKey(resource, dimsByResource?.[resource]));
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* watch) when abstract decls exist — not a third schema CLI.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
8
9
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
9
|
-
import { dirname, join
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
10
11
|
import type { TableContribution } from "../../kernel/plugin.ts";
|
|
11
12
|
import {
|
|
12
13
|
isFieldBuilder,
|
|
@@ -30,10 +31,16 @@ export type SqlDialect = "sqlite" | "postgres";
|
|
|
30
31
|
export const GENERATED_SCHEMA_HEADER = "// generated by oke — do not edit";
|
|
31
32
|
|
|
32
33
|
/** Default output path (relative to project root). */
|
|
33
|
-
export const DEFAULT_GENERATED_SCHEMA_REL = "src/schema.generated.ts";
|
|
34
|
+
export const DEFAULT_GENERATED_SCHEMA_REL = "src/db/schema.generated.ts";
|
|
34
35
|
|
|
35
36
|
/** Default declaration module path (relative to project root). */
|
|
36
|
-
export const DEFAULT_DECLARE_SCHEMA_REL = "src/schema.decl.ts";
|
|
37
|
+
export const DEFAULT_DECLARE_SCHEMA_REL = "src/db/schema.decl.ts";
|
|
38
|
+
|
|
39
|
+
/** Legacy paths kept so pre-layout projects keep resolving. */
|
|
40
|
+
export const LEGACY_GENERATED_SCHEMA_REL = "src/schema.generated.ts";
|
|
41
|
+
|
|
42
|
+
/** @see LEGACY_GENERATED_SCHEMA_REL */
|
|
43
|
+
export const LEGACY_DECLARE_SCHEMA_REL = "src/schema.decl.ts";
|
|
37
44
|
|
|
38
45
|
/**
|
|
39
46
|
* Map a store.sql driver id to an emit dialect.
|
|
@@ -306,8 +313,16 @@ export async function maybeEmitDomainSchema(
|
|
|
306
313
|
options: EmitDomainSchemaOptions,
|
|
307
314
|
): Promise<{ readonly emitted: boolean; readonly path?: string; readonly tableCount?: number }> {
|
|
308
315
|
const cwd = options.cwd;
|
|
309
|
-
const
|
|
310
|
-
|
|
316
|
+
const resolved = resolveEmitPaths(cwd, {
|
|
317
|
+
...(options.declarePath !== undefined ? { declare: options.declarePath } : {}),
|
|
318
|
+
...(options.outPath !== undefined ? { generated: options.outPath } : {}),
|
|
319
|
+
});
|
|
320
|
+
const declareAbs = resolved.declarePath;
|
|
321
|
+
const outAbs = resolved.outPath;
|
|
322
|
+
const declareRel = declareAbs.startsWith(`${cwd}/`)
|
|
323
|
+
? declareAbs.slice(cwd.length + 1)
|
|
324
|
+
: declareAbs;
|
|
325
|
+
const outRel = outAbs.startsWith(`${cwd}/`) ? outAbs.slice(cwd.length + 1) : outAbs;
|
|
311
326
|
const declareFile = Bun.file(declareAbs);
|
|
312
327
|
|
|
313
328
|
let appTables: SchemaTableDecl[] = [];
|
|
@@ -332,8 +347,6 @@ export async function maybeEmitDomainSchema(
|
|
|
332
347
|
}
|
|
333
348
|
|
|
334
349
|
const tables = mergeSchemaTables(appTables, fromPlugins);
|
|
335
|
-
const outRel = options.outPath ?? DEFAULT_GENERATED_SCHEMA_REL;
|
|
336
|
-
const outAbs = resolve(cwd, outRel);
|
|
337
350
|
const source = emitDrizzleSource(tables, options.dialect, { relations: appRelations });
|
|
338
351
|
await mkdir(dirname(outAbs), { recursive: true });
|
|
339
352
|
await writeFile(outAbs, source, "utf8");
|
|
@@ -345,6 +358,7 @@ export async function maybeEmitDomainSchema(
|
|
|
345
358
|
|
|
346
359
|
/**
|
|
347
360
|
* Resolve declare/out paths from an optional db config slice.
|
|
361
|
+
* Prefers explicit config, then new `src/db/*` defaults, then legacy `src/*`.
|
|
348
362
|
*
|
|
349
363
|
* @param cwd - Project root
|
|
350
364
|
* @param db - Config `db` block
|
|
@@ -353,9 +367,29 @@ export function resolveEmitPaths(
|
|
|
353
367
|
cwd: string,
|
|
354
368
|
db?: { readonly declare?: string; readonly generated?: string },
|
|
355
369
|
): { readonly declarePath: string; readonly outPath: string } {
|
|
370
|
+
let declareRel = db?.declare;
|
|
371
|
+
if (!declareRel) {
|
|
372
|
+
if (existsSync(join(cwd, DEFAULT_DECLARE_SCHEMA_REL))) {
|
|
373
|
+
declareRel = DEFAULT_DECLARE_SCHEMA_REL;
|
|
374
|
+
} else if (existsSync(join(cwd, LEGACY_DECLARE_SCHEMA_REL))) {
|
|
375
|
+
declareRel = LEGACY_DECLARE_SCHEMA_REL;
|
|
376
|
+
} else {
|
|
377
|
+
declareRel = DEFAULT_DECLARE_SCHEMA_REL;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let outRel = db?.generated;
|
|
382
|
+
if (!outRel) {
|
|
383
|
+
const preferLegacy =
|
|
384
|
+
declareRel === LEGACY_DECLARE_SCHEMA_REL ||
|
|
385
|
+
(!existsSync(join(cwd, DEFAULT_DECLARE_SCHEMA_REL)) &&
|
|
386
|
+
existsSync(join(cwd, LEGACY_GENERATED_SCHEMA_REL)));
|
|
387
|
+
outRel = preferLegacy ? LEGACY_GENERATED_SCHEMA_REL : DEFAULT_GENERATED_SCHEMA_REL;
|
|
388
|
+
}
|
|
389
|
+
|
|
356
390
|
return {
|
|
357
|
-
declarePath: join(cwd,
|
|
358
|
-
outPath: join(cwd,
|
|
391
|
+
declarePath: join(cwd, declareRel),
|
|
392
|
+
outPath: join(cwd, outRel),
|
|
359
393
|
};
|
|
360
394
|
}
|
|
361
395
|
|
|
@@ -473,7 +473,7 @@ export function createStoreRuntime(options: CreateStoreRuntimeOptions): StoreRun
|
|
|
473
473
|
},
|
|
474
474
|
putTier1(effects, value, dimsByResource) {
|
|
475
475
|
const keys = tier1KeysForReads(effects, dimsByResource);
|
|
476
|
-
const resources = effects.reads ?? [];
|
|
476
|
+
const resources = (effects.reads ?? []).filter((r): r is ResourceRef => r !== "runs");
|
|
477
477
|
for (const key of keys) {
|
|
478
478
|
cache.set({
|
|
479
479
|
tier: 1,
|
|
@@ -39,6 +39,8 @@ describe("store.schema.table + field.*", () => {
|
|
|
39
39
|
expect(notes.columns.createdAt.sqlType).toBe("integer");
|
|
40
40
|
expect(notes.columns.id.defaultFnKind).toBe("id");
|
|
41
41
|
expect(notes.columns.createdAt.defaultFnKind).toBe("now");
|
|
42
|
+
expect(typeof notes.id.getSQL).toBe("function");
|
|
43
|
+
expect(() => notes.id.getSQL()).toThrow(/type bridge/);
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
test("PII masks via declaration path without Drizzle metadata", async () => {
|
|
@@ -34,6 +34,10 @@ export interface ColumnReference {
|
|
|
34
34
|
/**
|
|
35
35
|
* Finalized column declaration — also satisfies {@link ColumnDef} so
|
|
36
36
|
* {@link classificationsFromTable} / runtime masking work without Drizzle.
|
|
37
|
+
*
|
|
38
|
+
* `getSQL` is a structural bridge so drizzle-orm operators (`eq`, `isNull`, …)
|
|
39
|
+
* accept abstract columns in TypeScript. OKE’s SQL compiler never calls it —
|
|
40
|
+
* it reads {@link SchemaColumnDecl.sqlName} / metadata directly.
|
|
37
41
|
*/
|
|
38
42
|
export interface SchemaColumnDecl extends ColumnDef {
|
|
39
43
|
/** JS object key (e.g. `createdAt`). */
|
|
@@ -57,6 +61,12 @@ export interface SchemaColumnDecl extends ColumnDef {
|
|
|
57
61
|
readonly references?: ColumnReference;
|
|
58
62
|
/** Optional human description for Console / docs (falls back to the JS key). */
|
|
59
63
|
readonly description?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Drizzle `SQLWrapper` structural match — never invoked at runtime.
|
|
66
|
+
*
|
|
67
|
+
* @returns never (throws if called)
|
|
68
|
+
*/
|
|
69
|
+
getSQL(): never;
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
/** Table from {@link store.schema.table} — extends {@link TableHandle}. */
|
|
@@ -171,6 +181,11 @@ function createBuilder(state: FieldState): FieldBuilder {
|
|
|
171
181
|
...(state.classification ? { classification: state.classification } : {}),
|
|
172
182
|
...(state.description !== undefined ? { description: state.description } : {}),
|
|
173
183
|
...(state.references ? { references: state.references } : {}),
|
|
184
|
+
getSQL(): never {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`SchemaColumnDecl.getSQL("${sqlName}") is a type bridge — OKE compiles columns directly`,
|
|
187
|
+
);
|
|
188
|
+
},
|
|
174
189
|
};
|
|
175
190
|
},
|
|
176
191
|
};
|
|
@@ -30,7 +30,7 @@ export interface SeedDef {
|
|
|
30
30
|
export type SeedCategory = "dev" | "prod";
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* Freeze a seed declaration for `src/seed/index.ts` default export.
|
|
33
|
+
* Freeze a seed declaration for `src/db/seed/index.ts` default export.
|
|
34
34
|
*
|
|
35
35
|
* @param def - Essential / dev / prod function bags
|
|
36
36
|
*/
|
package/src/index.ts
CHANGED
package/src/kernel/app.ts
CHANGED
|
@@ -59,7 +59,9 @@ import {
|
|
|
59
59
|
logDevRequest,
|
|
60
60
|
shouldLogDevRequests,
|
|
61
61
|
} from "../runtime/dev-request-log.ts";
|
|
62
|
+
import { fail, type FlowFailure } from "./errors.ts";
|
|
62
63
|
import {
|
|
64
|
+
isFlowFailure,
|
|
63
65
|
mergeHooks,
|
|
64
66
|
runPipeline,
|
|
65
67
|
type HookFn,
|
|
@@ -160,6 +162,18 @@ export interface OkeOptions {
|
|
|
160
162
|
readonly docker?: BootOptions["docker"];
|
|
161
163
|
/** Optional `oke.config.ts` document consumed at boot. */
|
|
162
164
|
readonly config?: BootOptions["config"];
|
|
165
|
+
/**
|
|
166
|
+
* Compiled Manifest for flows with no hand-declared `effects` — highest
|
|
167
|
+
* priority source for the boot-time capability stamp (see
|
|
168
|
+
* {@link BootOptions.manifest}).
|
|
169
|
+
*/
|
|
170
|
+
readonly manifest?: BootOptions["manifest"];
|
|
171
|
+
/**
|
|
172
|
+
* Project root for a lazy, best-effort effects extraction when a flow has
|
|
173
|
+
* no hand-declared `effects` and no {@link manifest} was given (see
|
|
174
|
+
* {@link BootOptions.rootDir}).
|
|
175
|
+
*/
|
|
176
|
+
readonly rootDir?: BootOptions["rootDir"];
|
|
163
177
|
/** Pre-built element runtimes (skip construction at boot when present). */
|
|
164
178
|
readonly elements?: BootOptions["elements"];
|
|
165
179
|
/** Vault create options (chain / allowDevFallbacks / secrets). */
|
|
@@ -359,6 +373,10 @@ export interface OkeApp<D extends Record<string, unknown> = {}, R extends AppRou
|
|
|
359
373
|
readonly operator?: FxOperator;
|
|
360
374
|
/** Pre-resolved principal (either plane). */
|
|
361
375
|
readonly principal?: ResolvedPrincipal;
|
|
376
|
+
/** Parent WideEvent id when this execution was caused by another. */
|
|
377
|
+
readonly parentId?: string;
|
|
378
|
+
/** Explicit run / WideEvent id (defaults to a new UUID). */
|
|
379
|
+
readonly runId?: string;
|
|
362
380
|
},
|
|
363
381
|
): Promise<ExecuteResult>;
|
|
364
382
|
/**
|
|
@@ -381,8 +399,13 @@ export interface OkeApp<D extends Record<string, unknown> = {}, R extends AppRou
|
|
|
381
399
|
*
|
|
382
400
|
* @param signal - Signal name or handle
|
|
383
401
|
* @param payload - Payload
|
|
402
|
+
* @param meta - Optional envelope from the bus (producer run id)
|
|
384
403
|
*/
|
|
385
|
-
dispatchSignal(
|
|
404
|
+
dispatchSignal(
|
|
405
|
+
signal: NamedRef,
|
|
406
|
+
payload?: unknown,
|
|
407
|
+
meta?: { readonly parentRunId?: string; readonly messageId?: string },
|
|
408
|
+
): Promise<ExecuteResult[]>;
|
|
386
409
|
/**
|
|
387
410
|
* Invoke all flows bound to an `every` interval.
|
|
388
411
|
*
|
|
@@ -637,8 +660,12 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
637
660
|
}
|
|
638
661
|
}
|
|
639
662
|
|
|
640
|
-
async function handleSignalFire(
|
|
641
|
-
|
|
663
|
+
async function handleSignalFire(
|
|
664
|
+
name: string,
|
|
665
|
+
payload: unknown,
|
|
666
|
+
meta?: { readonly parentRunId?: string; readonly messageId?: string },
|
|
667
|
+
): Promise<void> {
|
|
668
|
+
await app.dispatchSignal(name, payload, meta);
|
|
642
669
|
}
|
|
643
670
|
|
|
644
671
|
async function handleDurableResume(): Promise<void> {
|
|
@@ -812,6 +839,8 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
812
839
|
env: bootEnv,
|
|
813
840
|
docker: overrides?.docker ?? options.docker,
|
|
814
841
|
config: overrides?.config ?? options.config,
|
|
842
|
+
manifest: overrides?.manifest ?? options.manifest,
|
|
843
|
+
rootDir: overrides?.rootDir ?? options.rootDir,
|
|
815
844
|
elements: overrides?.elements ?? options.elements,
|
|
816
845
|
secrets: [...baseSecrets, ...pluginSecrets],
|
|
817
846
|
vault: overrides?.vault ?? options.vault,
|
|
@@ -918,6 +947,10 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
918
947
|
readonly originPrincipal?: FxPrincipal;
|
|
919
948
|
/** Explicit locale override for {@link Fx.t} / channel sends. */
|
|
920
949
|
readonly locale?: string;
|
|
950
|
+
/** Parent WideEvent id when this execution was caused by another. */
|
|
951
|
+
readonly parentId?: string;
|
|
952
|
+
/** Explicit run / WideEvent id (defaults to a new UUID). */
|
|
953
|
+
readonly runId?: string;
|
|
921
954
|
},
|
|
922
955
|
): Promise<ExecuteResult> {
|
|
923
956
|
registerFlow(flowDef);
|
|
@@ -957,6 +990,8 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
957
990
|
readonly principal?: ResolvedPrincipal;
|
|
958
991
|
readonly originPrincipal?: FxPrincipal;
|
|
959
992
|
readonly locale?: string;
|
|
993
|
+
readonly parentId?: string;
|
|
994
|
+
readonly runId?: string;
|
|
960
995
|
};
|
|
961
996
|
readonly resolvedLocale: string;
|
|
962
997
|
readonly defaultLocale: string;
|
|
@@ -991,6 +1026,15 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
991
1026
|
const now = options.fx?.now ?? booted?.clock?.now ?? (() => Date.now());
|
|
992
1027
|
const startedAt = now();
|
|
993
1028
|
const telemetry = createRunTelemetry();
|
|
1029
|
+
const inboundId =
|
|
1030
|
+
extras?.request?.headers.get("x-request-id")?.trim() ||
|
|
1031
|
+
extras?.request?.headers.get("x-correlation-id")?.trim() ||
|
|
1032
|
+
undefined;
|
|
1033
|
+
const runId =
|
|
1034
|
+
extras?.runId ??
|
|
1035
|
+
(inboundId && inboundId.length > 0 ? inboundId : undefined) ??
|
|
1036
|
+
crypto.randomUUID();
|
|
1037
|
+
const parentId = extras?.parentId;
|
|
994
1038
|
|
|
995
1039
|
// Element pipeline wiring only kicks in once booted — `autoBoot: false`
|
|
996
1040
|
// keeps intentional pre-boot unit tests ungated (no elements either).
|
|
@@ -1057,6 +1101,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1057
1101
|
const journal = createJournal({
|
|
1058
1102
|
store,
|
|
1059
1103
|
now,
|
|
1104
|
+
id: () => runId,
|
|
1060
1105
|
// Hold the run lease for the request's lifetime — a crash mid-run
|
|
1061
1106
|
// leaves an expired lease another instance can reclaim and resume.
|
|
1062
1107
|
...(hasJournalLease(store) ? { lease: { instanceId, leaseMs } } : {}),
|
|
@@ -1071,6 +1116,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1071
1116
|
flow: flowDef.name,
|
|
1072
1117
|
effects: flowDef.effects,
|
|
1073
1118
|
runTelemetry: telemetry,
|
|
1119
|
+
runId,
|
|
1074
1120
|
now,
|
|
1075
1121
|
i18n: {
|
|
1076
1122
|
locale: resolvedLocale,
|
|
@@ -1090,6 +1136,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1090
1136
|
aiRuntime: booted.ai,
|
|
1091
1137
|
}
|
|
1092
1138
|
: {}),
|
|
1139
|
+
runsRuntime: booted?.runs ?? (isRunsRuntimeLike(options.runs) ? options.runs : undefined),
|
|
1093
1140
|
...(journalSession ? { journal: journalSession, durable: true } : {}),
|
|
1094
1141
|
callHandler: async (name, callInput) => {
|
|
1095
1142
|
const target = flowsByName.get(name);
|
|
@@ -1103,6 +1150,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1103
1150
|
{
|
|
1104
1151
|
originPrincipal: freezePrincipal(fx.principal),
|
|
1105
1152
|
locale: resolvedLocale,
|
|
1153
|
+
parentId: runId,
|
|
1106
1154
|
},
|
|
1107
1155
|
);
|
|
1108
1156
|
if (inner.failure) return inner.failure;
|
|
@@ -1174,10 +1222,32 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1174
1222
|
input: ctx.input,
|
|
1175
1223
|
wakeAt: sleeping.wakeAt,
|
|
1176
1224
|
});
|
|
1177
|
-
} else if (!sleeping && result
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1225
|
+
} else if (!sleeping && isTerminalFailure(result)) {
|
|
1226
|
+
const terminalErr = result.failure ?? result.ctx.error;
|
|
1227
|
+
if (flowDef.compensate) {
|
|
1228
|
+
try {
|
|
1229
|
+
const completedSteps = journalSession.run.entries
|
|
1230
|
+
.filter((e) => e.kind === "step")
|
|
1231
|
+
.map((e) => e.name);
|
|
1232
|
+
await flowDef.compensate(
|
|
1233
|
+
{
|
|
1234
|
+
input: ctx.input as never,
|
|
1235
|
+
error: terminalErr,
|
|
1236
|
+
completedSteps,
|
|
1237
|
+
},
|
|
1238
|
+
fx,
|
|
1239
|
+
);
|
|
1240
|
+
} catch (compErr) {
|
|
1241
|
+
await journalSession.commit("failed", {
|
|
1242
|
+
error: `compensate:${failureCodeOf(compErr)}`,
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
if (journalSession.run.status === "running") {
|
|
1247
|
+
await journalSession.commit("failed", {
|
|
1248
|
+
error: failureCodeOf(terminalErr),
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1181
1251
|
} else if (!sleeping) {
|
|
1182
1252
|
await journalSession.commit("completed", { output: result.output });
|
|
1183
1253
|
}
|
|
@@ -1187,6 +1257,10 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1187
1257
|
booted?.runs ?? (isRunsRuntimeLike(options.runs) ? options.runs : undefined);
|
|
1188
1258
|
if (runsRuntime) {
|
|
1189
1259
|
const archiveCleartext = archiveFromInput(ctx.input, options.archiveInputFields);
|
|
1260
|
+
const replayInput = redactArchivedFields(ctx.input, options.archiveInputFields);
|
|
1261
|
+
const recordFailure =
|
|
1262
|
+
result.failure ??
|
|
1263
|
+
(isTerminalFailure(result) ? failureFromUnknown(result.ctx.error) : undefined);
|
|
1190
1264
|
await runsRuntime.record(
|
|
1191
1265
|
{
|
|
1192
1266
|
flow: flowDef,
|
|
@@ -1196,7 +1270,10 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1196
1270
|
telemetry,
|
|
1197
1271
|
startedAt,
|
|
1198
1272
|
endedAt,
|
|
1199
|
-
failure:
|
|
1273
|
+
failure: recordFailure,
|
|
1274
|
+
id: runId,
|
|
1275
|
+
input: replayInput,
|
|
1276
|
+
...(parentId !== undefined ? { parentId } : {}),
|
|
1200
1277
|
},
|
|
1201
1278
|
archiveCleartext,
|
|
1202
1279
|
);
|
|
@@ -1480,12 +1557,16 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1480
1557
|
|
|
1481
1558
|
return respond(encodeExecuteResult(result));
|
|
1482
1559
|
},
|
|
1483
|
-
async dispatchSignal(signal, payload) {
|
|
1560
|
+
async dispatchSignal(signal, payload, meta) {
|
|
1484
1561
|
const name = resolveName(signal);
|
|
1485
1562
|
const results: ExecuteResult[] = [];
|
|
1486
1563
|
for (const b of adopted) {
|
|
1487
1564
|
if (b.trigger.kind === "signal" && b.trigger.name === name) {
|
|
1488
|
-
results.push(
|
|
1565
|
+
results.push(
|
|
1566
|
+
await execute(b.flow, payload, b.trigger as SignalAsTrigger, {
|
|
1567
|
+
...(meta?.parentRunId !== undefined ? { parentId: meta.parentRunId } : {}),
|
|
1568
|
+
}),
|
|
1569
|
+
);
|
|
1489
1570
|
}
|
|
1490
1571
|
}
|
|
1491
1572
|
return results;
|
|
@@ -1555,6 +1636,26 @@ function compileHttpBinding(binding: Binding, aot: boolean): CompiledRoute {
|
|
|
1555
1636
|
);
|
|
1556
1637
|
}
|
|
1557
1638
|
|
|
1639
|
+
/** True when the pipeline ended in a terminal failure (not sleep park). */
|
|
1640
|
+
function isTerminalFailure(result: PipelineResult): boolean {
|
|
1641
|
+
if (result.failure) return true;
|
|
1642
|
+
return result.ctx.error !== undefined && !isJournalSuspend(result.ctx.error);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/** Machine-readable code for journal / Runs failure fields. */
|
|
1646
|
+
function failureCodeOf(err: unknown): string {
|
|
1647
|
+
if (isFlowFailure(err)) return err.error.code;
|
|
1648
|
+
if (err instanceof Error && err.name && err.name !== "Error") return err.name;
|
|
1649
|
+
if (err instanceof Error && err.message) return err.message.slice(0, 120);
|
|
1650
|
+
return "Error";
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/** Coerce a thrown value into a FlowFailure for Runs wide events. */
|
|
1654
|
+
function failureFromUnknown(err: unknown): FlowFailure {
|
|
1655
|
+
if (isFlowFailure(err)) return err;
|
|
1656
|
+
return fail(failureCodeOf(err), {});
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1558
1659
|
/**
|
|
1559
1660
|
* Pull personal fields from validated input for crypto-shred archival.
|
|
1560
1661
|
*
|
|
@@ -1576,6 +1677,26 @@ function archiveFromInput(
|
|
|
1576
1677
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
1577
1678
|
}
|
|
1578
1679
|
|
|
1680
|
+
/**
|
|
1681
|
+
* Clone input with archived personal fields replaced by shred markers so the
|
|
1682
|
+
* replay snapshot never retains cleartext for those keys.
|
|
1683
|
+
*
|
|
1684
|
+
* @param input - Validated flow input
|
|
1685
|
+
* @param fields - Property names that go to {@link archiveFromInput}
|
|
1686
|
+
*/
|
|
1687
|
+
function redactArchivedFields(input: unknown, fields: readonly string[] | undefined): unknown {
|
|
1688
|
+
if (input === undefined) return undefined;
|
|
1689
|
+
if (!fields || fields.length === 0) return input;
|
|
1690
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return input;
|
|
1691
|
+
const obj = { ...(input as Record<string, unknown>) };
|
|
1692
|
+
for (const name of fields) {
|
|
1693
|
+
if (typeof obj[name] === "string" && (obj[name] as string).length > 0) {
|
|
1694
|
+
obj[name] = "[archived]";
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return obj;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1579
1700
|
/**
|
|
1580
1701
|
* Deep-merge channel template catalogs (later parts win per locale).
|
|
1581
1702
|
*
|
|
@@ -7,6 +7,7 @@ import { memorySignalDriver } from "../../drivers/signal-memory.ts";
|
|
|
7
7
|
import { createBunSignalRedisClient, redisSignalDriver } from "../../drivers/signal-redis.ts";
|
|
8
8
|
import type { SignalRedisClientLike } from "../../drivers/signal-types.ts";
|
|
9
9
|
import { createSignalRuntime, type SignalRuntime } from "../../elements/signal.ts";
|
|
10
|
+
import { emitBootWarn } from "../../runtime/boot-warn.ts";
|
|
10
11
|
import type { BootOptions } from "../boot.ts";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -35,7 +36,7 @@ let signalRedisWarned = false;
|
|
|
35
36
|
function warnSignalRedisProcessLocal(): void {
|
|
36
37
|
if (signalRedisWarned) return;
|
|
37
38
|
signalRedisWarned = true;
|
|
38
|
-
|
|
39
|
+
emitBootWarn(
|
|
39
40
|
"oke boot: drivers.signal redis — emit relays to Redis, but consume/live/drain use a " +
|
|
40
41
|
"process-local outbox (not multi-instance competing consumers). Prefer a shared durable " +
|
|
41
42
|
"outbox path for multi-process tests, or a single consumer instance, until Redis Streams " +
|
|
@@ -129,7 +130,10 @@ export async function bindSignal(
|
|
|
129
130
|
if (seen.has(name)) continue;
|
|
130
131
|
seen.add(name);
|
|
131
132
|
await bus.subscribe(name, `oke:${name}`, async (msg) => {
|
|
132
|
-
await handler(name, msg.payload
|
|
133
|
+
await handler(name, msg.payload, {
|
|
134
|
+
...(msg.parentRunId !== undefined ? { parentRunId: msg.parentRunId } : {}),
|
|
135
|
+
messageId: msg.id,
|
|
136
|
+
});
|
|
133
137
|
});
|
|
134
138
|
}
|
|
135
139
|
}
|
|
@@ -16,6 +16,7 @@ import { sqliteDriver } from "../../drivers/sqlite.ts";
|
|
|
16
16
|
import type { FilesDriver, IndexDriver, KvDriver, SqlDriver } from "../../drivers/types.ts";
|
|
17
17
|
import { createStoreRuntime, type StoreRuntime } from "../../elements/store.ts";
|
|
18
18
|
import type { StoreDecl } from "../../elements/store/declare.ts";
|
|
19
|
+
import { emitBootWarn } from "../../runtime/boot-warn.ts";
|
|
19
20
|
import type { BootOptions } from "../boot.ts"; // type-only — no cycle at runtime
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -43,7 +44,7 @@ export function resetFilesFsWarnForTests(): void {
|
|
|
43
44
|
function warnFilesFsMultiInstance(): void {
|
|
44
45
|
if (filesFsWarned) return;
|
|
45
46
|
filesFsWarned = true;
|
|
46
|
-
|
|
47
|
+
emitBootWarn(
|
|
47
48
|
'oke boot: drivers.store.files "fs" is single-host — each instance sees its own ' +
|
|
48
49
|
"filesystem (temp root when unbound). For horizontal scale use drivers.store.files s3 " +
|
|
49
50
|
"(or an explicitly shared volume root, still single-host semantics).",
|