okengine 0.19.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/ai/skills.mdx +1 -1
- package/site/content/docs/client/index.mdx +1 -2
- package/site/content/docs/elements/ai/agents.mdx +5 -2
- package/site/content/docs/elements/ai/index.mdx +4 -3
- package/site/content/docs/elements/ai/prompts.mdx +3 -2
- package/site/content/docs/elements/channel/email.mdx +5 -2
- package/site/content/docs/elements/channel/index.mdx +1 -1
- package/site/content/docs/elements/clock/index.mdx +3 -2
- package/site/content/docs/elements/clock/sleep.mdx +10 -7
- package/site/content/docs/elements/flow/index.mdx +3 -3
- package/site/content/docs/elements/flow/routing.mdx +1 -1
- package/site/content/docs/elements/gate/tenancy.mdx +5 -2
- package/site/content/docs/elements/signal/broadcast.mdx +6 -4
- package/site/content/docs/elements/signal/index.mdx +1 -1
- package/site/content/docs/elements/signal/live.mdx +3 -2
- package/site/content/docs/elements/signal/once.mdx +3 -2
- package/site/content/docs/elements/store/files.mdx +24 -16
- package/site/content/docs/elements/store/index.mdx +11 -8
- package/site/content/docs/elements/store/kv.mdx +24 -16
- package/site/content/docs/elements/store/search.mdx +18 -14
- package/site/content/docs/elements/store/sql.mdx +14 -10
- package/site/content/docs/elements/vault/config.mdx +5 -2
- package/site/content/docs/elements/vault/rotation.mdx +5 -2
- package/site/content/docs/elements/vault/secrets.mdx +5 -2
- package/site/content/docs/index.mdx +4 -19
- package/site/content/docs/reference/cli.mdx +1 -1
- package/site/content/docs/reference/fx.mdx +3 -2
- package/site/content/docs/reference/security.mdx +2 -2
- package/site/content/docs/understand/meta.json +1 -1
- package/site/content/docs/understand/the-architecture.mdx +264 -0
- package/src/bench/REPORT.md +48 -17
- package/src/bench/g17-hybrid-search.bench.ts +13 -13
- package/src/console/ui-next/dist/assets/{access-page-DFeymU07.js → access-page-C_qLDhTq.js} +1 -1
- package/src/console/ui-next/dist/assets/{agent-disclosure-U1rdfblp.js → agent-disclosure-BHVqr3TN.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-BeFJeqBG.js → cache-glyph-CKe92lRQ.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-CVAONPii.js → call-pii-button-DEwTl8ZX.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-D2A6NJ-3.js → collapsible-BCBtDrCt.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-Cgk_h5ja.js → duration-tone-JroqeuCp.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-CVHa0RTt.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-MYZQtRnw.js → highlighted-json-DjJW6hqe.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-Jrh39p7A.js → http-method-DC5HBdLU.js} +1 -1
- package/src/console/ui-next/dist/assets/{index-DXP2dBIF.js → index-CYjiZ3WO.js} +3 -3
- package/src/console/ui-next/dist/assets/{observability-page-HvolXxTI.js → observability-page-CAYMyKb3.js} +1 -1
- package/src/console/ui-next/dist/assets/{replica-lag-CSh2dzrb.js → replica-lag-yAQYLv75.js} +1 -1
- package/src/console/ui-next/dist/assets/request-meta-D0yusGxJ.js +1 -0
- package/src/console/ui-next/dist/assets/{store-page-eiKiHnNe.js → store-page-BTKJeJ02.js} +1 -1
- package/src/console/ui-next/dist/assets/{trace-detail-sheet-CZkMeKS-.js → trace-detail-sheet-Bp-Yygs5.js} +1 -1
- package/src/console/ui-next/dist/assets/{tree-expand-toggle-CW8y5A2h.js → tree-expand-toggle-DoaVDfAM.js} +1 -1
- package/src/console/ui-next/dist/assets/{units-page-B_RWJrEO.js → units-page-BRz7xyYL.js} +1 -1
- package/src/console/ui-next/dist/assets/{vault-page-CWrg-A68.js → vault-page-3jQt-bOJ.js} +1 -1
- package/src/console/ui-next/dist/index.html +1 -1
- package/src/console/ui-next/src/features/flows/graph/element-map.test.ts +1 -1
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +3 -3
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +4 -5
- package/src/console/ui-next/src/features/flows/traces/trace-gates.ts +3 -6
- package/src/elements/gate/declare.ts +1 -1
- package/src/elements/gate.ts +1 -1
- package/src/elements/store/search-lsh.ts +35 -0
- package/src/elements/store/search-runtime.pglite.test.ts +186 -0
- package/src/elements/store/search-runtime.ts +30 -16
- package/src/elements/store/search.test.ts +83 -1
- package/src/elements/store.ts +2 -0
- package/src/mcp/docs-index.ts +3 -3
- package/src/mcp/docs-mcp.test.ts +2 -2
- package/src/mcp/docs-tools.ts +2 -1
- package/site/content/docs/understand/the-anatomy.mdx +0 -132
- package/site/content/docs/understand/the-model.mdx +0 -32
- package/site/content/docs/understand/the-problem.mdx +0 -74
- package/site/content/docs/understand/the-vocabulary.mdx +0 -26
- package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +0 -1
- package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +0 -1
- /package/site/content/docs/{ai → understand}/try-it.mdx +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okengine",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "One law. Eight elements. One contract. The backend model stays small; operational surfaces are derived from it instead of maintained separately.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -46,7 +46,7 @@ Contracts teach the _vocabulary_; [MCP](/docs/ai/mcp) grants the _hands_. An age
|
|
|
46
46
|
## Learn more
|
|
47
47
|
|
|
48
48
|
- [MCP](/docs/ai/mcp) — the runtime surface these contracts pair with
|
|
49
|
-
- [The
|
|
49
|
+
- [The Architecture](/docs/understand/the-architecture) — the eight elements in human terms
|
|
50
50
|
- [llms.txt](/docs/ai/llms-txt) — `/llms.txt`, `/llms.json`, `/llms/agents`, per-page markdown
|
|
51
51
|
|
|
52
52
|
## Next
|
|
@@ -184,8 +184,7 @@ dev so `createClient("")` stays same-origin behind the Vite proxy.
|
|
|
184
184
|
|
|
185
185
|
- [Routing](/docs/elements/flow/routing) — folders are the URL; `$routes` without `.adopt()`
|
|
186
186
|
- [HTTP](/docs/elements/flow/http) — verbs, `http.resource`, live SSE
|
|
187
|
-
- [The
|
|
188
|
-
- [The Model](/docs/understand/the-model) — same contract for Client, Console, MCP
|
|
187
|
+
- [The Architecture](/docs/understand/the-architecture) — generated barrel → client → test loop; same contract for Client, Console, MCP
|
|
189
188
|
- [Errors](/docs/reference/errors) — framework codes vs failure values
|
|
190
189
|
|
|
191
190
|
## Next
|
|
@@ -54,9 +54,12 @@ import { supportAgent } from "@/core/ai";
|
|
|
54
54
|
import { member } from "@/core/gate";
|
|
55
55
|
|
|
56
56
|
export const assist = on(
|
|
57
|
-
http
|
|
57
|
+
http
|
|
58
|
+
.post({
|
|
59
|
+
in: z.object({ query: z.string().min(1) }),
|
|
60
|
+
})
|
|
61
|
+
.gate(member),
|
|
58
62
|
flow({
|
|
59
|
-
in: z.object({ query: z.string().min(1) }),
|
|
60
63
|
do: async ({ query }, fx) => {
|
|
61
64
|
return await fx.run(supportAgent, {
|
|
62
65
|
message: `Help the member: ${query}`,
|
|
@@ -60,9 +60,10 @@ import { z } from "zod";
|
|
|
60
60
|
import { triage } from "@/core/ai";
|
|
61
61
|
|
|
62
62
|
export const classify = on(
|
|
63
|
-
http.post(
|
|
64
|
-
flow({
|
|
63
|
+
http.post({
|
|
65
64
|
in: z.object({ message: z.string().min(1) }),
|
|
65
|
+
}),
|
|
66
|
+
flow({
|
|
66
67
|
do: async ({ message }, fx) => {
|
|
67
68
|
return await fx.ask(triage, { message });
|
|
68
69
|
},
|
|
@@ -336,6 +337,6 @@ Compose does not pin inference — BYO URL + keys, or [OpenRouter](/docs/recipes
|
|
|
336
337
|
<Card
|
|
337
338
|
title="The Model"
|
|
338
339
|
description="Eight elements overview."
|
|
339
|
-
href="/docs/understand/the-
|
|
340
|
+
href="/docs/understand/the-architecture"
|
|
340
341
|
/>
|
|
341
342
|
</Cards>
|
|
@@ -56,9 +56,10 @@ import { z } from "zod";
|
|
|
56
56
|
import { classifyTicket } from "@/core/ai";
|
|
57
57
|
|
|
58
58
|
export const classify = on(
|
|
59
|
-
http.post(
|
|
60
|
-
flow({
|
|
59
|
+
http.post({
|
|
61
60
|
in: z.object({ message: z.string().min(1) }),
|
|
61
|
+
}),
|
|
62
|
+
flow({
|
|
62
63
|
do: async ({ message }, fx) => {
|
|
63
64
|
return await fx.ask(classifyTicket, { message });
|
|
64
65
|
},
|
|
@@ -66,9 +66,12 @@ import { z } from "zod";
|
|
|
66
66
|
import { passwordReset } from "@/core/channel";
|
|
67
67
|
|
|
68
68
|
export const reset = on(
|
|
69
|
-
http
|
|
69
|
+
http
|
|
70
|
+
.post({
|
|
71
|
+
in: z.object({ email: z.string().email() }),
|
|
72
|
+
})
|
|
73
|
+
.public(),
|
|
70
74
|
flow({
|
|
71
|
-
in: z.object({ email: z.string().email() }),
|
|
72
75
|
do: async ({ email }, fx) => {
|
|
73
76
|
await fx.send(passwordReset, {
|
|
74
77
|
to: email,
|
|
@@ -339,6 +339,6 @@ Env knobs: [Environment variables](/docs/reference/environment-variables). Local
|
|
|
339
339
|
<Card
|
|
340
340
|
title="The Model"
|
|
341
341
|
description="Eight elements overview."
|
|
342
|
-
href="/docs/understand/the-
|
|
342
|
+
href="/docs/understand/the-architecture"
|
|
343
343
|
/>
|
|
344
344
|
</Cards>
|
|
@@ -140,10 +140,11 @@ import { on, flow, http } from "okengine";
|
|
|
140
140
|
import { z } from "zod";
|
|
141
141
|
|
|
142
142
|
export const start = on(
|
|
143
|
-
http.post(
|
|
143
|
+
http.post({
|
|
144
|
+
in: z.object({ email: z.string().email() }),
|
|
145
|
+
}),
|
|
144
146
|
flow({
|
|
145
147
|
durable: true,
|
|
146
|
-
in: z.object({ email: z.string().email() }),
|
|
147
148
|
do: async ({ email }, fx) => {
|
|
148
149
|
await fx.step("mark-trial", async () => {
|
|
149
150
|
await fx.call(startTrial, { email });
|
|
@@ -30,10 +30,11 @@ import { on, flow, http } from "okengine";
|
|
|
30
30
|
import { z } from "zod";
|
|
31
31
|
|
|
32
32
|
export const start = on(
|
|
33
|
-
http.post(
|
|
33
|
+
http.post({
|
|
34
|
+
in: z.object({ email: z.string().email() }),
|
|
35
|
+
}),
|
|
34
36
|
flow({
|
|
35
37
|
durable: true,
|
|
36
|
-
in: z.object({ email: z.string().email() }),
|
|
37
38
|
do: async ({ email }, fx) => {
|
|
38
39
|
await fx.step("mark-trial", async () => {
|
|
39
40
|
await fx.call(startTrial, { email });
|
|
@@ -101,10 +102,11 @@ import { on, flow, http } from "okengine";
|
|
|
101
102
|
import { z } from "zod";
|
|
102
103
|
|
|
103
104
|
export const create = on(
|
|
104
|
-
http.post(
|
|
105
|
+
http.post({
|
|
106
|
+
in: z.object({ email: z.string().email() }),
|
|
107
|
+
}),
|
|
105
108
|
flow({
|
|
106
109
|
durable: true,
|
|
107
|
-
in: z.object({ email: z.string().email() }),
|
|
108
110
|
do: async ({ email }, fx) => {
|
|
109
111
|
await fx.step("create", async () => {
|
|
110
112
|
await fx.call(createWorkspace, { email });
|
|
@@ -256,11 +258,12 @@ import { on, flow, http } from "okengine";
|
|
|
256
258
|
import { z } from "zod";
|
|
257
259
|
|
|
258
260
|
export const create = on(
|
|
259
|
-
http.post(
|
|
260
|
-
flow({
|
|
261
|
-
durable: true,
|
|
261
|
+
http.post({
|
|
262
262
|
in: z.object({ email: z.string().email() }),
|
|
263
263
|
out: z.object({ trialId: z.string() }),
|
|
264
|
+
}),
|
|
265
|
+
flow({
|
|
266
|
+
durable: true,
|
|
264
267
|
do: async ({ email }, fx) => {
|
|
265
268
|
const trialId = await fx.step("create", async () => {
|
|
266
269
|
return await fx.call(createTrial, { email });
|
|
@@ -582,7 +582,7 @@ Default is `true` once `gate.auth.tenant` is on.
|
|
|
582
582
|
|
|
583
583
|
## Learn more
|
|
584
584
|
|
|
585
|
-
- [The
|
|
585
|
+
- [The Architecture](/docs/understand/the-architecture) — `on`, trigger, `flow`, `do`, `fx`
|
|
586
586
|
- [HTTP](/docs/elements/flow/http) — verbs, envelopes, resources, live SSE
|
|
587
587
|
- [Routing](/docs/elements/flow/routing) — file-tree stamps, barrels, OKE1030 · OKE1040–1045
|
|
588
588
|
- [Consumers](/docs/elements/flow/consumers) — Signal / Clock / CDC
|
|
@@ -611,8 +611,8 @@ Default is `true` once `gate.auth.tenant` is on.
|
|
|
611
611
|
href="/docs/elements/flow/workflows"
|
|
612
612
|
/>
|
|
613
613
|
<Card
|
|
614
|
-
title="The
|
|
614
|
+
title="The Architecture"
|
|
615
615
|
description="Five pieces behind on(trigger, flow)."
|
|
616
|
-
href="/docs/understand/the-
|
|
616
|
+
href="/docs/understand/the-architecture"
|
|
617
617
|
/>
|
|
618
618
|
</Cards>
|
|
@@ -535,7 +535,7 @@ plus a handwritten `http.get("/notes")`.
|
|
|
535
535
|
- [HTTP](/docs/elements/flow/http) — verbs, envelopes, `http.resource`, live SSE
|
|
536
536
|
- [Client](/docs/client/calling) — `api.notes.get`, REST vs RPC, `$routes`
|
|
537
537
|
- [Errors](/docs/reference/errors) — OKE1040 · OKE1030 · OKE1041 · OKE1045
|
|
538
|
-
- [The
|
|
538
|
+
- [The Architecture](/docs/understand/the-architecture) — derived routes, no hand-written table
|
|
539
539
|
- [Gate](/docs/elements/gate) — `.gate(...)` / `.public()` on the same trigger
|
|
540
540
|
|
|
541
541
|
## Next
|
|
@@ -267,9 +267,12 @@ import { z } from "zod";
|
|
|
267
267
|
import { member } from "@/core/gate";
|
|
268
268
|
|
|
269
269
|
export const switchTenant = on(
|
|
270
|
-
http
|
|
270
|
+
http
|
|
271
|
+
.post({
|
|
272
|
+
in: z.object({ tenantId: z.string() }),
|
|
273
|
+
})
|
|
274
|
+
.gate(member),
|
|
271
275
|
flow({
|
|
272
|
-
in: z.object({ tenantId: z.string() }),
|
|
273
276
|
do: async ({ tenantId }, fx) => fx.auth.switchTenant(tenantId),
|
|
274
277
|
}),
|
|
275
278
|
);
|
|
@@ -65,9 +65,10 @@ import { z } from "zod";
|
|
|
65
65
|
import { cacheInvalidated } from "@/signals/cache";
|
|
66
66
|
|
|
67
67
|
export const update = on(
|
|
68
|
-
http.patch(
|
|
69
|
-
flow({
|
|
68
|
+
http.patch({
|
|
70
69
|
in: z.object({ sku: z.string(), title: z.string().min(1) }),
|
|
70
|
+
}),
|
|
71
|
+
flow({
|
|
71
72
|
do: async ({ sku, title }, fx) => {
|
|
72
73
|
// … persist the change …
|
|
73
74
|
await fx.emit(cacheInvalidated, { key: `sku:${sku}` });
|
|
@@ -284,9 +285,10 @@ import { z } from "zod";
|
|
|
284
285
|
import { catalogChanged } from "@/signals/catalog";
|
|
285
286
|
|
|
286
287
|
export const create = on(
|
|
287
|
-
http.post(
|
|
288
|
-
flow({
|
|
288
|
+
http.post({
|
|
289
289
|
in: z.object({ sku: z.string(), title: z.string().min(1) }),
|
|
290
|
+
}),
|
|
291
|
+
flow({
|
|
290
292
|
do: async ({ sku, title }, fx) => {
|
|
291
293
|
// … write …
|
|
292
294
|
await fx.emit(catalogChanged, { sku });
|
|
@@ -342,9 +342,10 @@ import { z } from "zod";
|
|
|
342
342
|
import { orderStatus } from "@/signals/orders";
|
|
343
343
|
|
|
344
344
|
export const ship = on(
|
|
345
|
-
http.post(
|
|
346
|
-
flow({
|
|
345
|
+
http.post({
|
|
347
346
|
in: z.object({ id: z.string() }),
|
|
347
|
+
}),
|
|
348
|
+
flow({
|
|
348
349
|
do: async ({ id }, fx) => {
|
|
349
350
|
await fx.emit(orderStatus, { orderId: id, status: "shipped" });
|
|
350
351
|
return { id, status: "shipped" };
|
|
@@ -311,9 +311,10 @@ import { z } from "zod";
|
|
|
311
311
|
import { orderPlaced } from "@/signals/orders";
|
|
312
312
|
|
|
313
313
|
export const create = on(
|
|
314
|
-
http.post(
|
|
315
|
-
flow({
|
|
314
|
+
http.post({
|
|
316
315
|
in: z.object({ userId: z.string(), amount: z.number() }),
|
|
316
|
+
}),
|
|
317
|
+
flow({
|
|
317
318
|
do: async ({ userId, amount }, fx) => {
|
|
318
319
|
const orderId = fx.id();
|
|
319
320
|
await fx.emit(orderPlaced, { orderId, amount, userId }, { key: userId });
|
|
@@ -38,13 +38,14 @@ import { z } from "zod";
|
|
|
38
38
|
import { uploads } from "@/core";
|
|
39
39
|
|
|
40
40
|
export const create = on(
|
|
41
|
-
http.post(
|
|
42
|
-
flow({
|
|
41
|
+
http.post({
|
|
43
42
|
in: z.object({
|
|
44
43
|
name: z.string().min(1),
|
|
45
44
|
bytes: z.string(), // base64 for the demo
|
|
46
45
|
}),
|
|
47
46
|
out: z.object({ key: z.string() }),
|
|
47
|
+
}),
|
|
48
|
+
flow({
|
|
48
49
|
do: async ({ name, bytes }, fx) => {
|
|
49
50
|
const key = `uploads/${fx.id()}-${name}`;
|
|
50
51
|
const data = Uint8Array.from(atob(bytes), (c) => c.charCodeAt(0));
|
|
@@ -123,9 +124,10 @@ import { z } from "zod";
|
|
|
123
124
|
import { uploads } from "@/core";
|
|
124
125
|
|
|
125
126
|
export const cleanup = on(
|
|
126
|
-
http.delete(
|
|
127
|
-
flow({
|
|
127
|
+
http.delete({
|
|
128
128
|
in: z.object({ prefix: z.string().default("avatars/") }),
|
|
129
|
+
}),
|
|
130
|
+
flow({
|
|
129
131
|
do: async ({ prefix }, fx) => {
|
|
130
132
|
const keys = await fx.store(uploads).list(prefix);
|
|
131
133
|
let removed = 0;
|
|
@@ -154,9 +156,10 @@ import { z } from "zod";
|
|
|
154
156
|
import { uploads } from "@/core";
|
|
155
157
|
|
|
156
158
|
export const create = on(
|
|
157
|
-
http.post(
|
|
158
|
-
flow({
|
|
159
|
+
http.post({
|
|
159
160
|
in: z.object({ bytes: z.string() }),
|
|
161
|
+
}),
|
|
162
|
+
flow({
|
|
160
163
|
do: async ({ bytes }, fx) => {
|
|
161
164
|
const key = `photos/${fx.id()}.jpg`;
|
|
162
165
|
const data = Uint8Array.from(atob(bytes), (c) => c.charCodeAt(0));
|
|
@@ -195,9 +198,10 @@ import { z } from "zod";
|
|
|
195
198
|
import { uploads } from "@/core";
|
|
196
199
|
|
|
197
200
|
export const card = on(
|
|
198
|
-
http.post(
|
|
199
|
-
flow({
|
|
201
|
+
http.post({
|
|
200
202
|
in: z.object({ sourceKey: z.string() }),
|
|
203
|
+
}),
|
|
204
|
+
flow({
|
|
201
205
|
do: async ({ sourceKey }, fx) => {
|
|
202
206
|
const outKey = sourceKey.replace(/\.[^.]+$/, ".card.webp");
|
|
203
207
|
await fx
|
|
@@ -282,13 +286,14 @@ import { z } from "zod";
|
|
|
282
286
|
import { uploads } from "@/core";
|
|
283
287
|
|
|
284
288
|
export const create = on(
|
|
285
|
-
http.post(
|
|
286
|
-
flow({
|
|
289
|
+
http.post({
|
|
287
290
|
in: z.object({
|
|
288
291
|
id: z.string(),
|
|
289
292
|
name: z.string().min(1),
|
|
290
293
|
bytes: z.string(),
|
|
291
294
|
}),
|
|
295
|
+
}),
|
|
296
|
+
flow({
|
|
292
297
|
do: async ({ id, name, bytes }, fx) => {
|
|
293
298
|
const key = `notes/${id}/${name}`;
|
|
294
299
|
const data = Uint8Array.from(atob(bytes), (c) => c.charCodeAt(0));
|
|
@@ -311,10 +316,11 @@ import { z } from "zod";
|
|
|
311
316
|
import { uploads } from "@/core";
|
|
312
317
|
|
|
313
318
|
export const get = on(
|
|
314
|
-
http.get(
|
|
315
|
-
flow({
|
|
319
|
+
http.get({
|
|
316
320
|
in: z.object({ id: z.string(), name: z.string() }),
|
|
317
321
|
errors: { NotFound: z.object({ key: z.string() }) },
|
|
322
|
+
}),
|
|
323
|
+
flow({
|
|
318
324
|
do: async ({ id, name }, fx) => {
|
|
319
325
|
const key = `notes/${id}/${name}`;
|
|
320
326
|
const bytes = await fx.store(uploads).get(key);
|
|
@@ -337,9 +343,10 @@ import { z } from "zod";
|
|
|
337
343
|
import { uploads } from "@/core";
|
|
338
344
|
|
|
339
345
|
export const remove = on(
|
|
340
|
-
http.delete(
|
|
341
|
-
flow({
|
|
346
|
+
http.delete({
|
|
342
347
|
in: z.object({ id: z.string(), name: z.string() }),
|
|
348
|
+
}),
|
|
349
|
+
flow({
|
|
343
350
|
do: async ({ id, name }, fx) => {
|
|
344
351
|
const key = `notes/${id}/${name}`;
|
|
345
352
|
const removed = await fx.store(uploads).delete(key);
|
|
@@ -362,9 +369,10 @@ import { z } from "zod";
|
|
|
362
369
|
import { uploads } from "@/core";
|
|
363
370
|
|
|
364
371
|
export const attachments = on(
|
|
365
|
-
http.get(
|
|
366
|
-
flow({
|
|
372
|
+
http.get({
|
|
367
373
|
in: z.object({ id: z.string() }),
|
|
374
|
+
}),
|
|
375
|
+
flow({
|
|
368
376
|
do: async ({ id }, fx) => {
|
|
369
377
|
const keys = await fx.store(uploads).list(`notes/${id}/`);
|
|
370
378
|
return { keys };
|
|
@@ -47,9 +47,10 @@ import { z } from "zod";
|
|
|
47
47
|
import { db, notes } from "@/schema";
|
|
48
48
|
|
|
49
49
|
export const create = on(
|
|
50
|
-
http.post(
|
|
51
|
-
flow({
|
|
50
|
+
http.post({
|
|
52
51
|
in: z.object({ title: z.string().min(1) }),
|
|
52
|
+
}),
|
|
53
|
+
flow({
|
|
53
54
|
do: async ({ title }, fx) => {
|
|
54
55
|
const id = fx.id();
|
|
55
56
|
await fx.store(db).insert(notes).values({ id, title });
|
|
@@ -106,10 +107,11 @@ import { eq } from "drizzle-orm";
|
|
|
106
107
|
import { db, notes } from "@/schema";
|
|
107
108
|
|
|
108
109
|
export const get = on(
|
|
109
|
-
http.get(
|
|
110
|
-
flow({
|
|
110
|
+
http.get({
|
|
111
111
|
in: z.object({ id: z.string() }),
|
|
112
112
|
errors: { NotFound: z.object({ id: z.string() }) },
|
|
113
|
+
}),
|
|
114
|
+
flow({
|
|
113
115
|
do: async ({ id }, fx) => {
|
|
114
116
|
const [note] = await fx.store(db).select().from(notes).where(eq(notes.id, id));
|
|
115
117
|
if (!note) return fx.fail("NotFound", { id });
|
|
@@ -128,11 +130,11 @@ Prefer [`store.resource`](/docs/elements/store/sql#resources) when you want five
|
|
|
128
130
|
Pass the **declaration** into `fx.store` — there is no `fx.store.kv` namespace. TTL is a duration string:
|
|
129
131
|
|
|
130
132
|
```typescript title="src/flows/sessions/put.ts"
|
|
131
|
-
import {
|
|
133
|
+
import { call } from "okengine";
|
|
132
134
|
import { z } from "zod";
|
|
133
135
|
import { sessions } from "@/core";
|
|
134
136
|
|
|
135
|
-
export const putSession =
|
|
137
|
+
export const putSession = call("sessions.put", {
|
|
136
138
|
in: z.object({ token: z.string(), userId: z.string() }),
|
|
137
139
|
do: async ({ token, userId }, fx) => {
|
|
138
140
|
await fx.store(sessions).set(`session:${token}`, userId, "1h");
|
|
@@ -159,9 +161,10 @@ import { z } from "zod";
|
|
|
159
161
|
import { uploads } from "@/core";
|
|
160
162
|
|
|
161
163
|
export const create = on(
|
|
162
|
-
http.post(
|
|
163
|
-
flow({
|
|
164
|
+
http.post({
|
|
164
165
|
in: z.object({ bytes: z.string() }),
|
|
166
|
+
}),
|
|
167
|
+
flow({
|
|
165
168
|
do: async ({ bytes }, fx) => {
|
|
166
169
|
const key = `avatars/${fx.id()}.png`;
|
|
167
170
|
const data = Uint8Array.from(atob(bytes), (c) => c.charCodeAt(0));
|
|
@@ -39,9 +39,10 @@ import { z } from "zod";
|
|
|
39
39
|
import { sessions } from "@/core";
|
|
40
40
|
|
|
41
41
|
export const create = on(
|
|
42
|
-
http.post(
|
|
43
|
-
flow({
|
|
42
|
+
http.post({
|
|
44
43
|
in: z.object({ token: z.string(), userId: z.string() }),
|
|
44
|
+
}),
|
|
45
|
+
flow({
|
|
45
46
|
do: async ({ token, userId }, fx) => {
|
|
46
47
|
await fx.store(sessions).set(`session:${token}`, userId, "1h");
|
|
47
48
|
const cached = await fx.store(sessions).get(`session:${token}`);
|
|
@@ -97,9 +98,10 @@ import { z } from "zod";
|
|
|
97
98
|
import { sessions } from "@/core";
|
|
98
99
|
|
|
99
100
|
export const get = on(
|
|
100
|
-
http.get(
|
|
101
|
-
flow({
|
|
101
|
+
http.get({
|
|
102
102
|
in: z.object({ userId: z.string() }),
|
|
103
|
+
}),
|
|
104
|
+
flow({
|
|
103
105
|
do: async ({ userId }, fx) => {
|
|
104
106
|
const prefs = await fx.store(sessions).get(`user:${userId}:prefs`);
|
|
105
107
|
return { prefs: prefs ?? null };
|
|
@@ -122,9 +124,10 @@ import { z } from "zod";
|
|
|
122
124
|
import { sessions } from "@/core";
|
|
123
125
|
|
|
124
126
|
export const create = on(
|
|
125
|
-
http.post(
|
|
126
|
-
flow({
|
|
127
|
+
http.post({
|
|
127
128
|
in: z.object({ userId: z.string() }),
|
|
129
|
+
}),
|
|
130
|
+
flow({
|
|
128
131
|
do: async ({ userId }, fx) => {
|
|
129
132
|
const code = "482193";
|
|
130
133
|
await fx.store(sessions).set(`otp:${userId}`, code, "5m");
|
|
@@ -251,10 +254,11 @@ import { z } from "zod";
|
|
|
251
254
|
import { sessions } from "@/core";
|
|
252
255
|
|
|
253
256
|
export const get = on(
|
|
254
|
-
http.get(
|
|
255
|
-
flow({
|
|
257
|
+
http.get({
|
|
256
258
|
in: z.object({ token: z.string() }),
|
|
257
259
|
errors: { NotFound: z.object({ token: z.string() }) },
|
|
260
|
+
}),
|
|
261
|
+
flow({
|
|
258
262
|
do: async ({ token }, fx) => {
|
|
259
263
|
const userId = await fx.store(sessions).get(`session:${token}`);
|
|
260
264
|
if (userId === undefined || userId === null) {
|
|
@@ -278,9 +282,10 @@ import { z } from "zod";
|
|
|
278
282
|
import { sessions } from "@/core";
|
|
279
283
|
|
|
280
284
|
export const create = on(
|
|
281
|
-
http.post(
|
|
282
|
-
flow({
|
|
285
|
+
http.post({
|
|
283
286
|
in: z.object({ token: z.string(), userId: z.string() }),
|
|
287
|
+
}),
|
|
288
|
+
flow({
|
|
284
289
|
do: async ({ token, userId }, fx) => {
|
|
285
290
|
await fx.store(sessions).set(`session:${token}`, userId, "1h");
|
|
286
291
|
return { ok: true as const };
|
|
@@ -304,9 +309,10 @@ import { z } from "zod";
|
|
|
304
309
|
import { sessions } from "@/core";
|
|
305
310
|
|
|
306
311
|
export const remove = on(
|
|
307
|
-
http.delete(
|
|
308
|
-
flow({
|
|
312
|
+
http.delete({
|
|
309
313
|
in: z.object({ token: z.string() }),
|
|
314
|
+
}),
|
|
315
|
+
flow({
|
|
310
316
|
do: async ({ token }, fx) => {
|
|
311
317
|
const removed = await fx.store(sessions).delete(`session:${token}`);
|
|
312
318
|
return { removed };
|
|
@@ -358,9 +364,10 @@ import { z } from "zod";
|
|
|
358
364
|
import { sessions } from "@/core";
|
|
359
365
|
|
|
360
366
|
export const ttl = on(
|
|
361
|
-
http.get(
|
|
362
|
-
flow({
|
|
367
|
+
http.get({
|
|
363
368
|
in: z.object({ userId: z.string() }),
|
|
369
|
+
}),
|
|
370
|
+
flow({
|
|
364
371
|
do: async ({ userId }, fx) => {
|
|
365
372
|
const ttlMs = await fx.store(sessions).ttlMs(`otp:${userId}`);
|
|
366
373
|
return { ttlMs };
|
|
@@ -409,13 +416,14 @@ import { z } from "zod";
|
|
|
409
416
|
import { drafts } from "@/core";
|
|
410
417
|
|
|
411
418
|
export const save = on(
|
|
412
|
-
http.put("/drafts/:id"
|
|
413
|
-
flow({
|
|
419
|
+
http.put("/drafts/:id", {
|
|
414
420
|
in: z.object({
|
|
415
421
|
id: z.string().min(1),
|
|
416
422
|
title: z.string().min(1),
|
|
417
423
|
body: z.string().optional(),
|
|
418
424
|
}),
|
|
425
|
+
}),
|
|
426
|
+
flow({
|
|
419
427
|
do: async ({ id, title, body }, fx) => {
|
|
420
428
|
await fx.store(drafts).set(id, { title, body: body ?? "" }, "7d");
|
|
421
429
|
return { id };
|
|
@@ -42,9 +42,10 @@ import { z } from "zod";
|
|
|
42
42
|
import { articles, db } from "@/schema";
|
|
43
43
|
|
|
44
44
|
export const search = on(
|
|
45
|
-
http.get(
|
|
46
|
-
flow({
|
|
45
|
+
http.get({
|
|
47
46
|
in: z.object({ q: z.string() }),
|
|
47
|
+
}),
|
|
48
|
+
flow({
|
|
48
49
|
do: async ({ q }, fx) => {
|
|
49
50
|
const result = await fx.store(db).search(articles, {
|
|
50
51
|
query: q,
|
|
@@ -399,9 +400,10 @@ import { z } from "zod";
|
|
|
399
400
|
import { articles, db } from "@/schema";
|
|
400
401
|
|
|
401
402
|
export const search = on(
|
|
402
|
-
http.get(
|
|
403
|
-
flow({
|
|
403
|
+
http.get({
|
|
404
404
|
in: z.object({ q: z.string().min(1) }),
|
|
405
|
+
}),
|
|
406
|
+
flow({
|
|
405
407
|
do: async ({ q }, fx) => {
|
|
406
408
|
const result = await fx.store(db).search(articles, {
|
|
407
409
|
query: q,
|
|
@@ -430,12 +432,13 @@ import { z } from "zod";
|
|
|
430
432
|
import { articles, db } from "@/schema";
|
|
431
433
|
|
|
432
434
|
export const search = on(
|
|
433
|
-
http.get(
|
|
434
|
-
flow({
|
|
435
|
+
http.get({
|
|
435
436
|
in: z.object({
|
|
436
437
|
q: z.string().min(1),
|
|
437
438
|
status: z.string().optional(),
|
|
438
439
|
}),
|
|
440
|
+
}),
|
|
441
|
+
flow({
|
|
439
442
|
do: async ({ q, status }, fx) => {
|
|
440
443
|
const result = await fx.store(db).search(articles, {
|
|
441
444
|
query: q,
|
|
@@ -539,7 +542,7 @@ intentional. Do not poll `fx.embed` from the writer to “close” it.
|
|
|
539
542
|
| ------------------------------------- | ---------------------------------------------------------- |
|
|
540
543
|
| Generated `tsvector` + GIN | BM25 candidate retrieval (`plainto_tsquery('english', …)`) |
|
|
541
544
|
| `real[]` embedding column | Stored vector per `.embed()` field |
|
|
542
|
-
| `bigint` LSH column + B-tree |
|
|
545
|
+
| `bigint` LSH column + B-tree | Stored SimHash pack (query ranks by Hamming, not equality) |
|
|
543
546
|
| Corpus stats / DF / hyperplane tables | IDF, average length, stable LSH planes |
|
|
544
547
|
|
|
545
548
|
Hyperplanes insert once (`ON CONFLICT DO NOTHING`) and are **never** regenerated.
|
|
@@ -619,8 +622,9 @@ await fx.store(db).search(articles, {
|
|
|
619
622
|
BM25F uses Robertson–Zaragoza saturation: **k1 = 1.2**, **b = 0.75**. Field
|
|
620
623
|
`weight` multiplies term frequency *before* that saturation.
|
|
621
624
|
|
|
622
|
-
LSH uses **64** hyperplanes
|
|
623
|
-
|
|
625
|
+
LSH uses **64** hyperplanes packed into a `bigint`. Query-time retrieval ranks
|
|
626
|
+
rows by Hamming distance (`bit_count` of XOR) and keeps the oversampled nearest
|
|
627
|
+
(`max(limit × 5, 50)`, cap 500), then cosine-reranks in process.
|
|
624
628
|
|
|
625
629
|
</Accordion>
|
|
626
630
|
|
|
@@ -800,13 +804,13 @@ Headline numbers from the live-Postgres G17 gate (`OKE_TEST_POSTGRES=1`, Bun 1.4
|
|
|
800
804
|
|
|
801
805
|
| Corpus size | BM25 (text) p50 | LSH/hybrid p50 | LSH precision@10 vs exact cosine | Guidance |
|
|
802
806
|
| ----------- | --------------- | -------------- | -------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
803
|
-
| ≤10k | ~1–
|
|
804
|
-
| ~100k | ~
|
|
805
|
-
| ~1M | ~0.
|
|
807
|
+
| ≤10k | ~1–5 ms | ~1–9 ms | 0.10–0.17 vector | Built-in hybrid is fine for ranking UX; LSH is not HNSW |
|
|
808
|
+
| ~100k | ~21 ms | ~31–34 ms | 0.017 vector | Expect tens of ms; re-`EXPLAIN` after `ANALYZE` |
|
|
809
|
+
| ~1M | ~0.3 s | ~0.4 s | 0.017 vector / 0 hybrid | Prefer external `store.index` (pgvector / Meilisearch) for semantic recall at this scale |
|
|
806
810
|
|
|
807
|
-
**Honest LSH note:**
|
|
811
|
+
**Honest LSH note:** after the Hamming-rank fix (2026-09-11), vector precision@10 vs exact cosine is 0.17 at 1k, 0.10 at 10k, ~0.02 at 100k–1M on the G17 hash-bag corpus — a smooth drop, not the v0.19.0 zero collapse. Still **not** HNSW. Prefer BM25-only or `store.index` when semantic recall matters.
|
|
808
812
|
|
|
809
|
-
**Query plan:** at N=100k, `EXPLAIN (ANALYZE, BUFFERS)`
|
|
813
|
+
**Query plan:** at N=100k, `EXPLAIN (ANALYZE, BUFFERS)` is a UNION of GIN **Bitmap Index Scan** and a **Parallel Seq Scan** + top-N heapsort on Hamming distance (~15 ms). Hamming-rank cannot use the LSH B-tree. Capture your own plan on production data.
|
|
810
814
|
|
|
811
815
|
**Backfill:** `oke db search-backfill` is interrupt-safe to re-run (G17 killed at 2k/50k embeds, resumed to completion in ~29 s on that table).
|
|
812
816
|
|