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,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,16 +1,24 @@
|
|
|
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
|
-
IndexDriver,
|
|
10
17
|
IndexHit,
|
|
11
18
|
IndexOpenOptions,
|
|
12
|
-
IndexStore,
|
|
13
19
|
SqlConnection,
|
|
20
|
+
VectorIndexDriver,
|
|
21
|
+
VectorIndexStore,
|
|
14
22
|
} from "./types.ts";
|
|
15
23
|
|
|
16
24
|
/**
|
|
@@ -18,7 +26,7 @@ import type {
|
|
|
18
26
|
*
|
|
19
27
|
* @param options - Name / dims / SQL connection
|
|
20
28
|
*/
|
|
21
|
-
export async function openPgvectorIndex(options: IndexOpenOptions): Promise<
|
|
29
|
+
export async function openPgvectorIndex(options: IndexOpenOptions): Promise<VectorIndexStore> {
|
|
22
30
|
if (options.sql) {
|
|
23
31
|
return openPgvectorSql(options.name, options.dims, options.sql);
|
|
24
32
|
}
|
|
@@ -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
|
-
): Promise<
|
|
34
|
-
|
|
43
|
+
): Promise<VectorIndexStore> {
|
|
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() {
|
|
@@ -76,7 +111,7 @@ async function openPgvectorSql(
|
|
|
76
111
|
};
|
|
77
112
|
}
|
|
78
113
|
|
|
79
|
-
function openPgvectorMemory(dims: number):
|
|
114
|
+
function openPgvectorMemory(dims: number): VectorIndexStore {
|
|
80
115
|
const docs = new Map<string, { vector: number[]; meta?: Record<string, unknown> }>();
|
|
81
116
|
return {
|
|
82
117
|
driverId: "pgvector",
|
|
@@ -119,7 +154,7 @@ function cosine(a: readonly number[], b: readonly number[]): number {
|
|
|
119
154
|
}
|
|
120
155
|
|
|
121
156
|
/** Protocol-named pgvector driver. */
|
|
122
|
-
export const pgvectorDriver:
|
|
157
|
+
export const pgvectorDriver: VectorIndexDriver = {
|
|
123
158
|
id: "pgvector",
|
|
124
159
|
facet: "index",
|
|
125
160
|
open: openPgvectorIndex,
|
package/src/drivers/types.ts
CHANGED
|
@@ -8,7 +8,17 @@
|
|
|
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"
|
|
21
|
+
| "meilisearch";
|
|
12
22
|
|
|
13
23
|
/** Facets a driver may serve. */
|
|
14
24
|
export type DriverFacet = StoreFacet;
|
|
@@ -37,7 +47,7 @@ export interface SqlConnectOptions {
|
|
|
37
47
|
/** Low-level SQL connection used by every sql driver. */
|
|
38
48
|
export interface SqlConnection {
|
|
39
49
|
/** Protocol driver id. */
|
|
40
|
-
readonly driverId: "sqlite" | "postgres" | "memory";
|
|
50
|
+
readonly driverId: "sqlite" | "postgres" | "libsql" | "pglite" | "memory";
|
|
41
51
|
/** Primary vs replica. */
|
|
42
52
|
readonly role: SqlRole;
|
|
43
53
|
/**
|
|
@@ -61,7 +71,7 @@ export interface SqlConnection {
|
|
|
61
71
|
/** SQL driver factory. */
|
|
62
72
|
export interface SqlDriver {
|
|
63
73
|
/** Protocol id. */
|
|
64
|
-
readonly id: "sqlite" | "postgres" | "memory";
|
|
74
|
+
readonly id: "sqlite" | "postgres" | "libsql" | "pglite" | "memory";
|
|
65
75
|
/** Facet this driver serves. */
|
|
66
76
|
readonly facet: "sql";
|
|
67
77
|
/**
|
|
@@ -239,16 +249,20 @@ export interface FilesDriver {
|
|
|
239
249
|
open(options: FilesOpenOptions): Promise<FilesBucket>;
|
|
240
250
|
}
|
|
241
251
|
|
|
242
|
-
/**
|
|
252
|
+
/** Index hit — score meaning depends on the driver. */
|
|
243
253
|
export interface IndexHit {
|
|
244
254
|
readonly id: string;
|
|
255
|
+
/**
|
|
256
|
+
* Vector drivers: cosine similarity (higher = more similar).
|
|
257
|
+
* Full-text (`meilisearch`): relevance score (`_rankingScore`, higher = more relevant).
|
|
258
|
+
*/
|
|
245
259
|
readonly score: number;
|
|
246
260
|
readonly meta?: Record<string, unknown>;
|
|
247
261
|
}
|
|
248
262
|
|
|
249
|
-
/** Vector index handle. */
|
|
250
|
-
export interface
|
|
251
|
-
readonly driverId: "memory" | "pgvector";
|
|
263
|
+
/** Vector (ANN) index handle. */
|
|
264
|
+
export interface VectorIndexStore {
|
|
265
|
+
readonly driverId: "memory" | "pgvector" | "libsql";
|
|
252
266
|
/**
|
|
253
267
|
* Upsert a vector.
|
|
254
268
|
*
|
|
@@ -263,7 +277,7 @@ export interface IndexStore {
|
|
|
263
277
|
* @param vector - Query embedding
|
|
264
278
|
* @param topK - Max hits
|
|
265
279
|
*/
|
|
266
|
-
search(vector: readonly number[], topK?: number): Promise<IndexHit[]>;
|
|
280
|
+
search(vector: readonly number[], topK?: number): Promise<readonly IndexHit[]>;
|
|
267
281
|
/**
|
|
268
282
|
* Delete a vector.
|
|
269
283
|
*
|
|
@@ -274,28 +288,104 @@ export interface IndexStore {
|
|
|
274
288
|
close(): Promise<void>;
|
|
275
289
|
}
|
|
276
290
|
|
|
291
|
+
/** Full-text search options (meilisearch). */
|
|
292
|
+
export interface TextIndexSearchOptions {
|
|
293
|
+
/** Max hits (Meilisearch `limit`). */
|
|
294
|
+
readonly topK?: number;
|
|
295
|
+
/** Filter expression (attribute must be filterable). */
|
|
296
|
+
readonly filter?: string | string[];
|
|
297
|
+
/** Facet attributes to aggregate. */
|
|
298
|
+
readonly facets?: string[];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Full-text search result (meilisearch). */
|
|
302
|
+
export interface TextIndexSearchResult {
|
|
303
|
+
readonly hits: readonly IndexHit[];
|
|
304
|
+
/** Facet counts per attribute, when `facets` was requested. */
|
|
305
|
+
readonly facetDistribution?: Record<string, Record<string, number>>;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Full-text index handle. */
|
|
309
|
+
export interface TextIndexStore {
|
|
310
|
+
readonly driverId: "meilisearch";
|
|
311
|
+
/**
|
|
312
|
+
* Upsert a document.
|
|
313
|
+
*
|
|
314
|
+
* @param id - Document id
|
|
315
|
+
* @param document - Field map (must include the id field)
|
|
316
|
+
*/
|
|
317
|
+
upsert(id: string, document: Record<string, unknown>): Promise<void>;
|
|
318
|
+
/**
|
|
319
|
+
* Full-text search.
|
|
320
|
+
*
|
|
321
|
+
* @param q - Query text (typo tolerant)
|
|
322
|
+
* @param opts - topK / filter / facets
|
|
323
|
+
*/
|
|
324
|
+
search(q: string, opts?: TextIndexSearchOptions): Promise<TextIndexSearchResult>;
|
|
325
|
+
/**
|
|
326
|
+
* Delete a document.
|
|
327
|
+
*
|
|
328
|
+
* @param id - Document id
|
|
329
|
+
*/
|
|
330
|
+
delete(id: string): Promise<boolean>;
|
|
331
|
+
/** Close / release. */
|
|
332
|
+
close(): Promise<void>;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Index handle — discriminated by `driverId`. */
|
|
336
|
+
export type IndexStore = VectorIndexStore | TextIndexStore;
|
|
337
|
+
|
|
277
338
|
/** Options when opening an index. */
|
|
278
339
|
export interface IndexOpenOptions {
|
|
279
340
|
/** Index name. */
|
|
280
341
|
readonly name: string;
|
|
281
|
-
/** Vector dimensions. */
|
|
342
|
+
/** Vector dimensions (vector drivers only). */
|
|
282
343
|
readonly dims: number;
|
|
283
|
-
/**
|
|
344
|
+
/**
|
|
345
|
+
* Vector: SQL URL when the driver shares the sql facet's engine.
|
|
346
|
+
* Meilisearch: base HTTP URL.
|
|
347
|
+
*/
|
|
284
348
|
readonly url?: string;
|
|
285
|
-
/** Injected SQL connection
|
|
349
|
+
/** Injected SQL connection — shared with `store.sql` at boot (vector only). */
|
|
286
350
|
readonly sql?: SqlConnection;
|
|
351
|
+
/** Meilisearch API / master key. */
|
|
352
|
+
readonly apiKey?: string;
|
|
353
|
+
/** Injected fetch (tests). */
|
|
354
|
+
readonly fetch?: typeof fetch;
|
|
287
355
|
}
|
|
288
356
|
|
|
289
|
-
/**
|
|
290
|
-
export interface
|
|
291
|
-
readonly id: "memory" | "pgvector";
|
|
357
|
+
/** Vector index driver factory. */
|
|
358
|
+
export interface VectorIndexDriver {
|
|
359
|
+
readonly id: "memory" | "pgvector" | "libsql";
|
|
292
360
|
readonly facet: "index";
|
|
293
361
|
/**
|
|
294
|
-
* Open
|
|
362
|
+
* Open a vector index.
|
|
295
363
|
*
|
|
296
364
|
* @param options - Name / dims / SQL
|
|
297
365
|
*/
|
|
298
|
-
open(options: IndexOpenOptions): Promise<
|
|
366
|
+
open(options: IndexOpenOptions): Promise<VectorIndexStore>;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Full-text index driver factory. */
|
|
370
|
+
export interface TextIndexDriver {
|
|
371
|
+
readonly id: "meilisearch";
|
|
372
|
+
readonly facet: "index";
|
|
373
|
+
/**
|
|
374
|
+
* Open a full-text index.
|
|
375
|
+
*
|
|
376
|
+
* @param options - Name / HTTP URL / apiKey
|
|
377
|
+
*/
|
|
378
|
+
open(options: IndexOpenOptions): Promise<TextIndexStore>;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Index driver factory — discriminated by `id`. */
|
|
382
|
+
export type IndexDriver = VectorIndexDriver | TextIndexDriver;
|
|
383
|
+
|
|
384
|
+
/** True when the index driver shares the sql facet's engine (never memory). */
|
|
385
|
+
export function indexDriverNeedsSql(
|
|
386
|
+
driver: IndexDriver,
|
|
387
|
+
): driver is VectorIndexDriver & { id: "pgvector" | "libsql" } {
|
|
388
|
+
return driver.id === "pgvector" || driver.id === "libsql";
|
|
299
389
|
}
|
|
300
390
|
|
|
301
391
|
/** Union of all store drivers. */
|
|
@@ -12,6 +12,8 @@ const ROOT = join(import.meta.dir, "../..");
|
|
|
12
12
|
|
|
13
13
|
/** Forbidden driver id, split so this file does not match itself. */
|
|
14
14
|
const FORBIDDEN_DRIVER = ["so", "ps"].join("");
|
|
15
|
+
/** Forbidden vault driver id, split so this file does not match itself. */
|
|
16
|
+
const FORBIDDEN_SECRETS_SAAS = ["infi", "sical"].join("");
|
|
15
17
|
/** Forbidden optional peer, split so this file does not match itself. */
|
|
16
18
|
const FORBIDDEN_AGE = ["age", "-", "encryption"].join("");
|
|
17
19
|
|
|
@@ -49,6 +51,10 @@ describe("removed vault driver gate", () => {
|
|
|
49
51
|
assertZeroGitGrep(FORBIDDEN_DRIVER);
|
|
50
52
|
});
|
|
51
53
|
|
|
54
|
+
test("tracked tree has zero matches for the removed secrets-saas driver id", () => {
|
|
55
|
+
assertZeroGitGrep(FORBIDDEN_SECRETS_SAAS, ["changelog.md:"]);
|
|
56
|
+
});
|
|
57
|
+
|
|
52
58
|
test("tracked tree has zero matches for the removed age peer outside lockfiles", () => {
|
|
53
59
|
assertZeroGitGrep(FORBIDDEN_AGE, LOCKFILE_PREFIXES);
|
|
54
60
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Protocol-named vault driver contracts.
|
|
3
3
|
*
|
|
4
|
-
* Driver ids: `env` · `openbao` · `
|
|
5
|
-
* Never vendor names (`neon`,
|
|
4
|
+
* Driver ids: `env` · `openbao` · `managed` · `memory`.
|
|
5
|
+
* Never vendor names (`neon`, …) — vendor choice lives in `images`.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/** Protocol ids for vault drivers. */
|
|
9
|
-
export type VaultDriverId = "env" | "openbao" | "
|
|
9
|
+
export type VaultDriverId = "env" | "openbao" | "managed" | "memory";
|
|
10
10
|
|
|
11
11
|
/** Options when opening a vault backend. */
|
|
12
12
|
export interface VaultOpenOptions {
|
|
@@ -16,7 +16,7 @@ export interface VaultOpenOptions {
|
|
|
16
16
|
readonly envPrefix?: string;
|
|
17
17
|
/** Injected env map for tests (defaults to `process.env`). */
|
|
18
18
|
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
19
|
-
/** OpenBao /
|
|
19
|
+
/** OpenBao / managed base URL. */
|
|
20
20
|
readonly url?: string;
|
|
21
21
|
/** Token / API key for remote vaults. */
|
|
22
22
|
readonly token?: string;
|
|
@@ -485,6 +485,12 @@ export function createAiRuntime(options: CreateAiRuntimeOptions = {}): AiRuntime
|
|
|
485
485
|
if (!index) {
|
|
486
486
|
throw new Error(`ai: index "${decl.into}" not registered`);
|
|
487
487
|
}
|
|
488
|
+
if (index.driverId === "meilisearch") {
|
|
489
|
+
throw new Error(
|
|
490
|
+
`ai: embed into "${decl.into}" needs a vector index (memory/pgvector/libsql) — ` +
|
|
491
|
+
`"${index.driverId}" is full-text; embeddings don't apply`,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
488
494
|
const modelName = decl.model ?? [...models.keys()][0] ?? "mock";
|
|
489
495
|
const client = await clientFor(modelName);
|
|
490
496
|
if (!client.embed) {
|
package/src/elements/ai.test.ts
CHANGED
|
@@ -263,6 +263,28 @@ describe("embeddings into store.index", () => {
|
|
|
263
263
|
const hits = await index.search((await client.embed!({ input: "hello world" })).vectors[0]!, 1);
|
|
264
264
|
expect(hits[0]?.id).toBe("doc-1");
|
|
265
265
|
});
|
|
266
|
+
|
|
267
|
+
test("embed into a meilisearch (full-text) index fails loud", async () => {
|
|
268
|
+
const smart = ai.model("smart", { provider: "mock" });
|
|
269
|
+
const embed = ai.embed("docs", { model: smart, into: "kb" });
|
|
270
|
+
const client = await mockAiDriver.open({ model: "mock" });
|
|
271
|
+
const textIndex = {
|
|
272
|
+
driverId: "meilisearch" as const,
|
|
273
|
+
upsert: async () => {},
|
|
274
|
+
search: async () => ({ hits: [] }),
|
|
275
|
+
delete: async () => true,
|
|
276
|
+
close: async () => {},
|
|
277
|
+
};
|
|
278
|
+
const runtime = createAiRuntime({
|
|
279
|
+
models: [smart],
|
|
280
|
+
embeds: [embed],
|
|
281
|
+
clients: { smart: client },
|
|
282
|
+
indexes: { kb: textIndex as never },
|
|
283
|
+
});
|
|
284
|
+
await expect(runtime.embed("docs", "doc-1", "hello world")).rejects.toThrow(
|
|
285
|
+
/needs a vector index/,
|
|
286
|
+
);
|
|
287
|
+
});
|
|
266
288
|
});
|
|
267
289
|
|
|
268
290
|
describe("model fallback chain", () => {
|
|
@@ -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
|
};
|