okengine 0.12.0 → 0.13.0
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 +1 -1
- package/site/content/docs/elements/store.mdx +20 -0
- package/site/content/docs/providers/index.mdx +1 -0
- package/site/content/docs/recipes/dragonfly.mdx +4 -3
- package/site/content/docs/recipes/redis.mdx +4 -5
- package/site/content/docs/recipes/valkey.mdx +5 -3
- package/site/content/docs/reference/configuration.mdx +3 -1
- package/site/content/docs/reference/environment-variables.mdx +5 -5
- package/src/cli/load-config.images.test.ts +1 -0
- package/src/compiler/extract.test.ts +14 -0
- package/src/compiler/extract.ts +1 -0
- package/src/console/ui-next/dist/assets/{flows-page-C_Tas1E1.js → flows-page-Dg8CTE29.js} +1 -1
- package/src/console/ui-next/dist/assets/{index-DX248G39.js → index-Bp-R7jtM.js} +3 -3
- package/src/console/ui-next/dist/assets/{observability-page-BwVS5Jvm.js → observability-page-DAnpEaq1.js} +1 -1
- package/src/console/ui-next/dist/assets/{units-page-C5KOI7qG.js → units-page-BtQ0bqMe.js} +1 -1
- package/src/console/ui-next/dist/assets/{vault-page-CSOYMHhO.js → vault-page-Ca-MvcmJ.js} +1 -1
- package/src/console/ui-next/dist/index.html +1 -1
- package/src/docker/helpers.ts +28 -0
- package/src/docker/recipes/dragonfly.ts +3 -6
- package/src/docker/recipes/redis.ts +2 -6
- package/src/docker/recipes/valkey.ts +2 -6
- package/src/drivers/types.ts +1 -1
- package/src/elements/gate/runtime.ts +3 -3
- package/src/elements/store/declare.ts +7 -0
- package/src/elements/store/kv-sql.test.ts +86 -0
- package/src/elements/store/kv-sql.ts +178 -0
- package/src/elements/store/runtime.ts +35 -9
- package/src/elements/store.test.ts +2 -0
- package/src/kernel/boot-bind/store.test.ts +150 -1
- package/src/kernel/boot-bind/store.ts +66 -1
- package/src/manifest/diff.test.ts +13 -0
- package/src/manifest/diff.ts +15 -0
- package/src/manifest/types.ts +5 -0
|
@@ -34,12 +34,20 @@ import type { BootOptions } from "../boot.ts"; // type-only — no cycle at runt
|
|
|
34
34
|
* @param docker - Prefer compose URLs when opening postgres/redis
|
|
35
35
|
*/
|
|
36
36
|
let filesFsWarned = false;
|
|
37
|
+
let kvCacheShapedWarned = false;
|
|
38
|
+
let kvMemoryDurableWarned = false;
|
|
37
39
|
|
|
38
40
|
/** Test helper — reset the one-shot `fs` multi-instance warn. */
|
|
39
41
|
export function resetFilesFsWarnForTests(): void {
|
|
40
42
|
filesFsWarned = false;
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
/** Test helper — reset durable-KV boot warns. */
|
|
46
|
+
export function resetKvDurableWarnsForTests(): void {
|
|
47
|
+
kvCacheShapedWarned = false;
|
|
48
|
+
kvMemoryDurableWarned = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
/**
|
|
44
52
|
* Warn once when `drivers.store.files` is `fs` — per-process (or per-pod)
|
|
45
53
|
* filesystem visibility under horizontal scale.
|
|
@@ -54,6 +62,39 @@ function warnFilesFsMultiInstance(): void {
|
|
|
54
62
|
);
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Warn once: default KV namespaces are cache-shaped (no AOF) unless
|
|
67
|
+
* `{ durable: true }` persists them in SQL (`oke_kv`).
|
|
68
|
+
*/
|
|
69
|
+
function warnKvCacheShaped(names: readonly string[]): void {
|
|
70
|
+
if (kvCacheShapedWarned) return;
|
|
71
|
+
kvCacheShapedWarned = true;
|
|
72
|
+
const listed = names
|
|
73
|
+
.slice(0, 4)
|
|
74
|
+
.map((n) => `"${n}"`)
|
|
75
|
+
.join(", ");
|
|
76
|
+
const extra = names.length > 4 ? ` (and ${String(names.length - 4)} more)` : "";
|
|
77
|
+
emitBootWarn(
|
|
78
|
+
`oke boot: store.kv ${listed}${extra} is cache-shaped — a Redis recreate drops keys ` +
|
|
79
|
+
"(no AOF). Declare `{ durable: true }` on a separate namespace to persist in " +
|
|
80
|
+
"your SQL database (`oke_kv`).",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Warn once: in-process memory SQL cannot honor `{ durable: true }`.
|
|
86
|
+
*/
|
|
87
|
+
function warnKvMemoryDurable(names: readonly string[]): void {
|
|
88
|
+
if (kvMemoryDurableWarned) return;
|
|
89
|
+
kvMemoryDurableWarned = true;
|
|
90
|
+
const listed = names.map((n) => `"${n}"`).join(", ");
|
|
91
|
+
emitBootWarn(
|
|
92
|
+
`oke boot: store.kv ${listed} declared { durable: true } but drivers.store.sql is ` +
|
|
93
|
+
'"memory" — keys are process-local and vanish on restart. Use drivers.store.sql ' +
|
|
94
|
+
"postgres with DATABASE_URL.",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
57
98
|
export function bindStore(
|
|
58
99
|
options: BootOptions,
|
|
59
100
|
env: ConfigEnv,
|
|
@@ -65,9 +106,28 @@ export function bindStore(
|
|
|
65
106
|
const filesId = resolveFilesDriverId(options, env, docker);
|
|
66
107
|
const indexId = resolveIndexDriverId(options, env, docker);
|
|
67
108
|
if (filesId === "fs") warnFilesFsMultiInstance();
|
|
109
|
+
const kvDecls = (options.stores ?? []).filter(isKvDecl);
|
|
110
|
+
const durableKvDecls = kvDecls.filter((d) => d.durable === true);
|
|
111
|
+
const cacheKvDecls = kvDecls.filter((d) => d.durable !== true);
|
|
112
|
+
if (durableKvDecls.length > 0 && sqlId === "postgres") {
|
|
113
|
+
const url = process.env.DATABASE_URL ?? process.env.OKE_STORE_SQL_URL;
|
|
114
|
+
if (!url) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
docker
|
|
117
|
+
? "oke boot: durable store.kv needs DATABASE_URL (did `oke dev` write .env.local?)"
|
|
118
|
+
: "oke boot: durable store.kv needs DATABASE_URL",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
68
122
|
const sqlUrl = sqlUrlFor(sqlId, docker, env);
|
|
69
123
|
const kvUrl = kvUrlFor(kvId, docker);
|
|
70
124
|
const filesRoot = filesRootFor(filesId);
|
|
125
|
+
if (kvId === "redis" && env !== "test" && cacheKvDecls.length > 0) {
|
|
126
|
+
warnKvCacheShaped(cacheKvDecls.map((d) => d.name));
|
|
127
|
+
}
|
|
128
|
+
if (sqlId === "memory" && env !== "test" && durableKvDecls.length > 0) {
|
|
129
|
+
warnKvMemoryDurable(durableKvDecls.map((d) => d.name));
|
|
130
|
+
}
|
|
71
131
|
|
|
72
132
|
const sqlBindings: Record<string, { name: string; primary: { url: string } }> = {};
|
|
73
133
|
const kvBindings: Record<string, { url?: string }> = {};
|
|
@@ -81,7 +141,11 @@ export function bindStore(
|
|
|
81
141
|
primary: { url: sqlUrl },
|
|
82
142
|
};
|
|
83
143
|
} else if (isKvDecl(decl)) {
|
|
84
|
-
|
|
144
|
+
if (decl.durable === true) {
|
|
145
|
+
kvBindings[decl.name] = {};
|
|
146
|
+
} else {
|
|
147
|
+
kvBindings[decl.name] = kvUrl !== undefined ? { url: kvUrl } : {};
|
|
148
|
+
}
|
|
85
149
|
} else if (isFilesDecl(decl)) {
|
|
86
150
|
filesBindings[decl.name] = filesRoot !== undefined ? { root: filesRoot } : {};
|
|
87
151
|
} else if (isIndexDecl(decl)) {
|
|
@@ -114,6 +178,7 @@ export function bindStore(
|
|
|
114
178
|
index: indexBindings,
|
|
115
179
|
now,
|
|
116
180
|
domainDdl,
|
|
181
|
+
sqlUrl,
|
|
117
182
|
});
|
|
118
183
|
for (const decl of options.stores ?? []) {
|
|
119
184
|
store.register?.(decl);
|
|
@@ -309,6 +309,19 @@ describe("diffManifest — additional branches", () => {
|
|
|
309
309
|
expect(hasCategory(result.changes, "contract-breaking", "facet")).toBe(true);
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
+
test("store.kv durable on is effect-widening; off is contract-breaking", async () => {
|
|
313
|
+
const before = await loadBase();
|
|
314
|
+
before.stores!.db!.facet = "kv";
|
|
315
|
+
before.stores!.db!.namespaces = ["cache"];
|
|
316
|
+
delete before.stores!.db!.tables;
|
|
317
|
+
const on = clone(before);
|
|
318
|
+
on.stores!.db!.durable = true;
|
|
319
|
+
expect(hasCategory(diffManifest(before, on).changes, "effect-widening", "durable")).toBe(true);
|
|
320
|
+
const off = clone(on);
|
|
321
|
+
off.stores!.db!.durable = false;
|
|
322
|
+
expect(hasCategory(diffManifest(on, off).changes, "contract-breaking", "durable")).toBe(true);
|
|
323
|
+
});
|
|
324
|
+
|
|
312
325
|
test("clocks, vault, channels, journeys, drivers, i18n, topology, images", async () => {
|
|
313
326
|
const before = await loadBase();
|
|
314
327
|
const after = clone(before);
|
package/src/manifest/diff.ts
CHANGED
|
@@ -779,6 +779,21 @@ function diffStore(before: Store, after: Store, path: string, out: ManifestChang
|
|
|
779
779
|
diffStringSet(before.namespaces, after.namespaces, `${path}/namespaces`, out);
|
|
780
780
|
diffStringSet(before.buckets, after.buckets, `${path}/buckets`, out);
|
|
781
781
|
diffStringSet(before.indexes, after.indexes, `${path}/indexes`, out);
|
|
782
|
+
if (
|
|
783
|
+
before.durable !== after.durable &&
|
|
784
|
+
(before.durable !== undefined || after.durable !== undefined)
|
|
785
|
+
) {
|
|
786
|
+
out.push(
|
|
787
|
+
change(
|
|
788
|
+
`${path}/durable`,
|
|
789
|
+
after.durable === true ? "effect-widening" : "contract-breaking",
|
|
790
|
+
"changed",
|
|
791
|
+
before.durable,
|
|
792
|
+
after.durable,
|
|
793
|
+
`store durable ${String(before.durable)} → ${String(after.durable)}`,
|
|
794
|
+
),
|
|
795
|
+
);
|
|
796
|
+
}
|
|
782
797
|
if (!deepEqual(before.classifications, after.classifications)) {
|
|
783
798
|
out.push(
|
|
784
799
|
change(
|
package/src/manifest/types.ts
CHANGED
|
@@ -234,6 +234,11 @@ export interface Store {
|
|
|
234
234
|
buckets?: string[];
|
|
235
235
|
indexes?: string[];
|
|
236
236
|
classifications?: Record<string, ClassificationValue>;
|
|
237
|
+
/**
|
|
238
|
+
* KV only — namespace persists in SQL (`oke_kv`), not the cache Redis.
|
|
239
|
+
* Distinct from Flow `durable` (journaled steps).
|
|
240
|
+
*/
|
|
241
|
+
durable?: boolean;
|
|
237
242
|
}
|
|
238
243
|
|
|
239
244
|
/** Named clock / schedule. */
|