okengine 0.13.0 → 0.15.2
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/manifest.v1.schema.json +7 -1
- package/package.json +4 -5
- package/site/content/docs/ai/mcp.mdx +27 -2
- package/site/content/docs/elements/ai.mdx +59 -11
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +30 -12
- package/site/content/docs/elements/flow.mdx +9 -3
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +71 -25
- package/site/content/docs/elements/store.mdx +87 -28
- package/site/content/docs/elements/vault.mdx +1 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/installation.mdx +2 -2
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +25 -21
- package/site/content/docs/reference/fx.mdx +27 -22
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/bindings.ts +14 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/ai-setup/recommend.test.ts +25 -0
- package/src/cli/ai-setup/recommend.ts +8 -3
- package/src/cli/attach-host-console.test.ts +3 -2
- package/src/cli/attach-host-console.ts +3 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/effects-infer.ts +58 -3
- package/src/compiler/extract.test.ts +458 -29
- package/src/compiler/extract.ts +387 -69
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/response.ts +45 -1
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/ai.ts +5 -2
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +121 -40
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/serve.ts +17 -2
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +8 -0
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
- package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
- package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
- package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
- package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
- package/src/console/ui-next/dist/index.html +6 -6
- package/src/console/ui-next/seed-invoke-host.ts +2 -0
- package/src/console/ui-next/src/client.ts +35 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
- package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
- package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
- package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +4 -1
- package/src/docker/dockerfile.ts +1 -1
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/ai-anthropic.ts +5 -0
- package/src/drivers/ai-ollama.ts +49 -30
- package/src/drivers/ai-openai-compatible.ts +57 -46
- package/src/drivers/ai-providers.test.ts +3 -0
- package/src/drivers/bun-native-completeness.test.ts +7 -9
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/redis.ts +11 -4
- package/src/drivers/signal-redis.ts +24 -14
- package/src/drivers/signal-types.ts +2 -1
- package/src/drivers/types.ts +10 -0
- package/src/elements/ai/declare.ts +109 -0
- package/src/elements/ai/errors.test.ts +5 -1
- package/src/elements/ai/errors.ts +30 -2
- package/src/elements/ai/eval.ts +4 -6
- package/src/elements/ai/mcp-client.test.ts +206 -0
- package/src/elements/ai/mcp-client.ts +362 -0
- package/src/elements/ai/mcp-http.ts +159 -0
- package/src/elements/ai/mcp-mock.ts +134 -0
- package/src/elements/ai/mcp-protocol.ts +234 -0
- package/src/elements/ai/mcp-stdio.test.ts +50 -0
- package/src/elements/ai/mcp-stdio.ts +212 -0
- package/src/elements/ai/mcp-transport.ts +70 -0
- package/src/elements/ai/runtime.ts +159 -29
- package/src/elements/ai.test.ts +139 -0
- package/src/elements/ai.ts +16 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/clock/health.test.ts +43 -0
- package/src/elements/clock/runtime.ts +55 -2
- package/src/elements/clock/schedule.ts +71 -142
- package/src/elements/clock.test.ts +9 -40
- package/src/elements/clock.ts +6 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +36 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/index.ts +2 -0
- package/src/elements/signal/declare.ts +2 -1
- package/src/elements/signal/runtime.ts +16 -1
- package/src/elements/signal.ts +1 -0
- package/src/elements/store/cache.test.ts +2 -0
- package/src/elements/store/cache.ts +3 -3
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +4 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +161 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-session.ts +81 -3
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +14 -0
- package/src/elements/vault/builtin-adapter.ts +17 -0
- package/src/full.ts +2 -0
- package/src/http.ts +20 -3
- package/src/index.ts +14 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +227 -77
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +54 -9
- package/src/kernel/boot-bind/ai.ts +24 -0
- package/src/kernel/boot-bind/clock.ts +2 -0
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +5 -3
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +15 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx-dead-letters.test.ts +77 -0
- package/src/kernel/fx.test.ts +22 -0
- package/src/kernel/fx.ts +135 -6
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +174 -0
- package/src/kernel/index.ts +2 -0
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/mcp-ref.ts +88 -0
- package/src/manifest/types.ts +41 -2
- package/src/manifest/validate.test.ts +20 -0
- package/src/mcp/docs-server.ts +1 -1
- package/src/mcp/server.ts +1 -1
- package/src/plugins/compression.test.ts +21 -0
- package/src/plugins/compression.ts +1 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/bun.ts +41 -4
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +10 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
- package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-place HTTP path + Flow name stamps.
|
|
3
|
+
*
|
|
4
|
+
* `on()` already registered the trigger (possibly still on the pending-path
|
|
5
|
+
* sentinel). A wrapper that returned a new trigger would leave `listBindings()`
|
|
6
|
+
* on the sentinel — mutate the existing object.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { isFlow, type AnyFlowDef, type FlowDef } from "./flow.ts";
|
|
10
|
+
import { isPendingHttpPath, type HttpPathPending } from "./http-path-pending.ts";
|
|
11
|
+
import type { HttpTrigger, Trigger } from "./triggers.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replace a pending HTTP path on {@link FlowDef} with the file-tree stamp.
|
|
15
|
+
*
|
|
16
|
+
* Explicit `http.get("/x")` wins — the pending sentinel is the only path
|
|
17
|
+
* this function overwrites. Returns the same object with a tighter trigger
|
|
18
|
+
* type when the original path was pending.
|
|
19
|
+
*
|
|
20
|
+
* @param flow - Bound flow (already passed through `on()`)
|
|
21
|
+
* @param path - Inferred URL (`/notes/:id`)
|
|
22
|
+
*/
|
|
23
|
+
export function stampHttpPath<F extends AnyFlowDef, P extends string>(
|
|
24
|
+
flow: F,
|
|
25
|
+
path: P,
|
|
26
|
+
): StampHttpPath<F, P> {
|
|
27
|
+
if (!isFlow(flow)) return flow as StampHttpPath<F, P>;
|
|
28
|
+
const apply = (trigger: Trigger): void => {
|
|
29
|
+
if (trigger.kind !== "http") return;
|
|
30
|
+
if (!isPendingHttpPath(trigger.path)) return;
|
|
31
|
+
(trigger as { path: string }).path = path;
|
|
32
|
+
};
|
|
33
|
+
if (flow.$trigger) apply(flow.$trigger);
|
|
34
|
+
for (const trigger of flow.triggers) apply(trigger);
|
|
35
|
+
return flow as StampHttpPath<F, P>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Stamp `unit.export` onto a nameless (or `flow_*`) Flow.
|
|
40
|
+
*
|
|
41
|
+
* Explicit `flow("notes.get")` wins. Fills `unit` when missing.
|
|
42
|
+
*
|
|
43
|
+
* @param flow - Flow definition
|
|
44
|
+
* @param name - `notes.get`
|
|
45
|
+
*/
|
|
46
|
+
export function stampFlowName<F extends AnyFlowDef>(flow: F, name: string): F {
|
|
47
|
+
if (!isFlow(flow)) return flow;
|
|
48
|
+
const f = flow as { name: string; unit: string | undefined };
|
|
49
|
+
if (!f.name || f.name.startsWith("flow_")) {
|
|
50
|
+
f.name = name;
|
|
51
|
+
}
|
|
52
|
+
if (!f.unit) {
|
|
53
|
+
const resolved = f.name || name;
|
|
54
|
+
const dot = resolved.indexOf(".");
|
|
55
|
+
f.unit = dot > 0 ? resolved.slice(0, dot) : resolved;
|
|
56
|
+
}
|
|
57
|
+
return flow;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Type-level path replacement — only when the bound trigger is still pending.
|
|
62
|
+
*
|
|
63
|
+
* @typeParam F - Flow definition
|
|
64
|
+
* @typeParam P - Stamped path
|
|
65
|
+
*/
|
|
66
|
+
export type StampHttpPath<F, P extends string> =
|
|
67
|
+
F extends FlowDef<infer I, infer O, infer E, infer D, infer T>
|
|
68
|
+
? T extends HttpTrigger<infer M, infer Cur>
|
|
69
|
+
? [Cur] extends [HttpPathPending]
|
|
70
|
+
? FlowDef<I, O, E, D, HttpTrigger<M, P>>
|
|
71
|
+
: F
|
|
72
|
+
: F
|
|
73
|
+
: F;
|
|
74
|
+
|
|
75
|
+
export { HTTP_PATH_PENDING, isPendingHttpPath } from "./http-path-pending.ts";
|
package/src/kernel/triggers.ts
CHANGED
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import { flattenGateArgs, GATE_PUBLIC_NAME, type GateAllDecl } from "../elements/gate/flatten.ts";
|
|
9
9
|
import type { NamedRef } from "./fx.ts";
|
|
10
|
+
import { HTTP_PATH_PENDING, type HttpPathPending } from "./http-path-pending.ts";
|
|
10
11
|
import { lazyRequire } from "./lazy-require.ts";
|
|
11
12
|
|
|
13
|
+
export { HTTP_PATH_PENDING, isPendingHttpPath, type HttpPathPending } from "./http-path-pending.ts";
|
|
14
|
+
|
|
12
15
|
/** HTTP methods accepted by {@link http}. */
|
|
13
16
|
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD" | "QUERY";
|
|
14
17
|
|
|
@@ -18,15 +21,8 @@ export type GateRef = NamedRef;
|
|
|
18
21
|
/** One `.gate(...)` argument — a named ref, an `all` handle, or an array. */
|
|
19
22
|
export type GateArg = GateRef | GateAllDecl | readonly GateArg[];
|
|
20
23
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
* Call it with members, or use `.public` for the unauthenticated sentinel.
|
|
24
|
-
*/
|
|
25
|
-
export interface GateAttach<T> {
|
|
26
|
-
(...gates: GateArg[]): T;
|
|
27
|
-
/** Attach {@link gate.public} — same as `.gate(gate.public)`. */
|
|
28
|
-
readonly public: T;
|
|
29
|
-
}
|
|
24
|
+
/** Attach gates on an HTTP trigger / resource mount. */
|
|
25
|
+
export type GateAttach<T> = (...gates: GateArg[]) => T;
|
|
30
26
|
|
|
31
27
|
/**
|
|
32
28
|
* HTTP trigger value. Method and path are literal type parameters so
|
|
@@ -44,9 +40,12 @@ export interface HttpTrigger<M extends HttpMethod = HttpMethod, P extends string
|
|
|
44
40
|
readonly isLive: boolean;
|
|
45
41
|
/**
|
|
46
42
|
* Attach gates (registration order). `gate.all` handles and arrays flatten.
|
|
47
|
-
* `.gate.public` is the unauthenticated sentinel.
|
|
48
43
|
*/
|
|
49
44
|
readonly gate: GateAttach<HttpTrigger<M, P>>;
|
|
45
|
+
/**
|
|
46
|
+
* Attach the unauthenticated public sentinel.
|
|
47
|
+
*/
|
|
48
|
+
public(): HttpTrigger<M, P>;
|
|
50
49
|
/**
|
|
51
50
|
* Mark the HTTP trigger as live (push result to subscribers).
|
|
52
51
|
*/
|
|
@@ -93,7 +92,7 @@ export type Trigger = HttpTrigger | EveryTrigger | SignalAsTrigger | CdcTrigger
|
|
|
93
92
|
export type TriggerKind = Trigger["kind"];
|
|
94
93
|
|
|
95
94
|
/**
|
|
96
|
-
* Callable `.gate(...)`
|
|
95
|
+
* Callable `.gate(...)` on a trigger or resource mount.
|
|
97
96
|
*
|
|
98
97
|
* @param apply - Rebuild the host with the flattened gate list
|
|
99
98
|
* @param current - Gates already attached
|
|
@@ -102,19 +101,11 @@ export function createGateAttach<T>(
|
|
|
102
101
|
apply: (next: readonly GateRef[]) => T,
|
|
103
102
|
current: readonly GateRef[],
|
|
104
103
|
): GateAttach<T> {
|
|
105
|
-
|
|
106
|
-
apply([...current, ...flattenGateArgs(next)])) as GateAttach<T>;
|
|
107
|
-
Object.defineProperty(attach, "public", {
|
|
108
|
-
get() {
|
|
109
|
-
return apply([...current, GATE_PUBLIC_NAME]);
|
|
110
|
-
},
|
|
111
|
-
enumerable: true,
|
|
112
|
-
});
|
|
113
|
-
return attach;
|
|
104
|
+
return (...next: GateArg[]) => apply([...current, ...flattenGateArgs(next)]);
|
|
114
105
|
}
|
|
115
106
|
|
|
116
107
|
/**
|
|
117
|
-
* Build an HTTP trigger with `.gate` / `.live`.
|
|
108
|
+
* Build an HTTP trigger with `.gate` / `.public` / `.live`.
|
|
118
109
|
*
|
|
119
110
|
* @param method - HTTP verb
|
|
120
111
|
* @param path - Route path
|
|
@@ -134,6 +125,9 @@ export function createHttpTrigger<M extends HttpMethod, P extends string>(
|
|
|
134
125
|
gates,
|
|
135
126
|
isLive,
|
|
136
127
|
gate: createGateAttach((next) => createHttpTrigger(method, path, next, isLive), gates),
|
|
128
|
+
public() {
|
|
129
|
+
return createHttpTrigger(method, path, [...gates, GATE_PUBLIC_NAME], isLive);
|
|
130
|
+
},
|
|
137
131
|
live() {
|
|
138
132
|
return createHttpTrigger(method, path, gates, true);
|
|
139
133
|
},
|
|
@@ -156,7 +150,7 @@ export interface ResourceFlowBag {
|
|
|
156
150
|
/**
|
|
157
151
|
* A mounted resource — the single argument to the `on(http.resource(…))`
|
|
158
152
|
* overload. Branded so `on` can tell it apart from a plain trigger.
|
|
159
|
-
* Same chain as {@link HttpTrigger}: `.gate(...)` / `.live()`.
|
|
153
|
+
* Same chain as {@link HttpTrigger}: `.gate(...)` / `.public()` / `.live()`.
|
|
160
154
|
*/
|
|
161
155
|
export interface ResourceMount {
|
|
162
156
|
readonly [resourceMountBrand]: true;
|
|
@@ -166,9 +160,12 @@ export interface ResourceMount {
|
|
|
166
160
|
readonly isLive: boolean;
|
|
167
161
|
/**
|
|
168
162
|
* Attach gates to every verb (registration order). `gate.all` and arrays flatten.
|
|
169
|
-
* `.gate.public` is the unauthenticated sentinel.
|
|
170
163
|
*/
|
|
171
164
|
readonly gate: GateAttach<ResourceMount>;
|
|
165
|
+
/**
|
|
166
|
+
* Attach the unauthenticated public sentinel to every verb.
|
|
167
|
+
*/
|
|
168
|
+
public(): ResourceMount;
|
|
172
169
|
/**
|
|
173
170
|
* Mark list and get as live (GET mounts). Mutations stay request/response.
|
|
174
171
|
*/
|
|
@@ -200,34 +197,55 @@ function loadHttpResource(): typeof import("./http-resource.ts") {
|
|
|
200
197
|
* types for param extraction.
|
|
201
198
|
*/
|
|
202
199
|
export interface HttpTriggerNamespace {
|
|
200
|
+
/**
|
|
201
|
+
* Pathless — file-tree stamp fills the URL. Unresolved sentinel fails boot.
|
|
202
|
+
*/
|
|
203
|
+
get(): HttpTrigger<"GET", HttpPathPending>;
|
|
203
204
|
/**
|
|
204
205
|
* @param path - Route path (`/:id` params supported)
|
|
205
206
|
*/
|
|
206
207
|
get<P extends string>(path: P): HttpTrigger<"GET", P>;
|
|
208
|
+
/** Pathless — file-tree stamp fills the URL. */
|
|
209
|
+
post(): HttpTrigger<"POST", HttpPathPending>;
|
|
207
210
|
/**
|
|
208
211
|
* @param path - Route path
|
|
209
212
|
*/
|
|
210
213
|
post<P extends string>(path: P): HttpTrigger<"POST", P>;
|
|
214
|
+
/** Pathless — file-tree stamp fills the URL. */
|
|
215
|
+
put(): HttpTrigger<"PUT", HttpPathPending>;
|
|
211
216
|
/**
|
|
212
217
|
* @param path - Route path
|
|
213
218
|
*/
|
|
214
219
|
put<P extends string>(path: P): HttpTrigger<"PUT", P>;
|
|
220
|
+
/** Pathless — file-tree stamp fills the URL. */
|
|
221
|
+
patch(): HttpTrigger<"PATCH", HttpPathPending>;
|
|
215
222
|
/**
|
|
216
223
|
* @param path - Route path
|
|
217
224
|
*/
|
|
218
225
|
patch<P extends string>(path: P): HttpTrigger<"PATCH", P>;
|
|
226
|
+
/** Pathless — file-tree stamp fills the URL. */
|
|
227
|
+
delete(): HttpTrigger<"DELETE", HttpPathPending>;
|
|
219
228
|
/**
|
|
220
229
|
* @param path - Route path
|
|
221
230
|
*/
|
|
222
231
|
delete<P extends string>(path: P): HttpTrigger<"DELETE", P>;
|
|
232
|
+
/** Pathless — file-tree stamp fills the URL. */
|
|
233
|
+
options(): HttpTrigger<"OPTIONS", HttpPathPending>;
|
|
223
234
|
/**
|
|
224
235
|
* @param path - Route path
|
|
225
236
|
*/
|
|
226
237
|
options<P extends string>(path: P): HttpTrigger<"OPTIONS", P>;
|
|
238
|
+
/** Pathless — file-tree stamp fills the URL. */
|
|
239
|
+
head(): HttpTrigger<"HEAD", HttpPathPending>;
|
|
227
240
|
/**
|
|
228
241
|
* @param path - Route path
|
|
229
242
|
*/
|
|
230
243
|
head<P extends string>(path: P): HttpTrigger<"HEAD", P>;
|
|
244
|
+
/**
|
|
245
|
+
* Safe, idempotent read that carries a JSON body (RFC 10008).
|
|
246
|
+
* Pathless — file-tree stamp fills the URL.
|
|
247
|
+
*/
|
|
248
|
+
query(): HttpTrigger<"QUERY", HttpPathPending>;
|
|
231
249
|
/**
|
|
232
250
|
* Safe, idempotent read that carries a JSON body (RFC 10008).
|
|
233
251
|
*
|
|
@@ -236,8 +254,8 @@ export interface HttpTriggerNamespace {
|
|
|
236
254
|
query<P extends string>(path: P): HttpTrigger<"QUERY", P>;
|
|
237
255
|
/**
|
|
238
256
|
* Mount a CRUD resource (list/create on `path`, get/update/remove on
|
|
239
|
-
* `path/:id`) for the `on(http.resource(…))` overload. Chain `.gate(...)
|
|
240
|
-
* and `.live()` like {@link HttpTrigger}.
|
|
257
|
+
* `path/:id`) for the `on(http.resource(…))` overload. Chain `.gate(...)`,
|
|
258
|
+
* `.public()`, and `.live()` like {@link HttpTrigger}.
|
|
241
259
|
*/
|
|
242
260
|
resource<P extends string>(path: P, ops: ResourceFlowBag): ResourceMount;
|
|
243
261
|
}
|
|
@@ -245,8 +263,15 @@ export interface HttpTriggerNamespace {
|
|
|
245
263
|
/** Bind an HTTP verb constructor (`http.get`, `http.query`, …). */
|
|
246
264
|
function httpVerb<M extends HttpMethod>(
|
|
247
265
|
method: M,
|
|
248
|
-
):
|
|
249
|
-
|
|
266
|
+
): {
|
|
267
|
+
(): HttpTrigger<M, HttpPathPending>;
|
|
268
|
+
<P extends string>(path: P): HttpTrigger<M, P>;
|
|
269
|
+
} {
|
|
270
|
+
return ((path?: string) =>
|
|
271
|
+
createHttpTrigger(method, path === undefined ? HTTP_PATH_PENDING : path)) as {
|
|
272
|
+
(): HttpTrigger<M, HttpPathPending>;
|
|
273
|
+
<P extends string>(path: P): HttpTrigger<M, P>;
|
|
274
|
+
};
|
|
250
275
|
}
|
|
251
276
|
|
|
252
277
|
/**
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool resource refs — `mcp:<server>/<tool>` (capability / Manifest)
|
|
3
|
+
* and `<server>__<tool>` (provider-safe model tool name).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Capability / Manifest ref for one declared MCP tool. */
|
|
7
|
+
export type McpToolRef = `mcp:${string}/${string}`;
|
|
8
|
+
|
|
9
|
+
/** Parsed `mcp:<server>/<tool>` ref. */
|
|
10
|
+
export interface ParsedMcpToolRef {
|
|
11
|
+
readonly server: string;
|
|
12
|
+
readonly tool: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build a capability ref.
|
|
17
|
+
*
|
|
18
|
+
* @param server - Declared MCP server name
|
|
19
|
+
* @param tool - Allowlisted tool name on that server
|
|
20
|
+
*/
|
|
21
|
+
export function mcpToolRef(server: string, tool: string): McpToolRef {
|
|
22
|
+
return `mcp:${server}/${tool}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse `mcp:<server>/<tool>`. Null when the prefix or parts are missing.
|
|
27
|
+
*
|
|
28
|
+
* @param ref - Effect / tool resource string
|
|
29
|
+
*/
|
|
30
|
+
export function parseMcpToolRef(ref: string): ParsedMcpToolRef | null {
|
|
31
|
+
if (!ref.startsWith("mcp:")) return null;
|
|
32
|
+
const rest = ref.slice(4);
|
|
33
|
+
const slash = rest.indexOf("/");
|
|
34
|
+
if (slash <= 0 || slash === rest.length - 1) return null;
|
|
35
|
+
return { server: rest.slice(0, slash), tool: rest.slice(slash + 1) };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* True when `ref` is a well-formed MCP capability ref.
|
|
40
|
+
*
|
|
41
|
+
* @param ref - Candidate
|
|
42
|
+
*/
|
|
43
|
+
export function isMcpToolRef(ref: string): boolean {
|
|
44
|
+
return parseMcpToolRef(ref) !== null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Graph node id for one MCP server (`mcp:github`).
|
|
49
|
+
*
|
|
50
|
+
* @param server - Declared server name
|
|
51
|
+
*/
|
|
52
|
+
export function mcpServerNodeId(server: string): string {
|
|
53
|
+
return `mcp:${server}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Provider-safe function name (`github__create_issue`).
|
|
58
|
+
*
|
|
59
|
+
* @param server - Declared server name
|
|
60
|
+
* @param tool - MCP tool name
|
|
61
|
+
*/
|
|
62
|
+
export function mcpModelToolName(server: string, tool: string): string {
|
|
63
|
+
return `${server}__${tool}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Inverse of {@link mcpModelToolName}.
|
|
68
|
+
*
|
|
69
|
+
* @param name - Model-facing tool name
|
|
70
|
+
*/
|
|
71
|
+
export function parseMcpModelToolName(name: string): ParsedMcpToolRef | null {
|
|
72
|
+
const sep = name.indexOf("__");
|
|
73
|
+
if (sep <= 0 || sep === name.length - 2) return null;
|
|
74
|
+
return { server: name.slice(0, sep), tool: name.slice(sep + 2) };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Capability ref from either `mcp:server/tool` or `server__tool`.
|
|
79
|
+
*
|
|
80
|
+
* @param name - Model-facing or capability name
|
|
81
|
+
*/
|
|
82
|
+
export function mcpCapabilityRefFromName(name: string): McpToolRef | null {
|
|
83
|
+
const fromRef = parseMcpToolRef(name);
|
|
84
|
+
if (fromRef) return mcpToolRef(fromRef.server, fromRef.tool);
|
|
85
|
+
const fromModel = parseMcpModelToolName(name);
|
|
86
|
+
if (fromModel) return mcpToolRef(fromModel.server, fromModel.tool);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
package/src/manifest/types.ts
CHANGED
|
@@ -46,6 +46,12 @@ export type ResourceRef = `${StoreFacet}:${string}`;
|
|
|
46
46
|
*/
|
|
47
47
|
export type RunsResourceRef = "runs";
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Dead-letter read capability for `fx.deadLetters` — not a store facet.
|
|
51
|
+
* Declare on `effects.reads` (never `writes`).
|
|
52
|
+
*/
|
|
53
|
+
export type SignalResourceRef = `signal:${string}`;
|
|
54
|
+
|
|
49
55
|
/** Signal name reference. */
|
|
50
56
|
export type SignalRef = string;
|
|
51
57
|
|
|
@@ -74,8 +80,8 @@ export type JsonSchema = string | Record<string, unknown>;
|
|
|
74
80
|
* `sends` / `asks` are irreversible (asks also nondeterministic + cost).
|
|
75
81
|
*/
|
|
76
82
|
export interface Effects {
|
|
77
|
-
/** Store reads, plus
|
|
78
|
-
reads?: Array<ResourceRef | RunsResourceRef>;
|
|
83
|
+
/** Store reads, plus `"runs"` for `fx.runs` and `signal:name` for `fx.deadLetters`. */
|
|
84
|
+
reads?: Array<ResourceRef | RunsResourceRef | SignalResourceRef>;
|
|
79
85
|
/** Store writes. */
|
|
80
86
|
writes?: ResourceRef[];
|
|
81
87
|
/** Emitted signals. */
|
|
@@ -218,10 +224,23 @@ export interface DeclaredColumn extends ColumnClassification {
|
|
|
218
224
|
/** Field classification value forms. */
|
|
219
225
|
export type ClassificationValue = string | string[] | ColumnClassification;
|
|
220
226
|
|
|
227
|
+
/** Declared Postgres RLS policy on a {@link Table}. */
|
|
228
|
+
export interface TablePolicy {
|
|
229
|
+
as?: "permissive" | "restrictive";
|
|
230
|
+
to?: string | string[];
|
|
231
|
+
for?: "all" | "select" | "insert" | "update" | "delete";
|
|
232
|
+
using?: string;
|
|
233
|
+
withCheck?: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
221
236
|
/** SQL table metadata. */
|
|
222
237
|
export interface Table {
|
|
223
238
|
columns?: Record<string, DeclaredColumn | ColumnClassification>;
|
|
224
239
|
classifications?: Record<string, ClassificationValue>;
|
|
240
|
+
/** RLS enabled (`pgTable.withRLS` or any policy). */
|
|
241
|
+
rls?: boolean;
|
|
242
|
+
/** Policy name → Drizzle-shaped spec. */
|
|
243
|
+
policies?: Record<string, TablePolicy>;
|
|
225
244
|
}
|
|
226
245
|
|
|
227
246
|
/** One Store declaration. */
|
|
@@ -297,6 +316,8 @@ export interface AiModel {
|
|
|
297
316
|
provider?: string;
|
|
298
317
|
tier?: string;
|
|
299
318
|
model?: string;
|
|
319
|
+
/** Protocol driver override for this logical binding. */
|
|
320
|
+
driverId?: string;
|
|
300
321
|
}
|
|
301
322
|
|
|
302
323
|
/** Prompt / agent budget. */
|
|
@@ -327,11 +348,29 @@ export interface AiAgent {
|
|
|
327
348
|
budget?: AiBudget;
|
|
328
349
|
}
|
|
329
350
|
|
|
351
|
+
/**
|
|
352
|
+
* External MCP server the app consumes — tools join the existing `fx.call`
|
|
353
|
+
* / `toolLoop` path as `mcp:<server>/<tool>` capability refs.
|
|
354
|
+
*/
|
|
355
|
+
export interface AiMcpServer {
|
|
356
|
+
/** Streamable HTTP endpoint. */
|
|
357
|
+
url?: string;
|
|
358
|
+
/** stdio executable (no shell string). */
|
|
359
|
+
command?: string;
|
|
360
|
+
/** Arguments for {@link command}. */
|
|
361
|
+
args?: string[];
|
|
362
|
+
/** Bearer secret contract name (never the value). */
|
|
363
|
+
auth?: SecretRef;
|
|
364
|
+
/** Required allowlist — never trust whatever `tools/list` exposes. */
|
|
365
|
+
tools: string[];
|
|
366
|
+
}
|
|
367
|
+
|
|
330
368
|
/** AI element catalogue. */
|
|
331
369
|
export interface Ai {
|
|
332
370
|
models?: Record<string, AiModel>;
|
|
333
371
|
prompts?: Record<string, AiPrompt>;
|
|
334
372
|
agents?: Record<string, AiAgent>;
|
|
373
|
+
mcpServers?: Record<string, AiMcpServer>;
|
|
335
374
|
}
|
|
336
375
|
|
|
337
376
|
/** Per-table metadata on a Manifest {@link Plugin}. */
|
|
@@ -75,6 +75,26 @@ describe("validateManifest", () => {
|
|
|
75
75
|
expect(result.ok).toBe(false);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
test("accepts signal resource refs on reads only", async () => {
|
|
79
|
+
const reads = await validateManifest({
|
|
80
|
+
oke: "1.0",
|
|
81
|
+
app: "x",
|
|
82
|
+
flows: {
|
|
83
|
+
f: { effects: { reads: ["signal:notify"] } },
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
expect(reads.ok).toBe(true);
|
|
87
|
+
|
|
88
|
+
const writes = await validateManifest({
|
|
89
|
+
oke: "1.0",
|
|
90
|
+
app: "x",
|
|
91
|
+
flows: {
|
|
92
|
+
f: { effects: { writes: ["signal:notify"] } },
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
expect(writes.ok).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
78
98
|
test("parseManifest throws ManifestValidationError on invalid JSON", async () => {
|
|
79
99
|
expect(parseManifest("{")).rejects.toBeInstanceOf(ManifestValidationError);
|
|
80
100
|
});
|
package/src/mcp/docs-server.ts
CHANGED
package/src/mcp/server.ts
CHANGED
|
@@ -122,4 +122,25 @@ describe("compression plugin", () => {
|
|
|
122
122
|
);
|
|
123
123
|
expect(nt.headers.get("content-encoding")).toBeNull();
|
|
124
124
|
});
|
|
125
|
+
|
|
126
|
+
test("skips text/event-stream even when Accept-Encoding is gzip", async () => {
|
|
127
|
+
on(
|
|
128
|
+
http.get("/sse"),
|
|
129
|
+
flow("sse.get", {
|
|
130
|
+
do: (_input, fx) =>
|
|
131
|
+
fx.json.stream(
|
|
132
|
+
(async function* () {
|
|
133
|
+
yield "hello";
|
|
134
|
+
})(),
|
|
135
|
+
),
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
const app = oke({ autoBoot: false, name: "zip-sse" }).plug(compression({ minSize: 0 }));
|
|
139
|
+
const res = await app.fetch(
|
|
140
|
+
new Request("http://localhost/sse", { headers: { "accept-encoding": "gzip" } }),
|
|
141
|
+
);
|
|
142
|
+
expect(res.headers.get("content-type")).toMatch(/text\/event-stream/);
|
|
143
|
+
expect(res.headers.get("content-encoding")).toBeNull();
|
|
144
|
+
expect(await res.text()).toContain('data: "hello"');
|
|
145
|
+
});
|
|
125
146
|
});
|
|
@@ -73,6 +73,7 @@ export function compression(
|
|
|
73
73
|
if (cacheControl !== null && /\bno-transform\b/i.test(cacheControl)) return;
|
|
74
74
|
|
|
75
75
|
const contentType = headers.get("content-type") ?? "";
|
|
76
|
+
if (/^text\/event-stream\b/i.test(contentType.split(";")[0]?.trim() ?? "")) return;
|
|
76
77
|
if (!match.test(contentType)) return;
|
|
77
78
|
|
|
78
79
|
const body = await ctx.response.arrayBuffer();
|
package/src/release/measure.ts
CHANGED
|
@@ -201,7 +201,7 @@ export async function measureHttpPingAppBytes(): Promise<HttpPingBudgetSample> {
|
|
|
201
201
|
await Bun.write(
|
|
202
202
|
anchor,
|
|
203
203
|
`import { on, flow, http, gate, oke, createBunRuntime } from ${JSON.stringify(httpEntry)};\n` +
|
|
204
|
-
`on(http.get("/").
|
|
204
|
+
`on(http.get("/").public(), flow("ping", { do: () => "Hi" }));\n` +
|
|
205
205
|
`export const app = oke({ name: "ping" });\n` +
|
|
206
206
|
`export const rt = createBunRuntime;\n`,
|
|
207
207
|
);
|
|
@@ -362,7 +362,7 @@ const t0 = performance.now();
|
|
|
362
362
|
const { createBunRuntime, oke, on, flow, http, gate } = await import(${JSON.stringify(httpEntry)});
|
|
363
363
|
const { resetBindings } = await import(${JSON.stringify(`${import.meta.dir}/../kernel/on.ts`)});
|
|
364
364
|
resetBindings();
|
|
365
|
-
on(http.get("/ping").
|
|
365
|
+
on(http.get("/ping").public(), flow("ping", { do: () => ({ ok: true }) }));
|
|
366
366
|
const app = oke({ name: "cold-start" });
|
|
367
367
|
const rt = createBunRuntime();
|
|
368
368
|
const server = rt.serve(app, { port: 0, hostname: "127.0.0.1" });
|
package/src/runtime/bun.ts
CHANGED
|
@@ -81,7 +81,29 @@ export function isBunNativeMethod(method: string): boolean {
|
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
/** Bun.serve `fetch` / route handler — second arg is the live server. */
|
|
85
|
+
type ServeFetch = (
|
|
86
|
+
req: Request,
|
|
87
|
+
server: { timeout(request: Request, seconds: number): void },
|
|
88
|
+
) => Response | Promise<Response>;
|
|
89
|
+
|
|
90
|
+
type MethodHandlers = Partial<Record<string, ServeFetch>>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Disable the 10s idle timeout for SSE so a quiet token stream is not reset.
|
|
94
|
+
*
|
|
95
|
+
* @param inner - App fetch
|
|
96
|
+
*/
|
|
97
|
+
function holdSseIdle(inner: (request: Request) => Promise<Response>): ServeFetch {
|
|
98
|
+
return async (req, server) => {
|
|
99
|
+
const res = await inner(req);
|
|
100
|
+
const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
|
|
101
|
+
if (/^text\/event-stream$/i.test(ct)) {
|
|
102
|
+
server.timeout(req, 0);
|
|
103
|
+
}
|
|
104
|
+
return res;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
85
107
|
|
|
86
108
|
/**
|
|
87
109
|
* Build Bun.serve `routes` from app HTTP bindings.
|
|
@@ -103,7 +125,7 @@ export function buildBunRoutes(
|
|
|
103
125
|
if (typeof path !== "string" || typeof method !== "string") continue;
|
|
104
126
|
if (!isBunNativePath(path) || !isBunNativeMethod(method)) continue;
|
|
105
127
|
const methods = routes[path] ?? (routes[path] = {});
|
|
106
|
-
methods[method] = (
|
|
128
|
+
methods[method] = holdSseIdle(fetchHandler);
|
|
107
129
|
}
|
|
108
130
|
return routes;
|
|
109
131
|
}
|
|
@@ -128,10 +150,15 @@ export function serveBunHttp(options: {
|
|
|
128
150
|
readonly routes?: Record<string, MethodHandlers>;
|
|
129
151
|
readonly id?: string;
|
|
130
152
|
}): ReturnType<typeof Bun.serve> {
|
|
153
|
+
const fetch = holdSseIdle(
|
|
154
|
+
typeof options.fetch === "function"
|
|
155
|
+
? (req: Request) => Promise.resolve(options.fetch(req))
|
|
156
|
+
: options.fetch,
|
|
157
|
+
);
|
|
131
158
|
const base = {
|
|
132
159
|
port: options.port,
|
|
133
160
|
hostname: options.hostname,
|
|
134
|
-
fetch
|
|
161
|
+
fetch,
|
|
135
162
|
...(options.id !== undefined ? { id: options.id } : {}),
|
|
136
163
|
};
|
|
137
164
|
const table = options.routes;
|
|
@@ -160,6 +187,15 @@ function listenBun(app: FetchApp, options?: ServeOptions): ServerHandle {
|
|
|
160
187
|
...(options?.id !== undefined ? { id: options.id } : {}),
|
|
161
188
|
});
|
|
162
189
|
|
|
190
|
+
const closeIdle = (
|
|
191
|
+
server as typeof server & { closeIdleConnections(): void }
|
|
192
|
+
).closeIdleConnections.bind(server);
|
|
193
|
+
const onPressure = (): void => {
|
|
194
|
+
closeIdle();
|
|
195
|
+
};
|
|
196
|
+
const proc = process as NodeJS.EventEmitter;
|
|
197
|
+
proc.on("memoryPressure", onPressure);
|
|
198
|
+
|
|
163
199
|
const boundPort = server.port ?? port;
|
|
164
200
|
const boundHost = server.hostname ?? hostname;
|
|
165
201
|
const url = new URL(`http://${formatHostForUrl(boundHost)}:${boundPort}/`);
|
|
@@ -170,7 +206,8 @@ function listenBun(app: FetchApp, options?: ServeOptions): ServerHandle {
|
|
|
170
206
|
hostname: boundHost,
|
|
171
207
|
fetch: fetchHandler,
|
|
172
208
|
stop(closeActiveConnections = false) {
|
|
173
|
-
|
|
209
|
+
proc.off("memoryPressure", onPressure);
|
|
210
|
+
return server.stop(closeActiveConnections);
|
|
174
211
|
},
|
|
175
212
|
};
|
|
176
213
|
}
|
|
@@ -255,7 +255,7 @@ describe("httpGetNavPaths", () => {
|
|
|
255
255
|
describe("asBrowserJsonCodeBlock — HTTP", () => {
|
|
256
256
|
test("Chrome GET / becomes the code block; curl stays JSON", async () => {
|
|
257
257
|
on(
|
|
258
|
-
http.get("/").
|
|
258
|
+
http.get("/").public(),
|
|
259
259
|
flow("main.root", {
|
|
260
260
|
do: () => ({ ok: true as const, app: "keel" }),
|
|
261
261
|
}),
|
|
@@ -286,7 +286,7 @@ describe("asBrowserJsonCodeBlock — HTTP", () => {
|
|
|
286
286
|
});
|
|
287
287
|
|
|
288
288
|
test("?raw=1 stays on the traces page; format=json and Accept stay the envelope", async () => {
|
|
289
|
-
on(http.get("/health").
|
|
289
|
+
on(http.get("/health").public(), flow("main.health", { do: () => ({ ok: true as const }) }));
|
|
290
290
|
const app = oke({ autoBoot: false, name: "keel" });
|
|
291
291
|
|
|
292
292
|
const raw = await app.fetch(
|
|
@@ -55,13 +55,13 @@ function rawHttp(port: number, payload: string): Promise<string> {
|
|
|
55
55
|
|
|
56
56
|
function buildApp() {
|
|
57
57
|
on(
|
|
58
|
-
http.get("/ping").
|
|
58
|
+
http.get("/ping").public(),
|
|
59
59
|
flow("ping", {
|
|
60
60
|
do: () => ({ ok: true as const, n: 1 }),
|
|
61
61
|
}),
|
|
62
62
|
);
|
|
63
63
|
on(
|
|
64
|
-
http.get("/notes/:id").
|
|
64
|
+
http.get("/notes/:id").public(),
|
|
65
65
|
flow("notes.get", {
|
|
66
66
|
do: ({ id }: { id: string }) => ({ id }),
|
|
67
67
|
}),
|
|
@@ -157,13 +157,13 @@ describe("Bun.serve — real HTTP", () => {
|
|
|
157
157
|
|
|
158
158
|
test("QUERY is omitted from native routes so Bun.serve still boots", async () => {
|
|
159
159
|
on(
|
|
160
|
-
http.query("/search").
|
|
160
|
+
http.query("/search").public(),
|
|
161
161
|
flow("search.query", {
|
|
162
162
|
do: () => ({ hits: 1 }),
|
|
163
163
|
}),
|
|
164
164
|
);
|
|
165
165
|
on(
|
|
166
|
-
http.get("/search/suggest").
|
|
166
|
+
http.get("/search/suggest").public(),
|
|
167
167
|
flow("search.suggest", {
|
|
168
168
|
do: () => ({ hits: 0 }),
|
|
169
169
|
}),
|