okengine 0.3.5 → 0.3.6

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.
Files changed (98) hide show
  1. package/AGENTS.md +4 -0
  2. package/manifest.v1.schema.json +43 -1
  3. package/package.json +16 -1
  4. package/site/content/docs/ai/skills.mdx +9 -7
  5. package/site/content/docs/console/vault.mdx +4 -0
  6. package/site/content/docs/elements/channel.mdx +5 -4
  7. package/site/content/docs/elements/clock.mdx +1 -0
  8. package/site/content/docs/elements/flow.mdx +15 -0
  9. package/site/content/docs/elements/gate.mdx +16 -11
  10. package/site/content/docs/elements/signal.mdx +8 -7
  11. package/site/content/docs/elements/store.mdx +48 -8
  12. package/site/content/docs/elements/vault.mdx +1 -1
  13. package/site/content/docs/plugins/ip-allowlist.mdx +2 -2
  14. package/site/content/docs/reference/configuration.mdx +2 -2
  15. package/site/content/docs/reference/environment-variables.mdx +8 -0
  16. package/site/content/docs/reference/fx.mdx +40 -0
  17. package/site/content/docs/reference/plugins.mdx +18 -18
  18. package/src/compiler/extract.test.ts +63 -0
  19. package/src/compiler/extract.ts +36 -15
  20. package/src/console/server/channels.ts +2 -0
  21. package/src/console/server/clock.ts +3 -0
  22. package/src/console/server/flows.ts +13 -0
  23. package/src/console/server/gates.ts +2 -0
  24. package/src/console/server/plugins.ts +20 -1
  25. package/src/console/server/signals.ts +5 -0
  26. package/src/console/server/store.test.ts +17 -0
  27. package/src/console/server/store.ts +24 -0
  28. package/src/console/ui/channels/types.ts +1 -0
  29. package/src/console/ui/clock/types.ts +1 -0
  30. package/src/console/ui/display.test.ts +14 -0
  31. package/src/console/ui/display.ts +9 -0
  32. package/src/console/ui/dist/assets/{index-BWo8R7NR.js → index-CrKMmO__.js} +2 -2
  33. package/src/console/ui/dist/assets/panel-channels-DCDd4WAC.js +1 -0
  34. package/src/console/ui/dist/assets/panel-clock-DjGGFPzr.js +1 -0
  35. package/src/console/ui/dist/assets/panel-gates-B5eTE8XH.js +1 -0
  36. package/src/console/ui/dist/assets/{panel-overview-BznEOTnb.js → panel-overview-BsFvDdts.js} +1 -1
  37. package/src/console/ui/dist/assets/panel-plugins-Cj7DK1er.js +1 -0
  38. package/src/console/ui/dist/assets/{panel-runs-CGWNHLR4.js → panel-runs-C0gmnoYL.js} +1 -1
  39. package/src/console/ui/dist/assets/panel-signals-whmDXIg3.js +1 -0
  40. package/src/console/ui/dist/assets/panel-store-CEMHLvaw.js +1 -0
  41. package/src/console/ui/dist/assets/{panel-traces-DBLx2ilD.js → panel-traces-BDiAuVSK.js} +1 -1
  42. package/src/console/ui/dist/assets/panel-vault-C9wjbki8.js +1 -0
  43. package/src/console/ui/dist/index.html +1 -1
  44. package/src/console/ui/gates/types.ts +1 -0
  45. package/src/console/ui/plugins/fixture.ts +7 -0
  46. package/src/console/ui/plugins/types.ts +3 -0
  47. package/src/console/ui/shell/client.ts +3 -0
  48. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +7 -2
  49. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +9 -2
  50. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +12 -5
  51. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +18 -4
  52. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +13 -2
  53. package/src/console/ui/shell/panels/store/StorePanel.tsx +11 -4
  54. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +9 -3
  55. package/src/console/ui/signals/types.ts +1 -0
  56. package/src/console/ui/store/fixture.ts +5 -0
  57. package/src/console/ui/store/types.ts +2 -0
  58. package/src/drivers/conformance.test.ts +11 -0
  59. package/src/drivers/drizzle-dialect.test.ts +4 -0
  60. package/src/drivers/drizzle-dialect.ts +8 -4
  61. package/src/drivers/index.ts +4 -0
  62. package/src/drivers/libsql.ts +179 -0
  63. package/src/drivers/pglite.ts +79 -0
  64. package/src/drivers/pgvector.ts +54 -19
  65. package/src/drivers/types.ts +16 -7
  66. package/src/elements/channel/declare.ts +5 -0
  67. package/src/elements/clock/declare.ts +5 -0
  68. package/src/elements/clock/durable.ts +7 -1
  69. package/src/elements/gate/declare.ts +28 -5
  70. package/src/elements/gate.ts +1 -0
  71. package/src/elements/signal/declare.ts +5 -0
  72. package/src/elements/store/declare.ts +10 -2
  73. package/src/elements/store/index-boot.test.ts +257 -0
  74. package/src/elements/store/runtime.ts +67 -9
  75. package/src/elements/store/schema-decl.ts +7 -0
  76. package/src/kernel/abort-scope.ts +116 -0
  77. package/src/kernel/app.ts +12 -2
  78. package/src/kernel/boot-bind/store.test.ts +59 -1
  79. package/src/kernel/boot-bind/store.ts +64 -2
  80. package/src/kernel/concurrency.test.ts +236 -0
  81. package/src/kernel/concurrency.ts +172 -0
  82. package/src/kernel/flow.ts +9 -0
  83. package/src/kernel/fx.test.ts +12 -0
  84. package/src/kernel/fx.ts +44 -0
  85. package/src/kernel/index.ts +14 -0
  86. package/src/kernel/journal.ts +9 -0
  87. package/src/kernel/plugin/capabilities.test.ts +18 -0
  88. package/src/kernel/plugin.ts +11 -3
  89. package/src/kernel/registry.ts +29 -6
  90. package/src/manifest/types.ts +21 -0
  91. package/src/release/measure.ts +4 -0
  92. package/src/console/ui/dist/assets/panel-channels-BOmQ-onL.js +0 -1
  93. package/src/console/ui/dist/assets/panel-clock-giAq0Ccv.js +0 -1
  94. package/src/console/ui/dist/assets/panel-gates-XclZxWD5.js +0 -1
  95. package/src/console/ui/dist/assets/panel-plugins-CcGM1g64.js +0 -1
  96. package/src/console/ui/dist/assets/panel-signals-CNywkdak.js +0 -1
  97. package/src/console/ui/dist/assets/panel-store-KmTbFHMH.js +0 -1
  98. package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +0 -1
@@ -4,7 +4,9 @@
4
4
  * drizzle-kit `1.0.0-rc.4` dialects (from its schema validator):
5
5
  * `postgresql` · `mysql` · `sqlite` · `turso` · `singlestore` · `mssql` ·
6
6
  * `cockroach` · `duckdb`. OKE owns kit-facing SQL drivers `sqlite` /
7
- * `postgres` only — map those, never infer from connection URLs.
7
+ * `postgres` / `libsql` / `pglite` only — map those, never infer from
8
+ * connection URLs. `libsql` speaks the sqlite wire dialect; `pglite` speaks
9
+ * postgresql (same dialect as `postgres`, different transport).
8
10
  */
9
11
 
10
12
  import type { StoreDriverId } from "./types.ts";
@@ -18,20 +20,22 @@ export type OkeDrizzleKitDialect = "sqlite" | "postgresql";
18
20
  * dialect months later.
19
21
  */
20
22
  export const SQL_DRIVER_TO_DRIZZLE_DIALECT: Record<
21
- Extract<StoreDriverId, "sqlite" | "postgres">,
23
+ Extract<StoreDriverId, "sqlite" | "postgres" | "libsql" | "pglite">,
22
24
  OkeDrizzleKitDialect
23
25
  > = {
24
26
  sqlite: "sqlite",
25
27
  postgres: "postgresql",
28
+ libsql: "sqlite",
29
+ pglite: "postgresql",
26
30
  };
27
31
 
28
32
  /**
29
33
  * Resolve the drizzle-kit dialect for an OKE SQL driver id.
30
34
  *
31
- * @param driverId - Protocol driver id (`sqlite` | `postgres`)
35
+ * @param driverId - Protocol driver id (`sqlite` | `postgres` | `libsql` | `pglite`)
32
36
  */
33
37
  export function drizzleDialectFromSqlDriver(
34
- driverId: Extract<StoreDriverId, "sqlite" | "postgres">,
38
+ driverId: Extract<StoreDriverId, "sqlite" | "postgres" | "libsql" | "pglite">,
35
39
  ): OkeDrizzleKitDialect {
36
40
  return SQL_DRIVER_TO_DRIZZLE_DIALECT[driverId];
37
41
  }
@@ -77,6 +77,10 @@ export { s3Driver, openS3Bucket, createS3FakeClient } from "./s3.ts";
77
77
 
78
78
  export { pgvectorDriver, openPgvectorIndex } from "./pgvector.ts";
79
79
 
80
+ export { libsqlDriver, connectLibsql, libsqlIndexDriver, openLibsqlIndex } from "./libsql.ts";
81
+
82
+ export { pgliteDriver, connectPglite } from "./pglite.ts";
83
+
80
84
  export { memorySignalDriver, openMemorySignal } from "./signal-memory.ts";
81
85
 
82
86
  export {
@@ -0,0 +1,179 @@
1
+ /**
2
+ * `libsql` driver — binds `@libsql/client` (optional peer, dynamic import).
3
+ *
4
+ * Protocol-named: libSQL speaks the sqlite wire dialect plus native ANN
5
+ * vectors (`F32_BLOB` + `libsql_vector_idx` + `vector_top_k`). Classic libSQL
6
+ * only — never the Turso Rust rewrite (`@tursodatabase/database`).
7
+ *
8
+ * Serves both the `sql` facet (same `?` placeholder convention as `sqlite`)
9
+ * and the `index` facet (native ANN, no JS-side distance math).
10
+ *
11
+ * Latency note: local file / `:memory:` opens are in-process like sqlite.
12
+ */
13
+
14
+ import { customType, sqliteTable, text } from "drizzle-orm/sqlite-core";
15
+ import type {
16
+ IndexDriver,
17
+ IndexHit,
18
+ IndexOpenOptions,
19
+ IndexStore,
20
+ SqlConnectOptions,
21
+ SqlConnection,
22
+ SqlDriver,
23
+ SqlRow,
24
+ } from "./types.ts";
25
+
26
+ /** `@libsql/client` is an optional peer — loaded only when this driver runs. */
27
+ type LibsqlModule = typeof import("@libsql/client");
28
+
29
+ async function loadLibsql(): Promise<LibsqlModule> {
30
+ try {
31
+ return await import("@libsql/client");
32
+ } catch {
33
+ throw new Error(
34
+ "libsql driver: install optional peer `@libsql/client` (bun add @libsql/client)",
35
+ );
36
+ }
37
+ }
38
+
39
+ /** Bare file paths become `file:` URLs; `:memory:` / scheme URLs pass through. */
40
+ function normalizeLibsqlUrl(url: string): string {
41
+ if (url === ":memory:" || url.includes("://") || url.startsWith("file:")) return url;
42
+ return `file:${url}`;
43
+ }
44
+
45
+ /**
46
+ * Open a libSQL connection implementing {@link SqlConnection}.
47
+ *
48
+ * @param options - URL (`:memory:` / `file:…` / `libsql://…`) / role
49
+ */
50
+ export async function connectLibsql(options: SqlConnectOptions = {}): Promise<SqlConnection> {
51
+ const role = options.role ?? "primary";
52
+ const { createClient } = await loadLibsql();
53
+ const client = createClient({ url: normalizeLibsqlUrl(options.url ?? ":memory:") });
54
+ return {
55
+ driverId: "libsql",
56
+ role,
57
+ async query(sql, params = []) {
58
+ const rs = await client.execute({ sql, args: params as never });
59
+ return rs.rows as unknown as SqlRow[];
60
+ },
61
+ async exec(sql, params = []) {
62
+ const rs = await client.execute({ sql, args: params as never });
63
+ return { changes: rs.rowsAffected };
64
+ },
65
+ async close() {
66
+ client.close();
67
+ },
68
+ };
69
+ }
70
+
71
+ /** Protocol-named libsql sql driver. */
72
+ export const libsqlDriver: SqlDriver = {
73
+ id: "libsql",
74
+ facet: "sql",
75
+ connect: connectLibsql,
76
+ };
77
+
78
+ /** libSQL native vector column: `F32_BLOB(dims)` (float32, validated by the engine). */
79
+ const f32Blob = customType<{
80
+ data: readonly number[];
81
+ driverData: string;
82
+ config: { dimensions: number };
83
+ }>({
84
+ dataType(config) {
85
+ return `F32_BLOB(${config?.dimensions ?? 0})`;
86
+ },
87
+ toDriver(value) {
88
+ return JSON.stringify(value);
89
+ },
90
+ });
91
+
92
+ /**
93
+ * Open a libSQL-backed index with native ANN.
94
+ *
95
+ * DDL: `F32_BLOB(dims)` column + `libsql_vector_idx` ANN index (validated
96
+ * against `@libsql/client@0.17`). Queries run `vector_top_k` joined back to
97
+ * the base table — approximate nearest neighbours, never a full scan. Score
98
+ * is cosine similarity (`1 - vector_distance_cos`), matching the memory
99
+ * driver's higher-is-better convention.
100
+ *
101
+ * @param options - Name / dims / shared SQL connection
102
+ */
103
+ export async function openLibsqlIndex(options: IndexOpenOptions): Promise<IndexStore> {
104
+ const ownsSql = !options.sql;
105
+ const sql = options.sql ?? (await connectLibsql({ url: options.url }));
106
+ if (sql.driverId !== "libsql") {
107
+ if (ownsSql) await sql.close();
108
+ throw new Error(
109
+ `libsql index: needs a libsql SQL connection, got "${sql.driverId}" — ` +
110
+ `set store.sql to "libsql" so the index shares its connection`,
111
+ );
112
+ }
113
+
114
+ const tableName = `oke_idx_${options.name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
115
+ const indexName = `${tableName}_vec`;
116
+ const table = sqliteTable(tableName, {
117
+ id: text("id").primaryKey(),
118
+ embedding: f32Blob("embedding", { dimensions: options.dims }).notNull(),
119
+ meta: text("meta"),
120
+ });
121
+
122
+ await sql.exec(
123
+ `CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT PRIMARY KEY, embedding ${table.embedding.getSQLType()} NOT NULL, meta TEXT)`,
124
+ );
125
+ // ANN index — fails loud on engines without vector support (never falls back).
126
+ await sql.exec(
127
+ `CREATE INDEX IF NOT EXISTS "${indexName}" ON "${tableName}" (libsql_vector_idx(embedding))`,
128
+ );
129
+
130
+ return {
131
+ driverId: "libsql",
132
+ async upsert(id, vector, meta) {
133
+ if (vector.length !== options.dims) {
134
+ throw new Error(`vector dims ${vector.length} !== index dims ${options.dims}`);
135
+ }
136
+ await sql.exec(
137
+ `INSERT INTO "${tableName}" (id, embedding, meta) VALUES (?, vector32(?), ?) ` +
138
+ `ON CONFLICT (id) DO UPDATE SET embedding = excluded.embedding, meta = excluded.meta`,
139
+ [id, JSON.stringify(vector), meta ? JSON.stringify(meta) : null],
140
+ );
141
+ },
142
+ async search(vector, topK = 10): Promise<IndexHit[]> {
143
+ const k = Math.max(1, Math.floor(topK));
144
+ const rows = await sql.query(
145
+ `SELECT t.id AS id, t.meta AS meta, ` +
146
+ `vector_distance_cos(t.embedding, vector32(?)) AS distance ` +
147
+ `FROM vector_top_k('${indexName}', vector32(?), ${k}) AS i ` +
148
+ `JOIN "${tableName}" AS t ON t.rowid = i.id ` +
149
+ `ORDER BY distance ASC`,
150
+ [JSON.stringify(vector), JSON.stringify(vector)],
151
+ );
152
+ return rows.map((row) => {
153
+ const metaRaw = row.meta;
154
+ return {
155
+ id: String(row.id),
156
+ score: 1 - Number(row.distance),
157
+ meta:
158
+ typeof metaRaw === "string"
159
+ ? (JSON.parse(metaRaw) as Record<string, unknown>)
160
+ : undefined,
161
+ };
162
+ });
163
+ },
164
+ async delete(id) {
165
+ const result = await sql.exec(`DELETE FROM "${tableName}" WHERE id = ?`, [id]);
166
+ return result.changes > 0;
167
+ },
168
+ async close() {
169
+ if (ownsSql) await sql.close();
170
+ },
171
+ };
172
+ }
173
+
174
+ /** Protocol-named libsql index driver. */
175
+ export const libsqlIndexDriver: IndexDriver = {
176
+ id: "libsql",
177
+ facet: "index",
178
+ open: openLibsqlIndex,
179
+ };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `pglite` driver — binds `@electric-sql/pglite` (optional peer, dynamic import).
3
+ *
4
+ * Protocol-named transport: PGlite is Postgres compiled to WASM — same wire
5
+ * dialect as `postgres` (`?` params convert to `$n`), different transport.
6
+ * drizzle-kit dialect: `postgresql`.
7
+ *
8
+ * Honest latency trade-off (measured on `@electric-sql/pglite@0.5.4`): first
9
+ * init ~900 ms one-time (WASM instantiation); reopen of an existing datadir
10
+ * ~44 ms; warm CRUD ~45 ms vs ~3 ms for `bun:sqlite` (~15× slower). Choose
11
+ * `pglite` for dialect/pgvector parity on a laptop — never as a
12
+ * latency-competitive default. `local` stays on `sqlite`.
13
+ */
14
+
15
+ import { toPostgresParams } from "./postgres.ts";
16
+ import type { SqlConnectOptions, SqlConnection, SqlDriver, SqlRow } from "./types.ts";
17
+
18
+ /** `@electric-sql/pglite` is an optional peer — loaded only when this driver runs. */
19
+ type PgliteModule = typeof import("@electric-sql/pglite");
20
+ type PgliteVectorModule = typeof import("@electric-sql/pglite-pgvector");
21
+
22
+ async function loadPglite(): Promise<PgliteModule> {
23
+ try {
24
+ return await import("@electric-sql/pglite");
25
+ } catch {
26
+ throw new Error(
27
+ "pglite driver: install optional peer `@electric-sql/pglite` (bun add @electric-sql/pglite)",
28
+ );
29
+ }
30
+ }
31
+
32
+ async function loadPgliteVector(): Promise<PgliteVectorModule> {
33
+ try {
34
+ return await import("@electric-sql/pglite-pgvector");
35
+ } catch {
36
+ throw new Error(
37
+ "pglite driver: install optional peer `@electric-sql/pglite-pgvector` (bun add @electric-sql/pglite-pgvector)",
38
+ );
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Open a PGlite connection implementing {@link SqlConnection}.
44
+ *
45
+ * The pgvector extension is loaded at `PGlite.create` so `CREATE EXTENSION
46
+ * vector` in the pgvector index driver succeeds — same real-ANN code path as
47
+ * `postgres`.
48
+ *
49
+ * @param options - URL (`memory://` or a datadir path; default `.oke/pgdata`) / role
50
+ */
51
+ export async function connectPglite(options: SqlConnectOptions = {}): Promise<SqlConnection> {
52
+ const role = options.role ?? "primary";
53
+ const { PGlite } = await loadPglite();
54
+ const { vector } = await loadPgliteVector();
55
+ const dataDir = options.url && options.url.length > 0 ? options.url : ".oke/pgdata";
56
+ const db = await PGlite.create(dataDir, { extensions: { vector } });
57
+ return {
58
+ driverId: "pglite",
59
+ role,
60
+ async query(sql, params = []) {
61
+ const rs = await db.query<SqlRow>(toPostgresParams(sql), [...params]);
62
+ return rs.rows;
63
+ },
64
+ async exec(sql, params = []) {
65
+ const rs = await db.query<SqlRow>(toPostgresParams(sql), [...params]);
66
+ return { changes: rs.affectedRows ?? 0 };
67
+ },
68
+ async close() {
69
+ await db.close();
70
+ },
71
+ };
72
+ }
73
+
74
+ /** Protocol-named pglite driver. */
75
+ export const pgliteDriver: SqlDriver = {
76
+ id: "pglite",
77
+ facet: "sql",
78
+ connect: connectPglite,
79
+ };
@@ -1,10 +1,18 @@
1
1
  /**
2
2
  * `pgvector` driver — vector index facet over Postgres + pgvector.
3
3
  *
4
+ * Real ANN via drizzle-orm's pg vector API: `vector(dims)` column, HNSW index
5
+ * (`vector_cosine_ops`), `cosineDistance` ordering — no JS-side distance math
6
+ * on the SQL path. One implementation serves both the `postgres` and `pglite`
7
+ * sql drivers (identical wire dialect); the caller injects the already-open
8
+ * {@link SqlConnection} (shared with `store.sql`, never a second connection).
9
+ *
4
10
  * When no SQL connection is provided, falls back to an in-process cosine index
5
11
  * so the conformance suite runs without a live Postgres.
6
12
  */
7
13
 
14
+ import { cosineDistance } from "drizzle-orm";
15
+ import { PgDialect, index as pgIndex, pgTable, text, vector } from "drizzle-orm/pg-core";
8
16
  import type {
9
17
  IndexDriver,
10
18
  IndexHit,
@@ -26,48 +34,75 @@ export async function openPgvectorIndex(options: IndexOpenOptions): Promise<Inde
26
34
  return openPgvectorMemory(options.dims);
27
35
  }
28
36
 
37
+ const pgDialect = new PgDialect();
38
+
29
39
  async function openPgvectorSql(
30
40
  name: string,
31
41
  dims: number,
32
42
  sql: SqlConnection,
33
43
  ): Promise<IndexStore> {
34
- const table = `oke_idx_${name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
44
+ if (sql.driverId !== "postgres" && sql.driverId !== "pglite") {
45
+ throw new Error(
46
+ `pgvector index: needs a postgres/pglite SQL connection, got "${sql.driverId}" — ` +
47
+ `set store.sql to "postgres" or "pglite" so the index shares its connection`,
48
+ );
49
+ }
50
+
51
+ const tableName = `oke_idx_${name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
52
+ const indexName = `${tableName}_hnsw`;
53
+ const table = pgTable(
54
+ tableName,
55
+ {
56
+ id: text("id").primaryKey(),
57
+ embedding: vector("embedding", { dimensions: dims }).notNull(),
58
+ meta: text("meta"),
59
+ },
60
+ (t) => [pgIndex(indexName).using("hnsw", t.embedding.op("vector_cosine_ops"))],
61
+ );
62
+
63
+ // Extension + DDL — fail loud when pgvector is not installed (never fall back).
64
+ await sql.exec(`CREATE EXTENSION IF NOT EXISTS vector`);
35
65
  await sql.exec(
36
- `CREATE TABLE IF NOT EXISTS "${table}" (id TEXT PRIMARY KEY, embedding TEXT NOT NULL, meta TEXT)`,
66
+ `CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT PRIMARY KEY, embedding vector(${dims}) NOT NULL, meta TEXT)`,
67
+ );
68
+ await sql.exec(
69
+ `CREATE INDEX IF NOT EXISTS "${indexName}" ON "${tableName}" USING hnsw (embedding vector_cosine_ops)`,
37
70
  );
38
71
 
39
72
  return {
40
73
  driverId: "pgvector",
41
- async upsert(id, vector, meta) {
42
- if (vector.length !== dims) {
43
- throw new Error(`vector dims ${vector.length} !== index dims ${dims}`);
74
+ async upsert(id, vec, meta) {
75
+ if (vec.length !== dims) {
76
+ throw new Error(`vector dims ${vec.length} !== index dims ${dims}`);
44
77
  }
45
- await sql.exec(`DELETE FROM "${table}" WHERE id = ?`, [id]);
46
- await sql.exec(`INSERT INTO "${table}" (id, embedding, meta) VALUES (?, ?, ?)`, [
47
- id,
48
- JSON.stringify(vector),
49
- meta ? JSON.stringify(meta) : null,
50
- ]);
78
+ await sql.exec(
79
+ `INSERT INTO "${tableName}" (id, embedding, meta) VALUES (?, ?::vector, ?) ` +
80
+ `ON CONFLICT (id) DO UPDATE SET embedding = excluded.embedding, meta = excluded.meta`,
81
+ [id, JSON.stringify(vec), meta ? JSON.stringify(meta) : null],
82
+ );
51
83
  },
52
- async search(vector, topK = 10): Promise<IndexHit[]> {
53
- const rows = await sql.query(`SELECT id, embedding, meta FROM "${table}"`);
54
- const hits: IndexHit[] = rows.map((row) => {
55
- const emb = JSON.parse(String(row.embedding)) as number[];
84
+ async search(vec, topK = 10): Promise<IndexHit[]> {
85
+ const distance = pgDialect.sqlToQuery(cosineDistance(table.embedding, [...vec]));
86
+ const distanceSql = distance.sql.replace(/\$\d+/g, "?");
87
+ const rows = await sql.query(
88
+ `SELECT "id", "meta", ${distanceSql} AS "distance" FROM "${tableName}" ` +
89
+ `ORDER BY "distance" ASC LIMIT ?`,
90
+ [...distance.params, Math.max(1, Math.floor(topK))],
91
+ );
92
+ return rows.map((row) => {
56
93
  const metaRaw = row.meta;
57
94
  return {
58
95
  id: String(row.id),
59
- score: cosine(vector, emb),
96
+ score: 1 - Number(row.distance),
60
97
  meta:
61
98
  typeof metaRaw === "string"
62
99
  ? (JSON.parse(metaRaw) as Record<string, unknown>)
63
100
  : undefined,
64
101
  };
65
102
  });
66
- hits.sort((a, b) => b.score - a.score);
67
- return hits.slice(0, topK);
68
103
  },
69
104
  async delete(id) {
70
- const result = await sql.exec(`DELETE FROM "${table}" WHERE id = ?`, [id]);
105
+ const result = await sql.exec(`DELETE FROM "${tableName}" WHERE id = ?`, [id]);
71
106
  return result.changes > 0;
72
107
  },
73
108
  async close() {
@@ -8,7 +8,16 @@
8
8
  import type { ColumnClassification, StoreFacet } from "../manifest/types.ts";
9
9
 
10
10
  /** Protocol ids for store drivers in scope. */
11
- export type StoreDriverId = "sqlite" | "postgres" | "memory" | "redis" | "fs" | "s3" | "pgvector";
11
+ export type StoreDriverId =
12
+ | "sqlite"
13
+ | "postgres"
14
+ | "libsql"
15
+ | "pglite"
16
+ | "memory"
17
+ | "redis"
18
+ | "fs"
19
+ | "s3"
20
+ | "pgvector";
12
21
 
13
22
  /** Facets a driver may serve. */
14
23
  export type DriverFacet = StoreFacet;
@@ -37,7 +46,7 @@ export interface SqlConnectOptions {
37
46
  /** Low-level SQL connection used by every sql driver. */
38
47
  export interface SqlConnection {
39
48
  /** Protocol driver id. */
40
- readonly driverId: "sqlite" | "postgres" | "memory";
49
+ readonly driverId: "sqlite" | "postgres" | "libsql" | "pglite" | "memory";
41
50
  /** Primary vs replica. */
42
51
  readonly role: SqlRole;
43
52
  /**
@@ -61,7 +70,7 @@ export interface SqlConnection {
61
70
  /** SQL driver factory. */
62
71
  export interface SqlDriver {
63
72
  /** Protocol id. */
64
- readonly id: "sqlite" | "postgres" | "memory";
73
+ readonly id: "sqlite" | "postgres" | "libsql" | "pglite" | "memory";
65
74
  /** Facet this driver serves. */
66
75
  readonly facet: "sql";
67
76
  /**
@@ -248,7 +257,7 @@ export interface IndexHit {
248
257
 
249
258
  /** Vector index handle. */
250
259
  export interface IndexStore {
251
- readonly driverId: "memory" | "pgvector";
260
+ readonly driverId: "memory" | "pgvector" | "libsql";
252
261
  /**
253
262
  * Upsert a vector.
254
263
  *
@@ -280,15 +289,15 @@ export interface IndexOpenOptions {
280
289
  readonly name: string;
281
290
  /** Vector dimensions. */
282
291
  readonly dims: number;
283
- /** Postgres URL when using pgvector. */
292
+ /** SQL URL when the index driver shares the sql facet's engine. */
284
293
  readonly url?: string;
285
- /** Injected SQL connection for pgvector tests. */
294
+ /** Injected SQL connection shared with `store.sql` at boot. */
286
295
  readonly sql?: SqlConnection;
287
296
  }
288
297
 
289
298
  /** Index driver factory. */
290
299
  export interface IndexDriver {
291
- readonly id: "memory" | "pgvector";
300
+ readonly id: "memory" | "pgvector" | "libsql";
292
301
  readonly facet: "index";
293
302
  /**
294
303
  * Open an index.
@@ -14,6 +14,8 @@ export interface ChannelMediumOptions {
14
14
 
15
15
  /** Options for {@link channel.template} / medium `.template`. */
16
16
  export interface ChannelTemplateOptions {
17
+ /** Optional human description for Console / docs (falls back to the template name). */
18
+ readonly description?: string;
17
19
  readonly medium?: ChannelMedium;
18
20
  readonly locales?: string[];
19
21
  readonly schema?: unknown;
@@ -24,6 +26,7 @@ export interface ChannelTemplateOptions {
24
26
  export interface ChannelTemplateDecl {
25
27
  readonly kind: "template";
26
28
  readonly name: string;
29
+ readonly description?: string;
27
30
  readonly medium: ChannelMedium;
28
31
  readonly locales?: string[];
29
32
  readonly schema?: unknown;
@@ -65,6 +68,7 @@ function mediumBinder(
65
68
  kind: "template",
66
69
  name,
67
70
  medium,
71
+ ...(opts.description !== undefined ? { description: opts.description } : {}),
68
72
  ...(opts.locales ? { locales: opts.locales } : {}),
69
73
  ...(opts.schema !== undefined ? { schema: opts.schema } : {}),
70
74
  ...(from !== undefined ? { from } : {}),
@@ -110,6 +114,7 @@ export const channel: ChannelNamespace = {
110
114
  kind: "template",
111
115
  name,
112
116
  medium: options.medium ?? "email",
117
+ ...(options.description !== undefined ? { description: options.description } : {}),
113
118
  ...(options.locales ? { locales: options.locales } : {}),
114
119
  ...(options.schema !== undefined ? { schema: options.schema } : {}),
115
120
  ...(options.from !== undefined ? { from: options.from } : {}),
@@ -18,6 +18,8 @@ export interface ClockOptions {
18
18
  * Without it, no override is possible (console §4.1).
19
19
  */
20
20
  readonly overridable?: boolean;
21
+ /** Optional human description for Console / docs (falls back to the clock name). */
22
+ readonly description?: string;
21
23
  }
22
24
 
23
25
  /**
@@ -34,6 +36,8 @@ export interface ClockDecl {
34
36
  readonly timezone: string;
35
37
  /** Whether Console override is allowed. */
36
38
  readonly overridable: boolean;
39
+ /** Optional human description. */
40
+ readonly description?: string;
37
41
  }
38
42
 
39
43
  /**
@@ -52,5 +56,6 @@ export function clock(name: string, options: ClockOptions = {}): ClockDecl {
52
56
  every: options.every,
53
57
  timezone: options.timezone ?? "UTC",
54
58
  overridable: options.overridable ?? false,
59
+ ...(options.description !== undefined ? { description: options.description } : {}),
55
60
  };
56
61
  }
@@ -6,6 +6,7 @@
6
6
  * step never re-runs (four-applications · Provisions).
7
7
  */
8
8
 
9
+ import { fxRetry } from "../../kernel/concurrency.ts";
9
10
  import type { AnyFlowDef } from "../../kernel/flow.ts";
10
11
  import { createFxContext, type CreateFxOptions, type Fx } from "../../kernel/fx.ts";
11
12
  import {
@@ -95,7 +96,12 @@ export async function runDurable<O = unknown>(
95
96
  });
96
97
 
97
98
  try {
98
- const output = await options.flow.do(options.input as never, fx);
99
+ const run = () => {
100
+ // Same journal session across attempts — rewind so completed steps replay.
101
+ session.rewind();
102
+ return options.flow.do(options.input as never, fx);
103
+ };
104
+ const output = await (options.flow.retry ? fxRetry(run, options.flow.retry) : run());
99
105
  await session.commit("completed", { output });
100
106
  return {
101
107
  status: "completed",
@@ -45,6 +45,16 @@ export interface RateOptions {
45
45
  * Without it, no override is possible (console §4.1).
46
46
  */
47
47
  readonly overridable?: boolean;
48
+ /** Optional human description for Console / docs (falls back to the rate name). */
49
+ readonly description?: string;
50
+ }
51
+
52
+ /** Options object form of {@link gate.policy} (keeps the two-arg check form valid). */
53
+ export interface PolicyGateOptions {
54
+ /** Predicate over {@link GatePolicyContext}. */
55
+ readonly check: (ctx: GatePolicyContext) => boolean | Promise<boolean>;
56
+ /** Optional human description for Console / docs (falls back to the policy name). */
57
+ readonly description?: string;
48
58
  }
49
59
 
50
60
  /** Policy gate declaration. */
@@ -52,6 +62,7 @@ export interface PolicyGateDecl {
52
62
  readonly kind: "policy";
53
63
  readonly name: string;
54
64
  readonly check: (ctx: GatePolicyContext) => boolean | Promise<boolean>;
65
+ readonly description?: string;
55
66
  }
56
67
 
57
68
  /** Rate gate declaration. */
@@ -63,6 +74,7 @@ export interface RateGateDecl {
63
74
  readonly per: string;
64
75
  readonly keyBy?: string;
65
76
  readonly overridable: boolean;
77
+ readonly description?: string;
66
78
  }
67
79
 
68
80
  /** Declared gate handle. */
@@ -76,11 +88,11 @@ export interface GateNamespace {
76
88
  * Declare a named ABAC / auth policy.
77
89
  *
78
90
  * @param name - Policy id (also a Module:Action when it contains `:`)
79
- * @param check - Predicate over {@link GatePolicyContext}
91
+ * @param checkOrOptions - Predicate, or `{ check, description? }`
80
92
  */
81
93
  policy(
82
94
  name: string,
83
- check: (ctx: GatePolicyContext) => boolean | Promise<boolean>,
95
+ checkOrOptions: ((ctx: GatePolicyContext) => boolean | Promise<boolean>) | PolicyGateOptions,
84
96
  ): PolicyGateDecl;
85
97
  /**
86
98
  * Declare a rate limit (atomic Lua on the kv driver).
@@ -98,13 +110,23 @@ export const gate: GateNamespace = {
98
110
  * Declare a named ABAC / auth policy.
99
111
  *
100
112
  * @param name - Policy id (also a Module:Action when it contains `:`)
101
- * @param check - Predicate over {@link GatePolicyContext}
113
+ * @param checkOrOptions - Predicate, or `{ check, description? }`
102
114
  */
103
115
  policy(
104
116
  name: string,
105
- check: (ctx: GatePolicyContext) => boolean | Promise<boolean>,
117
+ checkOrOptions: ((ctx: GatePolicyContext) => boolean | Promise<boolean>) | PolicyGateOptions,
106
118
  ): PolicyGateDecl {
107
- return { kind: "policy", name, check };
119
+ if (typeof checkOrOptions === "function") {
120
+ return { kind: "policy", name, check: checkOrOptions };
121
+ }
122
+ return {
123
+ kind: "policy",
124
+ name,
125
+ check: checkOrOptions.check,
126
+ ...(checkOrOptions.description !== undefined
127
+ ? { description: checkOrOptions.description }
128
+ : {}),
129
+ };
108
130
  },
109
131
 
110
132
  /**
@@ -129,6 +151,7 @@ export const gate: GateNamespace = {
129
151
  per: options.per,
130
152
  keyBy: options.keyBy,
131
153
  overridable: options.overridable ?? false,
154
+ ...(options.description !== undefined ? { description: options.description } : {}),
132
155
  };
133
156
  },
134
157
  };
@@ -13,6 +13,7 @@ export type {
13
13
  GateDecl,
14
14
  GatePolicyContext,
15
15
  PolicyGateDecl,
16
+ PolicyGateOptions,
16
17
  RateGateDecl,
17
18
  RateOptions,
18
19
  } from "./gate/declare.ts";
@@ -17,6 +17,8 @@ export interface SignalOptions {
17
17
  * - `live` — client-subscribable stream (replayable)
18
18
  */
19
19
  readonly delivery: SignalDelivery;
20
+ /** Optional human description for Console / docs (falls back to the signal name). */
21
+ readonly description?: string;
20
22
  /** Max delivery attempts before dead-letter (once). */
21
23
  readonly retries?: number;
22
24
  /** Preserve exhausted messages in the DLQ (once). */
@@ -35,6 +37,8 @@ export interface SignalDecl<T = unknown> {
35
37
  readonly name: string;
36
38
  /** Delivery physics. */
37
39
  readonly delivery: SignalDelivery;
40
+ /** Optional human description. */
41
+ readonly description?: string;
38
42
  /** Max retries before DLQ. */
39
43
  readonly retries: number;
40
44
  /** Whether exhausted messages enter the DLQ. */
@@ -64,6 +68,7 @@ export function signal<T = unknown>(name: string, options: SignalOptions): Signa
64
68
  return {
65
69
  name,
66
70
  delivery: options.delivery,
71
+ ...(options.description !== undefined ? { description: options.description } : {}),
67
72
  retries: options.retries ?? 3,
68
73
  deadLetter: options.deadLetter ?? true,
69
74
  schema: options.schema,