okengine 0.3.5 → 0.4.3
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 +6 -0
- package/manifest.v1.schema.json +43 -1
- package/package.json +27 -12
- package/site/content/docs/ai/skills.mdx +11 -7
- package/site/content/docs/console/vault.mdx +4 -0
- package/site/content/docs/elements/ai.mdx +2 -0
- package/site/content/docs/elements/channel.mdx +7 -4
- package/site/content/docs/elements/clock.mdx +2 -4
- package/site/content/docs/elements/flow.mdx +19 -10
- package/site/content/docs/elements/gate.mdx +18 -11
- package/site/content/docs/elements/signal.mdx +9 -12
- package/site/content/docs/elements/store.mdx +69 -8
- package/site/content/docs/elements/vault.mdx +11 -12
- package/site/content/docs/get-started/basic-usage.mdx +76 -41
- package/site/content/docs/get-started/installation.mdx +95 -43
- package/site/content/docs/get-started/introduction.mdx +128 -75
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/why.mdx +141 -0
- package/site/content/docs/plugins/ip-allowlist.mdx +1 -2
- package/site/content/docs/plugins/security-headers.mdx +1 -1
- package/site/content/docs/reference/configuration.mdx +2 -2
- package/site/content/docs/reference/environment-variables.mdx +10 -0
- package/site/content/docs/reference/fx.mdx +56 -3
- package/site/content/docs/reference/plugins.mdx +18 -18
- package/src/cli/competitor-mention-removal.test.ts +117 -0
- package/src/cli/dev.ts +20 -0
- package/src/cli/meilisearch-local.test.ts +69 -0
- package/src/cli/meilisearch-local.ts +188 -0
- package/src/compiler/extract.test.ts +63 -0
- package/src/compiler/extract.ts +36 -15
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +5 -1
- package/src/console/server/channels.ts +2 -0
- package/src/console/server/clock.ts +3 -0
- package/src/console/server/flows.ts +13 -0
- package/src/console/server/gates.ts +2 -0
- package/src/console/server/plugins.ts +20 -1
- package/src/console/server/signals.ts +5 -0
- package/src/console/server/store.test.ts +17 -0
- package/src/console/server/store.ts +35 -1
- package/src/console/ui/channels/types.ts +1 -0
- package/src/console/ui/clock/types.ts +1 -0
- package/src/console/ui/display.test.ts +14 -0
- package/src/console/ui/display.ts +9 -0
- package/src/console/ui/dist/assets/index-CjxwRGVv.js +10 -0
- package/src/console/ui/dist/assets/panel-access-BGv45snf.js +64 -0
- package/src/console/ui/dist/assets/{panel-ai-D_m6WQI8.js → panel-ai-B2S7LEii.js} +1 -1
- package/src/console/ui/dist/assets/{panel-architecture-CKnXFyUx.js → panel-architecture-D7UJh91v.js} +1 -1
- package/src/console/ui/dist/assets/panel-channels-9T3ybqRu.js +1 -0
- package/src/console/ui/dist/assets/panel-clock-Cb1UXGRQ.js +1 -0
- package/src/console/ui/dist/assets/{panel-diff-cdonmH8c.js → panel-diff-DmYbKWmN.js} +1 -1
- package/src/console/ui/dist/assets/panel-flows-PiHwT55z.js +48 -0
- package/src/console/ui/dist/assets/panel-gates-BQGYXvjT.js +1 -0
- package/src/console/ui/dist/assets/panel-overview-BBnRO18l.js +1 -0
- package/src/console/ui/dist/assets/panel-plugins-D0PsmVw2.js +1 -0
- package/src/console/ui/dist/assets/panel-runs-CWuRDe0r.js +1 -0
- package/src/console/ui/dist/assets/panel-signals-Bbg4ewpP.js +1 -0
- package/src/console/ui/dist/assets/panel-store-CPCbsDRa.js +1 -0
- package/src/console/ui/dist/assets/panel-traces-DVAzuA_S.js +1 -0
- package/src/console/ui/dist/assets/panel-vault-D1_MvOmo.js +1 -0
- package/src/console/ui/dist/assets/{rolldown-runtime-CNC7AqOf.js → rolldown-runtime-B0Z9INg1.js} +1 -1
- package/src/console/ui/dist/index.html +2 -2
- package/src/console/ui/gates/types.ts +1 -0
- package/src/console/ui/plugins/fixture.ts +7 -0
- package/src/console/ui/plugins/types.ts +3 -0
- package/src/console/ui/shell/client.ts +3 -0
- package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +7 -2
- package/src/console/ui/shell/panels/clock/ClockPanel.tsx +9 -2
- package/src/console/ui/shell/panels/gates/GatesPanel.tsx +12 -5
- package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +18 -4
- package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +13 -2
- package/src/console/ui/shell/panels/store/StorePanel.tsx +11 -4
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +9 -3
- package/src/console/ui/signals/types.ts +1 -0
- package/src/console/ui/store/fixture.ts +5 -0
- package/src/console/ui/store/types.ts +2 -0
- package/src/docker/compose.ts +5 -0
- package/src/docker/docker.test.ts +41 -0
- package/src/docker/recipes/index.ts +10 -2
- package/src/docker/recipes/meilisearch.ts +31 -0
- package/src/drivers/conformance.test.ts +26 -0
- package/src/drivers/conformance.ts +40 -3
- package/src/drivers/drizzle-dialect.test.ts +4 -0
- package/src/drivers/drizzle-dialect.ts +8 -4
- package/src/drivers/index.ts +18 -2
- package/src/drivers/libsql.ts +179 -0
- package/src/drivers/meilisearch.integration.test.ts +77 -0
- package/src/drivers/meilisearch.test.ts +181 -0
- package/src/drivers/meilisearch.ts +208 -0
- package/src/drivers/memory.ts +4 -4
- package/src/drivers/pglite.ts +79 -0
- package/src/drivers/pgvector.ts +60 -25
- package/src/drivers/types.ts +106 -16
- package/src/drivers/vault-driver-removal.test.ts +6 -0
- package/src/drivers/vault-types.ts +4 -4
- package/src/elements/ai/runtime.ts +6 -0
- package/src/elements/ai.test.ts +22 -0
- package/src/elements/channel/declare.ts +5 -0
- package/src/elements/clock/declare.ts +5 -0
- package/src/elements/clock/durable.ts +7 -1
- package/src/elements/gate/declare.ts +28 -5
- package/src/elements/gate.ts +1 -0
- package/src/elements/signal/declare.ts +5 -0
- package/src/elements/store/declare.ts +10 -2
- package/src/elements/store/index-boot.test.ts +299 -0
- package/src/elements/store/runtime.ts +110 -17
- package/src/elements/store/schema-decl.ts +7 -0
- package/src/elements/store.ts +2 -0
- package/src/elements/vault.test.ts +27 -4
- package/src/elements/vault.ts +1 -1
- package/src/index.ts +4 -0
- package/src/kernel/abort-scope.ts +116 -0
- package/src/kernel/app.ts +12 -2
- package/src/kernel/boot-bind/store.test.ts +68 -1
- package/src/kernel/boot-bind/store.ts +92 -2
- package/src/kernel/concurrency.test.ts +294 -0
- package/src/kernel/concurrency.ts +220 -0
- package/src/kernel/flow.ts +9 -0
- package/src/kernel/fx.test.ts +23 -2
- package/src/kernel/fx.ts +80 -4
- package/src/kernel/index.ts +23 -0
- package/src/kernel/journal.ts +9 -0
- package/src/kernel/plugin/capabilities.test.ts +18 -0
- package/src/kernel/plugin.ts +11 -3
- package/src/kernel/redacted.ts +74 -0
- package/src/kernel/registry.ts +29 -6
- package/src/kernel/router.ts +3 -3
- package/src/manifest/types.ts +21 -0
- package/src/release/measure.ts +4 -0
- package/src/test/provisions.integration.test.ts +1 -1
- package/site/content/docs/get-started/comparison.mdx +0 -65
- package/src/console/ui/dist/assets/index-BWo8R7NR.js +0 -10
- package/src/console/ui/dist/assets/panel-access-C0J2D-a2.js +0 -64
- package/src/console/ui/dist/assets/panel-channels-BOmQ-onL.js +0 -1
- package/src/console/ui/dist/assets/panel-clock-giAq0Ccv.js +0 -1
- package/src/console/ui/dist/assets/panel-flows-DlCU5zjA.js +0 -45
- package/src/console/ui/dist/assets/panel-gates-XclZxWD5.js +0 -1
- package/src/console/ui/dist/assets/panel-overview-BznEOTnb.js +0 -1
- package/src/console/ui/dist/assets/panel-plugins-CcGM1g64.js +0 -1
- package/src/console/ui/dist/assets/panel-runs-CGWNHLR4.js +0 -1
- package/src/console/ui/dist/assets/panel-signals-CNywkdak.js +0 -1
- package/src/console/ui/dist/assets/panel-store-KmTbFHMH.js +0 -1
- package/src/console/ui/dist/assets/panel-traces-DBLx2ilD.js +0 -1
- package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +0 -1
- package/src/drivers/vault-infisical.ts +0 -57
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Meilisearch lifecycle for `oke dev` (local mode).
|
|
3
|
+
*
|
|
4
|
+
* A Meilisearch server is a fourth moving part only when the app actually
|
|
5
|
+
* opts into the `meilisearch` store.index driver. This module:
|
|
6
|
+
*
|
|
7
|
+
* - generates the master key once and persists it under `.oke/meilisearch/`
|
|
8
|
+
* (`0700` dir / `0600` file, atomic write — same material discipline as
|
|
9
|
+
* OpenBao, minus the init/unseal ceremony: a single static key),
|
|
10
|
+
* - resolves the `meilisearch` binary from `PATH` (a documented prerequisite,
|
|
11
|
+
* like Docker for `--docker`; we never auto-download binaries),
|
|
12
|
+
* - spawns it via `Bun.spawn` with its own data dir + port, polls `/health`,
|
|
13
|
+
* and hands back an env overlay (`OKE_STORE_INDEX_URL` / `…_KEY`) plus a
|
|
14
|
+
* `stop()` that kills the child alongside the app.
|
|
15
|
+
*
|
|
16
|
+
* Fail-loud: missing binary / unhealthy server / never-ready all throw
|
|
17
|
+
* {@link MeilisearchLocalError} — never a silent memory fallback.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { randomBytes } from "node:crypto";
|
|
21
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { dirname, join, resolve } from "node:path";
|
|
23
|
+
|
|
24
|
+
/** Material directory relative to the project root. */
|
|
25
|
+
export const MEILISEARCH_STATE_DIR_REL = join(".oke", "meilisearch");
|
|
26
|
+
/** Default loopback port for the local server. */
|
|
27
|
+
export const MEILISEARCH_LOCAL_PORT = 7700;
|
|
28
|
+
|
|
29
|
+
/** Fail-loud local Meilisearch error (missing binary / never healthy). */
|
|
30
|
+
export class MeilisearchLocalError extends Error {
|
|
31
|
+
constructor(message: string) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "MeilisearchLocalError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Options for {@link startLocalMeilisearch}. */
|
|
38
|
+
export interface LocalMeilisearchOptions {
|
|
39
|
+
readonly cwd?: string;
|
|
40
|
+
/** Loopback port (default {@link MEILISEARCH_LOCAL_PORT}). */
|
|
41
|
+
readonly port?: number;
|
|
42
|
+
/** Override state dir (tests). */
|
|
43
|
+
readonly stateDir?: string;
|
|
44
|
+
/** Binary name/path resolved via `Bun.which` (default `meilisearch`). */
|
|
45
|
+
readonly binary?: string;
|
|
46
|
+
/** Max ms to wait for `/health` to report available (default 30_000). */
|
|
47
|
+
readonly readyTimeoutMs?: number;
|
|
48
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A running local Meilisearch child. */
|
|
52
|
+
export interface LocalMeilisearchHandle {
|
|
53
|
+
/** Base URL the app should talk to. */
|
|
54
|
+
readonly url: string;
|
|
55
|
+
/** Master key (also persisted on disk). */
|
|
56
|
+
readonly masterKey: string;
|
|
57
|
+
/** Env overlay for the app child. */
|
|
58
|
+
readonly overlay: Record<string, string>;
|
|
59
|
+
/** Kill the child (idempotent). */
|
|
60
|
+
readonly stop: () => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Env overlay for the app child. */
|
|
64
|
+
export function meilisearchStackEnv(handle: LocalMeilisearchHandle): Record<string, string> {
|
|
65
|
+
return handle.overlay;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeMaterial(path: string, value: string): void {
|
|
69
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
70
|
+
chmodSync(dirname(path), 0o700);
|
|
71
|
+
const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
72
|
+
writeFileSync(tmp, `${value}\n`, { mode: 0o600 });
|
|
73
|
+
renameSync(tmp, path);
|
|
74
|
+
chmodSync(path, 0o600);
|
|
75
|
+
const back = readFileSync(path, "utf8").trim();
|
|
76
|
+
if (back !== value) {
|
|
77
|
+
throw new MeilisearchLocalError(`meilisearch local: verify failed reading ${path}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Read the persisted master key, or generate + persist a fresh one. */
|
|
82
|
+
export function ensureMasterKey(stateDir: string): string {
|
|
83
|
+
const keyPath = join(stateDir, "master.key");
|
|
84
|
+
if (existsSync(keyPath)) {
|
|
85
|
+
const key = readFileSync(keyPath, "utf8").trim();
|
|
86
|
+
if (key.length >= 16) return key;
|
|
87
|
+
throw new MeilisearchLocalError(
|
|
88
|
+
`meilisearch local: ${keyPath} exists but is too short — delete it to regenerate`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const key = randomBytes(32).toString("hex");
|
|
92
|
+
writeMaterial(keyPath, key);
|
|
93
|
+
return key;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Start a local Meilisearch server and wait for `/health`.
|
|
98
|
+
*
|
|
99
|
+
* @param options - cwd / port / binary / injected fetch
|
|
100
|
+
*/
|
|
101
|
+
export async function startLocalMeilisearch(
|
|
102
|
+
options: LocalMeilisearchOptions = {},
|
|
103
|
+
): Promise<LocalMeilisearchHandle> {
|
|
104
|
+
const cwd = options.cwd ?? process.cwd();
|
|
105
|
+
const port = options.port ?? MEILISEARCH_LOCAL_PORT;
|
|
106
|
+
const stateDir = options.stateDir ?? resolve(cwd, MEILISEARCH_STATE_DIR_REL);
|
|
107
|
+
const dataDir = join(stateDir, "data");
|
|
108
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
109
|
+
const readyTimeoutMs = options.readyTimeoutMs ?? 30_000;
|
|
110
|
+
|
|
111
|
+
const binary = options.binary ?? "meilisearch";
|
|
112
|
+
const resolved = Bun.which(binary);
|
|
113
|
+
if (!resolved) {
|
|
114
|
+
throw new MeilisearchLocalError(
|
|
115
|
+
"meilisearch local: `meilisearch` not found on PATH — install it to use the store.index FTS driver " +
|
|
116
|
+
"(brew install meilisearch · curl -L https://install.meilisearch.com | sh), " +
|
|
117
|
+
"or run `oke dev --docker` instead. OKE never auto-downloads binaries.",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const masterKey = ensureMasterKey(stateDir);
|
|
122
|
+
mkdirSync(dataDir, { recursive: true });
|
|
123
|
+
|
|
124
|
+
const url = `http://127.0.0.1:${port}`;
|
|
125
|
+
const proc = Bun.spawn(
|
|
126
|
+
[
|
|
127
|
+
resolved,
|
|
128
|
+
"--http-addr",
|
|
129
|
+
`127.0.0.1:${port}`,
|
|
130
|
+
"--master-key",
|
|
131
|
+
masterKey,
|
|
132
|
+
"--db-path",
|
|
133
|
+
dataDir,
|
|
134
|
+
"--env",
|
|
135
|
+
"development",
|
|
136
|
+
"--no-analytics",
|
|
137
|
+
],
|
|
138
|
+
{
|
|
139
|
+
cwd,
|
|
140
|
+
stdout: "ignore",
|
|
141
|
+
stderr: "inherit",
|
|
142
|
+
env: { ...(process.env as Record<string, string>) },
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
let stopped = false;
|
|
147
|
+
const stop = () => {
|
|
148
|
+
if (stopped) return;
|
|
149
|
+
stopped = true;
|
|
150
|
+
try {
|
|
151
|
+
proc.kill("SIGKILL");
|
|
152
|
+
} catch {
|
|
153
|
+
// Already gone.
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const deadline = Date.now() + readyTimeoutMs;
|
|
158
|
+
for (;;) {
|
|
159
|
+
if (proc.exitCode !== null) {
|
|
160
|
+
throw new MeilisearchLocalError(
|
|
161
|
+
`meilisearch local: server exited (${proc.exitCode}) before becoming healthy`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const res = await fetchFn(`${url}/health`);
|
|
166
|
+
if (res.ok) break;
|
|
167
|
+
} catch {
|
|
168
|
+
// Not up yet.
|
|
169
|
+
}
|
|
170
|
+
if (Date.now() > deadline) {
|
|
171
|
+
stop();
|
|
172
|
+
throw new MeilisearchLocalError(
|
|
173
|
+
`meilisearch local: no /health from ${url} within ${readyTimeoutMs}ms`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
url,
|
|
181
|
+
masterKey,
|
|
182
|
+
overlay: {
|
|
183
|
+
OKE_STORE_INDEX_URL: url,
|
|
184
|
+
OKE_STORE_INDEX_KEY: masterKey,
|
|
185
|
+
},
|
|
186
|
+
stop,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -240,6 +240,69 @@ export const db = store.sql("app", { schema: { notes } });
|
|
|
240
240
|
sqlName: "id",
|
|
241
241
|
});
|
|
242
242
|
});
|
|
243
|
+
|
|
244
|
+
test("extracts optional description fields additively", async () => {
|
|
245
|
+
const source = `
|
|
246
|
+
import { store, field, signal, channel, clock, gate, vault } from "okengine";
|
|
247
|
+
|
|
248
|
+
export const notes = store.schema.table("notes", {
|
|
249
|
+
title: field.text().notNull().describe("Note title"),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
export const db = store.sql("app", {
|
|
253
|
+
description: "Primary app database",
|
|
254
|
+
schema: { notes },
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
export const embeddings = store.index("embeddings", {
|
|
258
|
+
description: "Document embeddings",
|
|
259
|
+
dims: 3,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
export const sessions = store.kv("sessions", { description: "Session cache" });
|
|
263
|
+
|
|
264
|
+
export const orderPlaced = signal("order-placed", {
|
|
265
|
+
delivery: "once",
|
|
266
|
+
description: "Order placed event",
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
export const bookingConfirmed = channel.template("booking-confirmed", {
|
|
270
|
+
medium: "email",
|
|
271
|
+
description: "Booking confirmation email",
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
export const expireHolds = clock("expire-holds", {
|
|
275
|
+
every: "10m",
|
|
276
|
+
description: "Expire unpaid holds",
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
export const member = gate.policy("member", {
|
|
280
|
+
description: "Verified members only",
|
|
281
|
+
check: ({ auth }) => !!auth?.verified,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
export const stripeKey = vault.secret("STRIPE_KEY", {
|
|
285
|
+
description: "Payments gateway key",
|
|
286
|
+
});
|
|
287
|
+
`;
|
|
288
|
+
const manifest = await extractFromSources({
|
|
289
|
+
"src/described.ts": source,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(manifest.stores?.app?.description).toBe("Primary app database");
|
|
293
|
+
expect(manifest.stores?.app?.tables?.notes?.columns?.title).toMatchObject({
|
|
294
|
+
description: "Note title",
|
|
295
|
+
});
|
|
296
|
+
expect(manifest.stores?.embeddings?.description).toBe("Document embeddings");
|
|
297
|
+
expect(manifest.stores?.sessions?.description).toBe("Session cache");
|
|
298
|
+
expect(manifest.signals?.["order-placed"]?.description).toBe("Order placed event");
|
|
299
|
+
expect(manifest.channels?.["booking-confirmed"]?.description).toBe(
|
|
300
|
+
"Booking confirmation email",
|
|
301
|
+
);
|
|
302
|
+
expect(manifest.clocks?.["expire-holds"]?.description).toBe("Expire unpaid holds");
|
|
303
|
+
expect(manifest.gates?.member?.description).toBe("Verified members only");
|
|
304
|
+
expect(manifest.vault?.STRIPE_KEY?.description).toBe("Payments gateway key");
|
|
305
|
+
});
|
|
243
306
|
});
|
|
244
307
|
|
|
245
308
|
describe("extractManifest — on(http.resource(...))", () => {
|
package/src/compiler/extract.ts
CHANGED
|
@@ -420,6 +420,7 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
|
|
|
420
420
|
const chain: string[] = [];
|
|
421
421
|
let sqlType: "text" | "integer" | undefined;
|
|
422
422
|
let sqlName: string | undefined;
|
|
423
|
+
let description: string | undefined;
|
|
423
424
|
let defaultValue: string | number | boolean | null | undefined;
|
|
424
425
|
let hasDefault = false;
|
|
425
426
|
let cur: AstNode | undefined = node;
|
|
@@ -435,6 +436,9 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
|
|
|
435
436
|
if (method === "as") {
|
|
436
437
|
sqlName = stringArg(call.arguments[0]) ?? sqlName;
|
|
437
438
|
}
|
|
439
|
+
if (method === "describe") {
|
|
440
|
+
description = stringArg(call.arguments[0]) ?? description;
|
|
441
|
+
}
|
|
438
442
|
if (method === "default") {
|
|
439
443
|
const lit = call.arguments[0];
|
|
440
444
|
if (lit && lit.type === "Literal") {
|
|
@@ -504,6 +508,7 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
|
|
|
504
508
|
...(methods.has("primaryKey") ? { primaryKey: true } : {}),
|
|
505
509
|
...(methods.has("unique") ? { unique: true } : {}),
|
|
506
510
|
...(hasDefault ? { default: defaultValue ?? null } : {}),
|
|
511
|
+
...(description !== undefined ? { description } : {}),
|
|
507
512
|
...(methods.has("pii") ? { pii: true } : {}),
|
|
508
513
|
...(methods.has("sensitive") ? { sensitive: true } : {}),
|
|
509
514
|
};
|
|
@@ -597,7 +602,12 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
597
602
|
const storeName = stringArg(call.arguments[0]) ?? "store";
|
|
598
603
|
const ref = `${facet}:${storeName}` as const;
|
|
599
604
|
const bindingName = enclosingConstName(call, program);
|
|
605
|
+
const storeOpts = objectArg(call.arguments[1]);
|
|
606
|
+
const description = stringProp(storeOpts, "description");
|
|
600
607
|
scope.stores[storeName] = scope.stores[storeName] ?? { facet };
|
|
608
|
+
if (description) {
|
|
609
|
+
scope.stores[storeName]!.description = description;
|
|
610
|
+
}
|
|
601
611
|
if (facet === "sql") {
|
|
602
612
|
attachSchemaOption(call.arguments[1], scope.stores[storeName]!, scope);
|
|
603
613
|
}
|
|
@@ -643,7 +653,13 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
643
653
|
if (obj === "gate" && prop === "policy") {
|
|
644
654
|
const policyName = stringArg(call.arguments[0]);
|
|
645
655
|
if (policyName) {
|
|
646
|
-
|
|
656
|
+
const opts = objectArg(call.arguments[1]);
|
|
657
|
+
const description = stringProp(opts, "description");
|
|
658
|
+
scope.gates[policyName] = {
|
|
659
|
+
kind: "policy",
|
|
660
|
+
roles: [policyName],
|
|
661
|
+
...(description ? { description } : {}),
|
|
662
|
+
};
|
|
647
663
|
const bindingName = enclosingConstName(call, program);
|
|
648
664
|
if (bindingName) {
|
|
649
665
|
scope.gateIds.set(bindingName, policyName);
|
|
@@ -661,26 +677,23 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
661
677
|
const max = numberProp(opts, "max");
|
|
662
678
|
const per = stringProp(opts, "per");
|
|
663
679
|
const keyBy = stringProp(opts, "keyBy");
|
|
680
|
+
const description = stringProp(opts, "description");
|
|
664
681
|
if (max !== undefined && per) {
|
|
665
682
|
const expr = `rate:${strategy}:${max}/${per}`;
|
|
666
683
|
const bindingName = enclosingConstName(call, program);
|
|
684
|
+
const rateGate = {
|
|
685
|
+
kind: "rate" as const,
|
|
686
|
+
strategy,
|
|
687
|
+
max,
|
|
688
|
+
per,
|
|
689
|
+
...(keyBy ? { keyBy } : {}),
|
|
690
|
+
...(description ? { description } : {}),
|
|
691
|
+
};
|
|
667
692
|
if (bindingName) {
|
|
668
|
-
scope.gates[bindingName] =
|
|
669
|
-
kind: "rate",
|
|
670
|
-
strategy,
|
|
671
|
-
max,
|
|
672
|
-
per,
|
|
673
|
-
...(keyBy ? { keyBy } : {}),
|
|
674
|
-
};
|
|
693
|
+
scope.gates[bindingName] = rateGate;
|
|
675
694
|
scope.gateIds.set(bindingName, expr);
|
|
676
695
|
} else {
|
|
677
|
-
scope.gates[expr] =
|
|
678
|
-
kind: "rate",
|
|
679
|
-
strategy,
|
|
680
|
-
max,
|
|
681
|
-
per,
|
|
682
|
-
...(keyBy ? { keyBy } : {}),
|
|
683
|
-
};
|
|
696
|
+
scope.gates[expr] = rateGate;
|
|
684
697
|
}
|
|
685
698
|
}
|
|
686
699
|
}
|
|
@@ -691,9 +704,11 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
691
704
|
if (templateName) {
|
|
692
705
|
const medium = stringProp(opts, "medium");
|
|
693
706
|
const locales = stringArrayProp(opts, "locales");
|
|
707
|
+
const description = stringProp(opts, "description");
|
|
694
708
|
scope.channels[templateName] = {
|
|
695
709
|
...(medium ? { medium: medium as Channel["medium"] } : { medium: "email" }),
|
|
696
710
|
...(locales ? { locales } : {}),
|
|
711
|
+
...(description ? { description } : {}),
|
|
697
712
|
};
|
|
698
713
|
const bindingName = enclosingConstName(call, program);
|
|
699
714
|
if (bindingName) {
|
|
@@ -809,6 +824,9 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
809
824
|
if (delivery) {
|
|
810
825
|
const signal: Signal = {
|
|
811
826
|
delivery,
|
|
827
|
+
...(stringProp(opts, "description")
|
|
828
|
+
? { description: stringProp(opts, "description") }
|
|
829
|
+
: {}),
|
|
812
830
|
...(numberProp(opts, "retries") !== undefined
|
|
813
831
|
? { retries: numberProp(opts, "retries") }
|
|
814
832
|
: {}),
|
|
@@ -839,6 +857,9 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
839
857
|
...(boolProp(opts, "overridable") !== undefined
|
|
840
858
|
? { overridable: boolProp(opts, "overridable") }
|
|
841
859
|
: {}),
|
|
860
|
+
...(stringProp(opts, "description")
|
|
861
|
+
? { description: stringProp(opts, "description") }
|
|
862
|
+
: {}),
|
|
842
863
|
};
|
|
843
864
|
}
|
|
844
865
|
}
|
|
@@ -5,7 +5,11 @@ export const chargeBooking = flow({
|
|
|
5
5
|
name: "payments.chargeBooking",
|
|
6
6
|
durable: true,
|
|
7
7
|
do: async ({ orderId }, fx) => {
|
|
8
|
-
|
|
8
|
+
// Journal only serializable step values — a Redacted is not journaled.
|
|
9
|
+
const intent = await fx.step("create-intent", async () => ({
|
|
10
|
+
id: `pi_${orderId}`,
|
|
11
|
+
key: fx.vault(stripeKey).reveal(),
|
|
12
|
+
}));
|
|
9
13
|
await fx.clock.sleep("verify-window", "2m");
|
|
10
14
|
return fx.step("confirm", () => intent);
|
|
11
15
|
},
|
|
@@ -41,6 +41,7 @@ export type ChannelsFace = "inbox" | "deliverability";
|
|
|
41
41
|
/** One Manifest template row. */
|
|
42
42
|
export interface ConsoleChannelTemplate {
|
|
43
43
|
readonly name: string;
|
|
44
|
+
readonly description?: string;
|
|
44
45
|
readonly medium: string;
|
|
45
46
|
readonly locales: readonly string[];
|
|
46
47
|
readonly from: string | null;
|
|
@@ -214,6 +215,7 @@ export function projectTemplates(manifest: Manifest | null): readonly ConsoleCha
|
|
|
214
215
|
return Object.entries(manifest?.channels ?? {})
|
|
215
216
|
.map(([name, c]) => ({
|
|
216
217
|
name,
|
|
218
|
+
...(c.description !== undefined ? { description: c.description } : {}),
|
|
217
219
|
medium: c.medium ?? "email",
|
|
218
220
|
locales: c.locales ?? [],
|
|
219
221
|
from: c.from ?? null,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
/** One cron row in `console.clock.list`. */
|
|
29
29
|
export interface ConsoleClockRow {
|
|
30
30
|
readonly name: string;
|
|
31
|
+
readonly description?: string;
|
|
31
32
|
readonly status: CronRow["status"];
|
|
32
33
|
readonly timezone: string;
|
|
33
34
|
readonly overridable: boolean;
|
|
@@ -110,8 +111,10 @@ export async function projectClocksList(options: ProjectClocksOptions): Promise<
|
|
|
110
111
|
.map((row) => {
|
|
111
112
|
const flowIds = flowIdsForCronRow(row, options.manifest);
|
|
112
113
|
const external = flowIds.some((id) => isExternal(flows[id]));
|
|
114
|
+
const description = options.manifest?.clocks?.[row.name]?.description;
|
|
113
115
|
return {
|
|
114
116
|
name: row.name,
|
|
117
|
+
...(description !== undefined ? { description } : {}),
|
|
115
118
|
status: row.status,
|
|
116
119
|
timezone: row.timezone,
|
|
117
120
|
overridable: row.overridable,
|
|
@@ -158,6 +158,7 @@ const SignalsListOut = z.object({
|
|
|
158
158
|
signals: z.array(
|
|
159
159
|
z.object({
|
|
160
160
|
name: z.string(),
|
|
161
|
+
description: z.string().optional(),
|
|
161
162
|
delivery: z.enum(["once", "broadcast", "live"]),
|
|
162
163
|
retries: z.number(),
|
|
163
164
|
deadLetterEnabled: z.boolean(),
|
|
@@ -336,6 +337,7 @@ const ChannelsListOut = z.object({
|
|
|
336
337
|
templates: z.array(
|
|
337
338
|
z.object({
|
|
338
339
|
name: z.string(),
|
|
340
|
+
description: z.string().optional(),
|
|
339
341
|
medium: z.string(),
|
|
340
342
|
locales: z.array(z.string()),
|
|
341
343
|
from: z.string().nullable(),
|
|
@@ -465,6 +467,7 @@ const CronHealthOut = z.object({
|
|
|
465
467
|
|
|
466
468
|
const ClockCronOut = z.object({
|
|
467
469
|
name: z.string(),
|
|
470
|
+
description: z.string().optional(),
|
|
468
471
|
status: z.enum(["active", "paused", "orphaned"]),
|
|
469
472
|
timezone: z.string(),
|
|
470
473
|
overridable: z.boolean(),
|
|
@@ -753,6 +756,7 @@ const GatesListOut = z.object({
|
|
|
753
756
|
gates: z.array(
|
|
754
757
|
z.object({
|
|
755
758
|
name: z.string(),
|
|
759
|
+
description: z.string().optional(),
|
|
756
760
|
kind: z.enum(["policy", "rate"]),
|
|
757
761
|
scopes: z.array(z.string()),
|
|
758
762
|
roles: z.array(z.string()),
|
|
@@ -853,6 +857,13 @@ const PluginsListOut = z.object({
|
|
|
853
857
|
}),
|
|
854
858
|
),
|
|
855
859
|
declares: z.array(z.string()),
|
|
860
|
+
tables: z.record(
|
|
861
|
+
z.string(),
|
|
862
|
+
z.object({
|
|
863
|
+
plane: z.string().optional(),
|
|
864
|
+
description: z.string().optional(),
|
|
865
|
+
}),
|
|
866
|
+
),
|
|
856
867
|
intercepts: z.array(
|
|
857
868
|
z.object({
|
|
858
869
|
stage: z.string(),
|
|
@@ -1216,6 +1227,7 @@ const StoreListOut = z.object({
|
|
|
1216
1227
|
ref: z.string(),
|
|
1217
1228
|
facet: z.enum(["sql", "kv", "files", "index"]),
|
|
1218
1229
|
name: z.string(),
|
|
1230
|
+
description: z.string().optional(),
|
|
1219
1231
|
children: z.array(
|
|
1220
1232
|
z.object({
|
|
1221
1233
|
name: z.string(),
|
|
@@ -1229,6 +1241,7 @@ const StoreListOut = z.object({
|
|
|
1229
1241
|
}),
|
|
1230
1242
|
willNotFire: WillNotFireOut,
|
|
1231
1243
|
piiColumns: z.array(z.string()),
|
|
1244
|
+
columnDescriptions: z.record(z.string(), z.string()),
|
|
1232
1245
|
}),
|
|
1233
1246
|
),
|
|
1234
1247
|
replicaLagMs: z.number().nullable(),
|
|
@@ -48,6 +48,7 @@ export type GatePrincipalKind = "role" | "key" | "user";
|
|
|
48
48
|
/** One Module:Action / flow / gate surface row. */
|
|
49
49
|
export interface ConsoleGateDefRow {
|
|
50
50
|
readonly name: string;
|
|
51
|
+
readonly description?: string;
|
|
51
52
|
readonly kind: "policy" | "rate";
|
|
52
53
|
readonly scopes: readonly string[];
|
|
53
54
|
readonly roles: readonly string[];
|
|
@@ -477,6 +478,7 @@ function projectGateDefs(
|
|
|
477
478
|
def.kind === "rate" || name.startsWith("rate:") ? "rate" : "policy";
|
|
478
479
|
rows.push({
|
|
479
480
|
name,
|
|
481
|
+
...(def.description !== undefined ? { description: def.description } : {}),
|
|
480
482
|
kind,
|
|
481
483
|
scopes: [...(def.scopes ?? [])],
|
|
482
484
|
roles: [...(def.roles ?? [])],
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
import { allHookCostSummaries, type HookCostSummary } from "../../kernel/hook-timing.ts";
|
|
10
10
|
import type { PluginRegistry } from "../../kernel/registry.ts";
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
Manifest,
|
|
13
|
+
ManifestChange,
|
|
14
|
+
Plugin,
|
|
15
|
+
PluginOrigin,
|
|
16
|
+
PluginTable,
|
|
17
|
+
} from "../../manifest/types.ts";
|
|
12
18
|
import { CORE_PLUGINS, isCorePluginOn, type PluginConfigProbe } from "../../plugins/catalogue.ts";
|
|
13
19
|
import type { PackageJsonProbe, SupplyChainSignals } from "../../plugins/supply-chain.ts";
|
|
14
20
|
import type { ScanSourceFile } from "../../plugins/node-import-scan.ts";
|
|
@@ -46,6 +52,8 @@ export interface ConsolePluginRow {
|
|
|
46
52
|
readonly summary: string | null;
|
|
47
53
|
readonly scopes: readonly PluginScopeView[];
|
|
48
54
|
readonly declares: readonly string[];
|
|
55
|
+
/** Optional metadata for `table:*` declares. */
|
|
56
|
+
readonly tables: Readonly<Record<string, PluginTable>>;
|
|
49
57
|
readonly intercepts: readonly PluginInterceptView[];
|
|
50
58
|
readonly hookCost: {
|
|
51
59
|
readonly count: number;
|
|
@@ -133,6 +141,7 @@ export async function projectPluginsList(
|
|
|
133
141
|
version: meta?.version ?? manifestPlugin?.version ?? null,
|
|
134
142
|
summary: spec.summary,
|
|
135
143
|
declares: meta?.declares ?? manifestPlugin?.declares ?? [],
|
|
144
|
+
tables: meta?.tables ?? manifestPlugin?.tables ?? {},
|
|
136
145
|
interceptStages: meta?.intercepts ?? manifestPlugin?.intercepts ?? [],
|
|
137
146
|
scopes: meta?.scopes ?? [],
|
|
138
147
|
costs,
|
|
@@ -155,6 +164,7 @@ export async function projectPluginsList(
|
|
|
155
164
|
version: meta.version,
|
|
156
165
|
summary: null,
|
|
157
166
|
declares: meta.declares,
|
|
167
|
+
tables: meta.tables,
|
|
158
168
|
interceptStages: meta.intercepts,
|
|
159
169
|
scopes: meta.scopes,
|
|
160
170
|
costs,
|
|
@@ -184,6 +194,7 @@ interface PluggedMeta {
|
|
|
184
194
|
readonly version: string | null;
|
|
185
195
|
readonly declares: readonly string[];
|
|
186
196
|
readonly intercepts: readonly string[];
|
|
197
|
+
readonly tables: Readonly<Record<string, PluginTable>>;
|
|
187
198
|
readonly scopes: readonly PluginScopeView[];
|
|
188
199
|
readonly origin?: PluginOrigin;
|
|
189
200
|
}
|
|
@@ -199,6 +210,7 @@ function collectPlugged(
|
|
|
199
210
|
version: p.version ?? null,
|
|
200
211
|
declares: p.declares ?? [],
|
|
201
212
|
intercepts: p.intercepts ?? [],
|
|
213
|
+
tables: p.tables ?? {},
|
|
202
214
|
scopes: [],
|
|
203
215
|
origin: p.origin,
|
|
204
216
|
});
|
|
@@ -214,10 +226,15 @@ function collectPlugged(
|
|
|
214
226
|
? { kind: "app" }
|
|
215
227
|
: { kind: entry.scope.kind, name: entry.scope.name };
|
|
216
228
|
const scopes = [...(prev?.scopes ?? []), scope];
|
|
229
|
+
const tables = {
|
|
230
|
+
...(prev?.tables ?? {}),
|
|
231
|
+
...(caps.tables ?? {}),
|
|
232
|
+
};
|
|
217
233
|
out.set(id, {
|
|
218
234
|
version: caps.version ?? prev?.version ?? null,
|
|
219
235
|
declares: caps.declares.length ? [...caps.declares] : (prev?.declares ?? []),
|
|
220
236
|
intercepts: caps.intercepts.length ? [...caps.intercepts] : (prev?.intercepts ?? []),
|
|
237
|
+
tables,
|
|
221
238
|
scopes,
|
|
222
239
|
origin: prev?.origin,
|
|
223
240
|
});
|
|
@@ -243,6 +260,7 @@ async function buildRow(input: {
|
|
|
243
260
|
readonly version: string | null;
|
|
244
261
|
readonly summary: string | null;
|
|
245
262
|
readonly declares: readonly string[];
|
|
263
|
+
readonly tables: Readonly<Record<string, PluginTable>>;
|
|
246
264
|
readonly interceptStages: readonly string[];
|
|
247
265
|
readonly scopes: readonly PluginScopeView[];
|
|
248
266
|
readonly costs: Readonly<Record<string, HookCostSummary>>;
|
|
@@ -286,6 +304,7 @@ async function buildRow(input: {
|
|
|
286
304
|
summary: input.summary,
|
|
287
305
|
scopes: input.scopes,
|
|
288
306
|
declares: [...input.declares],
|
|
307
|
+
tables: { ...input.tables },
|
|
289
308
|
intercepts,
|
|
290
309
|
hookCost: cost
|
|
291
310
|
? {
|
|
@@ -28,6 +28,7 @@ export interface SignalEndpoint {
|
|
|
28
28
|
/** One row in `console.signals.list`. */
|
|
29
29
|
export interface ConsoleSignalRow {
|
|
30
30
|
readonly name: string;
|
|
31
|
+
readonly description?: string;
|
|
31
32
|
readonly delivery: "once" | "broadcast" | "live";
|
|
32
33
|
readonly retries: number;
|
|
33
34
|
readonly deadLetterEnabled: boolean;
|
|
@@ -112,8 +113,10 @@ export async function projectSignalsList(
|
|
|
112
113
|
withCause(dl, cfg.name, options.runs),
|
|
113
114
|
);
|
|
114
115
|
|
|
116
|
+
const description = options.manifest?.signals?.[cfg.name]?.description;
|
|
115
117
|
rows.push({
|
|
116
118
|
name: cfg.name,
|
|
119
|
+
...(description !== undefined ? { description } : {}),
|
|
117
120
|
delivery: cfg.delivery,
|
|
118
121
|
retries: stats?.retries ?? cfg.retries,
|
|
119
122
|
deadLetterEnabled: stats?.deadLetterEnabled ?? cfg.deadLetter,
|
|
@@ -142,8 +145,10 @@ export async function projectSignalsList(
|
|
|
142
145
|
if (rows.some((r) => r.name === name)) continue;
|
|
143
146
|
const producers = producersOf(name, flows);
|
|
144
147
|
const consumers = consumersOf(name, flows);
|
|
148
|
+
const description = options.manifest?.signals?.[name]?.description;
|
|
145
149
|
rows.push({
|
|
146
150
|
name,
|
|
151
|
+
...(description !== undefined ? { description } : {}),
|
|
147
152
|
delivery: stats.delivery,
|
|
148
153
|
retries: stats.retries,
|
|
149
154
|
deadLetterEnabled: stats.deadLetterEnabled,
|
|
@@ -130,6 +130,23 @@ describe("willNotFireFor", () => {
|
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
describe("index driver resolution", () => {
|
|
134
|
+
test("console manifest runtime uses the shared boot switch (memory default)", async () => {
|
|
135
|
+
const runtime = await createManifestStoreRuntime(MANIFEST);
|
|
136
|
+
const decl = runtime.declarations.get("index:docs");
|
|
137
|
+
expect(decl).toBeDefined();
|
|
138
|
+
const handle = (await runtime.open(decl!, {
|
|
139
|
+
effects: { reads: ["index:docs"], writes: ["index:docs"] },
|
|
140
|
+
})) as import("../../elements/store.ts").VectorIndexStoreFxHandle;
|
|
141
|
+
expect(handle.driverId).toBe("memory");
|
|
142
|
+
await handle.upsert("d1", [1, 0, 0], { t: 1 });
|
|
143
|
+
const hits = await handle.search([1, 0, 0], 1);
|
|
144
|
+
expect(hits[0]?.id).toBe("d1");
|
|
145
|
+
expect(hits[0]?.meta).toEqual({ t: 1 });
|
|
146
|
+
await runtime.close();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
133
150
|
describe("PII masking survives SELECT *", () => {
|
|
134
151
|
test("masks classified columns; reveal returns cleartext", async () => {
|
|
135
152
|
const runtime = await createManifestStoreRuntime(MANIFEST);
|