okengine 0.3.4 → 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.
- package/AGENTS.md +4 -0
- package/manifest.v1.schema.json +43 -1
- package/package.json +16 -2
- package/site/content/docs/ai/skills.mdx +9 -7
- package/site/content/docs/console/vault.mdx +4 -0
- package/site/content/docs/elements/channel.mdx +5 -4
- package/site/content/docs/elements/clock.mdx +1 -0
- package/site/content/docs/elements/flow.mdx +15 -0
- package/site/content/docs/elements/gate.mdx +16 -11
- package/site/content/docs/elements/signal.mdx +8 -7
- package/site/content/docs/elements/store.mdx +48 -8
- package/site/content/docs/elements/vault.mdx +1 -1
- package/site/content/docs/plugins/ip-allowlist.mdx +2 -2
- package/site/content/docs/reference/configuration.mdx +2 -2
- package/site/content/docs/reference/environment-variables.mdx +8 -0
- package/site/content/docs/reference/fx.mdx +40 -0
- package/site/content/docs/reference/plugins.mdx +18 -18
- package/src/compiler/extract.test.ts +63 -0
- package/src/compiler/extract.ts +36 -15
- 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 +24 -0
- 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-BWo8R7NR.js → index-CrKMmO__.js} +2 -2
- package/src/console/ui/dist/assets/panel-channels-DCDd4WAC.js +1 -0
- package/src/console/ui/dist/assets/panel-clock-DjGGFPzr.js +1 -0
- package/src/console/ui/dist/assets/panel-gates-B5eTE8XH.js +1 -0
- package/src/console/ui/dist/assets/{panel-overview-BznEOTnb.js → panel-overview-BsFvDdts.js} +1 -1
- package/src/console/ui/dist/assets/panel-plugins-Cj7DK1er.js +1 -0
- package/src/console/ui/dist/assets/{panel-runs-CGWNHLR4.js → panel-runs-C0gmnoYL.js} +1 -1
- package/src/console/ui/dist/assets/panel-signals-whmDXIg3.js +1 -0
- package/src/console/ui/dist/assets/panel-store-CEMHLvaw.js +1 -0
- package/src/console/ui/dist/assets/{panel-traces-DBLx2ilD.js → panel-traces-BDiAuVSK.js} +1 -1
- package/src/console/ui/dist/assets/panel-vault-C9wjbki8.js +1 -0
- package/src/console/ui/dist/index.html +1 -1
- 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/drivers/conformance.test.ts +11 -0
- package/src/drivers/drizzle-dialect.test.ts +4 -0
- package/src/drivers/drizzle-dialect.ts +8 -4
- package/src/drivers/index.ts +4 -0
- package/src/drivers/libsql.ts +179 -0
- package/src/drivers/pglite.ts +79 -0
- package/src/drivers/pgvector.ts +54 -19
- package/src/drivers/types.ts +16 -7
- 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 +257 -0
- package/src/elements/store/runtime.ts +67 -9
- package/src/elements/store/schema-decl.ts +7 -0
- package/src/kernel/abort-scope.ts +116 -0
- package/src/kernel/app.ts +12 -2
- package/src/kernel/boot-bind/store.test.ts +59 -1
- package/src/kernel/boot-bind/store.ts +64 -2
- package/src/kernel/concurrency.test.ts +236 -0
- package/src/kernel/concurrency.ts +172 -0
- package/src/kernel/flow.ts +9 -0
- package/src/kernel/fx.test.ts +12 -0
- package/src/kernel/fx.ts +44 -0
- package/src/kernel/index.ts +14 -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/registry.ts +29 -6
- package/src/manifest/types.ts +21 -0
- package/src/release/measure.ts +4 -0
- 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-gates-XclZxWD5.js +0 -1
- package/src/console/ui/dist/assets/panel-plugins-CcGM1g64.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-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
|
|
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
|
}
|
package/src/drivers/index.ts
CHANGED
|
@@ -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
|
+
};
|
package/src/drivers/pgvector.ts
CHANGED
|
@@ -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
|
-
|
|
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 "${
|
|
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,
|
|
42
|
-
if (
|
|
43
|
-
throw new Error(`vector 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(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
JSON.stringify(
|
|
49
|
-
|
|
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(
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
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:
|
|
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 "${
|
|
105
|
+
const result = await sql.exec(`DELETE FROM "${tableName}" WHERE id = ?`, [id]);
|
|
71
106
|
return result.changes > 0;
|
|
72
107
|
},
|
|
73
108
|
async close() {
|
package/src/drivers/types.ts
CHANGED
|
@@ -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 =
|
|
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
|
-
/**
|
|
292
|
+
/** SQL URL when the index driver shares the sql facet's engine. */
|
|
284
293
|
readonly url?: string;
|
|
285
|
-
/** Injected SQL connection
|
|
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
|
|
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
|
|
91
|
+
* @param checkOrOptions - Predicate, or `{ check, description? }`
|
|
80
92
|
*/
|
|
81
93
|
policy(
|
|
82
94
|
name: string,
|
|
83
|
-
|
|
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
|
|
113
|
+
* @param checkOrOptions - Predicate, or `{ check, description? }`
|
|
102
114
|
*/
|
|
103
115
|
policy(
|
|
104
116
|
name: string,
|
|
105
|
-
|
|
117
|
+
checkOrOptions: ((ctx: GatePolicyContext) => boolean | Promise<boolean>) | PolicyGateOptions,
|
|
106
118
|
): PolicyGateDecl {
|
|
107
|
-
|
|
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
|
};
|
package/src/elements/gate.ts
CHANGED
|
@@ -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,
|