okengine 0.14.1 → 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/package.json +3 -4
- package/site/content/docs/elements/ai.mdx +2 -1
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +3 -0
- package/site/content/docs/elements/flow.mdx +3 -0
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +4 -4
- package/site/content/docs/elements/store.mdx +86 -27
- 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/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/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/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/extract.test.ts +409 -33
- package/src/compiler/extract.ts +318 -67
- 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/schema-from-ast.ts +609 -0
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +120 -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/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-BanhLsEY.js → cache-glyph-F1FI122b.js} +1 -1
- 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-BEZDzyYh.js → observability-page-HK9-BO8z.js} +2 -2
- 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/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/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- 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/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 +3 -0
- package/src/docker/recipes/pgdog.ts +2 -0
- 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/types.ts +10 -0
- package/src/elements/clock/declare.ts +26 -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/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/http.ts +20 -3
- package/src/index.ts +10 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +131 -14
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +49 -9
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +12 -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.ts +23 -0
- 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 +4 -4
- 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/types.ts +13 -0
- package/src/release/measure.ts +2 -2
- 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 +8 -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/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- 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-DFLFfUUX.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
package/src/elements/store.ts
CHANGED
|
@@ -26,6 +26,11 @@ export {
|
|
|
26
26
|
schema,
|
|
27
27
|
schemaTable,
|
|
28
28
|
schemaRelations,
|
|
29
|
+
schemaRls,
|
|
30
|
+
schemaPolicy,
|
|
31
|
+
schemaPolicyGate,
|
|
32
|
+
schemaPolicyOwner,
|
|
33
|
+
schemaPolicyScope,
|
|
29
34
|
isSchemaTableDecl,
|
|
30
35
|
isSchemaColumnDecl,
|
|
31
36
|
isSchemaRelationsDecl,
|
|
@@ -43,6 +48,8 @@ export type {
|
|
|
43
48
|
SchemaTableDecl,
|
|
44
49
|
SchemaTableWithColumns,
|
|
45
50
|
SchemaRelationsDecl,
|
|
51
|
+
SchemaPolicyDecl,
|
|
52
|
+
SchemaTableExtra,
|
|
46
53
|
SchemaRelationEntry,
|
|
47
54
|
SchemaRelationOne,
|
|
48
55
|
SchemaRelationMany,
|
|
@@ -123,6 +130,13 @@ export type {
|
|
|
123
130
|
WhereMap,
|
|
124
131
|
} from "./store/sql-session.ts";
|
|
125
132
|
|
|
133
|
+
export {
|
|
134
|
+
resolveRlsIdentity,
|
|
135
|
+
rlsIdentityFromAuth,
|
|
136
|
+
firstPolicyOrPublic,
|
|
137
|
+
policyGateForScopes,
|
|
138
|
+
} from "./store/rls-identity.ts";
|
|
139
|
+
export type { RlsIdentityRow, ResolveRlsIdentityInput } from "./store/rls-identity.ts";
|
|
126
140
|
export { createStoreRuntime, putTier2 } from "./store/runtime.ts";
|
|
127
141
|
export type {
|
|
128
142
|
StoreRuntime,
|
package/src/http.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* ```ts
|
|
9
9
|
* import { on, flow, http, gate, oke, createBunRuntime } from "okengine/http";
|
|
10
10
|
*
|
|
11
|
-
* on(http.get("/").
|
|
11
|
+
* on(http.get("/").public(), flow("ping", { do: () => "Hi" }));
|
|
12
12
|
* const app = oke({ name: "ping" });
|
|
13
13
|
* createBunRuntime().serve(app);
|
|
14
14
|
* ```
|
|
@@ -24,15 +24,32 @@ export { fail, type FlowFailure } from "./kernel/errors.ts";
|
|
|
24
24
|
export { isFlowFailure } from "./kernel/hooks.ts";
|
|
25
25
|
export { plugin, isPlugin, type PluginDef } from "./kernel/plugin.ts";
|
|
26
26
|
export { createBunRuntime, APP_PORT, type Runtime, type ServeOptions } from "./runtime/index.ts";
|
|
27
|
-
export type {
|
|
27
|
+
export type {
|
|
28
|
+
OkeApp,
|
|
29
|
+
OkeOptions,
|
|
30
|
+
ReadyState,
|
|
31
|
+
RegisteredFlowUnits,
|
|
32
|
+
RoutesFromRegisteredUnits,
|
|
33
|
+
} from "./kernel/app.ts";
|
|
34
|
+
export { registerFlowUnits } from "./kernel/flow-units.ts";
|
|
35
|
+
export { stampFlowName, stampHttpPath } from "./kernel/stamp-http.ts";
|
|
28
36
|
|
|
29
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
oke as okeCore,
|
|
39
|
+
type OkeApp,
|
|
40
|
+
type OkeOptions,
|
|
41
|
+
type RoutesFromRegisteredUnits,
|
|
42
|
+
} from "./kernel/app.ts";
|
|
30
43
|
|
|
31
44
|
/**
|
|
32
45
|
* Create an HTTP-oriented app — defaults `router` to `"edge"` when omitted.
|
|
33
46
|
*
|
|
34
47
|
* @param options - Application options
|
|
35
48
|
*/
|
|
49
|
+
export function oke(
|
|
50
|
+
options: OkeOptions & { readonly registry: "ignore" },
|
|
51
|
+
): OkeApp<Record<string, never>, Record<string, never>>;
|
|
52
|
+
export function oke(options: OkeOptions): OkeApp<{}, RoutesFromRegisteredUnits>;
|
|
36
53
|
export function oke(options: OkeOptions): OkeApp {
|
|
37
54
|
return okeCore({
|
|
38
55
|
...options,
|
package/src/index.ts
CHANGED
|
@@ -14,10 +14,19 @@
|
|
|
14
14
|
* @module
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
export {
|
|
17
|
+
export {
|
|
18
|
+
oke,
|
|
19
|
+
type OkeApp,
|
|
20
|
+
type OkeOptions,
|
|
21
|
+
type ReadyState,
|
|
22
|
+
type RegisteredFlowUnits,
|
|
23
|
+
type RoutesFromRegisteredUnits,
|
|
24
|
+
} from "./kernel/app.ts";
|
|
18
25
|
export { on, type Binding } from "./kernel/on.ts";
|
|
19
26
|
export { flow, isFlow, type FlowDef } from "./kernel/flow.ts";
|
|
20
27
|
export { http, every, internal, table } from "./kernel/triggers.ts";
|
|
28
|
+
export { registerFlowUnits } from "./kernel/flow-units.ts";
|
|
29
|
+
export { stampFlowName, stampHttpPath } from "./kernel/stamp-http.ts";
|
|
21
30
|
export { fail, type FlowFailure, type FlowErrorValue } from "./kernel/errors.ts";
|
|
22
31
|
export { isFlowFailure } from "./kernel/hooks.ts";
|
|
23
32
|
export { plugin, isPlugin, type PluginDef, type PluginCapabilities } from "./kernel/plugin.ts";
|
|
@@ -89,7 +89,7 @@ describe("boot-level: stale barrel through a real oke() boot", () => {
|
|
|
89
89
|
effects: {},
|
|
90
90
|
do: () => ({ ok: true as const }),
|
|
91
91
|
});
|
|
92
|
-
on(http.get("/health").
|
|
92
|
+
on(http.get("/health").public(), mainFlow);
|
|
93
93
|
const app = oke({ name: "stale-barrel", gate: { unguardedHttp: "allow" } });
|
|
94
94
|
|
|
95
95
|
await expect(
|
|
@@ -39,10 +39,20 @@ export type AppFlowRoute<
|
|
|
39
39
|
readonly path?: undefined;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Unit → flow → contract. Leaf is the HTTP / call-only union — do not
|
|
44
|
+
* index with `AppFlowRoute<any, any, any, any, any>` (`any extends string`
|
|
45
|
+
* makes `method`/`path` required and rejects cron / signal flows).
|
|
46
|
+
*/
|
|
43
47
|
export type AppRouteMap = {
|
|
44
48
|
readonly [unit: string]: {
|
|
45
|
-
readonly [flow: string]:
|
|
49
|
+
readonly [flow: string]: {
|
|
50
|
+
readonly in?: unknown;
|
|
51
|
+
readonly out?: unknown;
|
|
52
|
+
readonly errors?: unknown;
|
|
53
|
+
readonly method?: string;
|
|
54
|
+
readonly path?: string;
|
|
55
|
+
};
|
|
46
56
|
};
|
|
47
57
|
};
|
|
48
58
|
|
|
@@ -170,6 +180,7 @@ export function accumulateAdoptArgs(args: readonly unknown[], into: RuntimeRoute
|
|
|
170
180
|
const unitBag = into[unit] ?? (into[unit] = {});
|
|
171
181
|
for (const [flowName, flowDef] of Object.entries(value as Record<string, unknown>)) {
|
|
172
182
|
if (!isFlow(flowDef)) continue;
|
|
183
|
+
stampAdoptedFlow(flowDef, unit, flowName);
|
|
173
184
|
found.push(flowDef);
|
|
174
185
|
unitBag[flowName] = runtimeRouteFromFlow(flowDef);
|
|
175
186
|
}
|
|
@@ -178,3 +189,20 @@ export function accumulateAdoptArgs(args: readonly unknown[], into: RuntimeRoute
|
|
|
178
189
|
|
|
179
190
|
return found;
|
|
180
191
|
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Stamp `unit.export` on nameless flows — same rule as {@link unit}.
|
|
195
|
+
*
|
|
196
|
+
* @param flowDef - Flow to stamp
|
|
197
|
+
* @param unit - Client unit (folder / adopt key)
|
|
198
|
+
* @param exportName - Namespace export
|
|
199
|
+
*/
|
|
200
|
+
function stampAdoptedFlow(flowDef: AnyFlowDef, unit: string, exportName: string): void {
|
|
201
|
+
const f = flowDef as { name: string; unit: string | undefined };
|
|
202
|
+
if (!f.name || f.name.startsWith("flow_")) {
|
|
203
|
+
f.name = `${unit}.${exportName}`;
|
|
204
|
+
}
|
|
205
|
+
if (!f.unit) {
|
|
206
|
+
f.unit = unit;
|
|
207
|
+
}
|
|
208
|
+
}
|
package/src/kernel/app.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
accumulateAdoptArgs,
|
|
15
15
|
type AppRouteMap,
|
|
16
16
|
type RoutesFromAdoptArgs,
|
|
17
|
+
type RoutesFromNamespace,
|
|
17
18
|
type RuntimeRouteMap,
|
|
18
19
|
} from "./adopt-routes.ts";
|
|
19
20
|
// `./boot.ts` pulls in every element + driver module (vault, store, signal,
|
|
@@ -59,7 +60,8 @@ import {
|
|
|
59
60
|
} from "../runtime/dev-request-log.ts";
|
|
60
61
|
import { asBrowserJsonCodeBlock, httpNavGroups } from "../runtime/json-code-block.ts";
|
|
61
62
|
import { resolveDurationMs } from "./elapsed.ts";
|
|
62
|
-
import { fail } from "./errors.ts";
|
|
63
|
+
import { fail, throwOke } from "./errors.ts";
|
|
64
|
+
import { consumeRegisteredFlowUnits, type FlowUnitBag } from "./flow-units.ts";
|
|
63
65
|
import {
|
|
64
66
|
isFlowFailure,
|
|
65
67
|
mergeHooks,
|
|
@@ -91,6 +93,8 @@ import {
|
|
|
91
93
|
aiModelRegistry,
|
|
92
94
|
aiPromptRegistry,
|
|
93
95
|
channelTemplateRegistry,
|
|
96
|
+
clockRegistry,
|
|
97
|
+
gateRegistry,
|
|
94
98
|
requiredEnvRegistry,
|
|
95
99
|
secretRegistry,
|
|
96
100
|
signalRegistry,
|
|
@@ -99,6 +103,7 @@ import {
|
|
|
99
103
|
import {
|
|
100
104
|
applyPrincipal,
|
|
101
105
|
createElementPipelineHooks,
|
|
106
|
+
gateNamesOf,
|
|
102
107
|
type PrincipalBag,
|
|
103
108
|
type ResolvedPrincipal,
|
|
104
109
|
} from "./pipeline.ts";
|
|
@@ -118,6 +123,7 @@ import {
|
|
|
118
123
|
type RouterPreset,
|
|
119
124
|
type SmartRouter,
|
|
120
125
|
} from "./router.ts";
|
|
126
|
+
import { isPendingHttpPath } from "./http-path-pending.ts";
|
|
121
127
|
import type {
|
|
122
128
|
CdcTrigger,
|
|
123
129
|
EveryTrigger,
|
|
@@ -299,6 +305,21 @@ export interface UnitHooks<D extends Record<string, unknown> = {}> {
|
|
|
299
305
|
plug<P extends PluginDef>(pluginDef: P): UnitHooks<AccumulateDecorations<D, P>>;
|
|
300
306
|
}
|
|
301
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Module-augmentation slot filled by `src/flows/generated.ts`.
|
|
310
|
+
* Kernel tests do not import an app generated file, so this stays `{}`.
|
|
311
|
+
*/
|
|
312
|
+
export interface RegisteredFlowUnits {}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* `$routes` derived from {@link RegisteredFlowUnits} after `import generated`.
|
|
316
|
+
*
|
|
317
|
+
* @typeParam U - Augmented unit map
|
|
318
|
+
*/
|
|
319
|
+
export type RoutesFromRegisteredUnits<U = RegisteredFlowUnits> = {
|
|
320
|
+
[K in keyof U]: U[K] extends Record<string, unknown> ? RoutesFromNamespace<U[K]> : never;
|
|
321
|
+
};
|
|
322
|
+
|
|
302
323
|
/**
|
|
303
324
|
* Application instance — adopts flows, routes HTTP, runs the pipeline.
|
|
304
325
|
*
|
|
@@ -435,6 +456,8 @@ export interface OkeApp<D extends Record<string, unknown> = {}, R extends AppRou
|
|
|
435
456
|
* Takes effect only with {@link trustedInvoke}.
|
|
436
457
|
*/
|
|
437
458
|
readonly revealPii?: boolean;
|
|
459
|
+
/** Forced RLS bag (Console / Call API). Operator omit = no stamp. */
|
|
460
|
+
readonly rls?: import("../drivers/pg-rls.ts").RlsIdentity;
|
|
438
461
|
/** Parent WideEvent id when this execution was caused by another. */
|
|
439
462
|
readonly parentId?: string;
|
|
440
463
|
/** Explicit run / WideEvent id (defaults to a new UUID). */
|
|
@@ -562,11 +585,57 @@ function mergeAiOptions(
|
|
|
562
585
|
};
|
|
563
586
|
}
|
|
564
587
|
|
|
588
|
+
/**
|
|
589
|
+
* Refuse an unresolved path sentinel or a nameless HTTP flow.
|
|
590
|
+
*
|
|
591
|
+
* @param binding - HTTP binding
|
|
592
|
+
*/
|
|
593
|
+
function assertHttpBindingReady(binding: Binding): void {
|
|
594
|
+
const trigger = binding.trigger;
|
|
595
|
+
if (trigger.kind !== "http") return;
|
|
596
|
+
if (isPendingHttpPath(trigger.path)) {
|
|
597
|
+
throwOke("HTTP_PATH_UNRESOLVED", {
|
|
598
|
+
flow: binding.flow.name || "(unnamed)",
|
|
599
|
+
method: trigger.method,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
if (!binding.flow.name || binding.flow.name.startsWith("flow_")) {
|
|
603
|
+
throwOke("HTTP_FLOW_UNNAMED", {
|
|
604
|
+
method: trigger.method,
|
|
605
|
+
path: trigger.path,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Fold `generated.ts` units into `$routes` and `flowsByName`.
|
|
612
|
+
*
|
|
613
|
+
* @param units - Drained {@link registerFlowUnits} bag
|
|
614
|
+
* @param routes - Runtime `$routes`
|
|
615
|
+
* @param registerFlow - App flow index
|
|
616
|
+
*/
|
|
617
|
+
function drainGeneratedUnits(
|
|
618
|
+
units: Record<string, FlowUnitBag>,
|
|
619
|
+
routes: RuntimeRouteMap,
|
|
620
|
+
registerFlow: (flowDef: AnyFlowDef) => void,
|
|
621
|
+
): void {
|
|
622
|
+
if (Object.keys(units).length === 0) return;
|
|
623
|
+
const flows = accumulateAdoptArgs([units], routes);
|
|
624
|
+
for (const flowDef of flows) {
|
|
625
|
+
registerFlow(flowDef);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
565
629
|
/**
|
|
566
630
|
* Create an application. Adopts bindings registered via {@link on}.
|
|
631
|
+
* Drains {@link registerFlowUnits} into `$routes` unless `registry: "ignore"`.
|
|
567
632
|
*
|
|
568
633
|
* @param options - App name and router preset
|
|
569
634
|
*/
|
|
635
|
+
export function oke(
|
|
636
|
+
options: OkeOptions & { readonly registry: "ignore" },
|
|
637
|
+
): OkeApp<Record<string, never>, Record<string, never>>;
|
|
638
|
+
export function oke(options: OkeOptions): OkeApp<{}, RoutesFromRegisteredUnits>;
|
|
570
639
|
export function oke(options: OkeOptions): OkeApp {
|
|
571
640
|
const aot = options.aot !== false;
|
|
572
641
|
const registry = options.registry ?? "consume";
|
|
@@ -576,11 +645,12 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
576
645
|
: [...listBindings(), ...(options.bindings ?? [])];
|
|
577
646
|
if (registry === "consume") resetBindings();
|
|
578
647
|
|
|
579
|
-
// Same registry mode drains store
|
|
580
|
-
// channel.<medium>().template(), and
|
|
581
|
-
// module-evaluation registries
|
|
582
|
-
// (`listBindings`/`resetBindings` above).
|
|
583
|
-
// `
|
|
648
|
+
// Same registry mode drains store.*, vault.secret, signal(), clock(),
|
|
649
|
+
// gate.policy/scope/rate, channel.<medium>().template(), and
|
|
650
|
+
// ai.model/prompt/embed/agent/mcpServer — module-evaluation registries
|
|
651
|
+
// that mirror `on`'s trigger drain (`listBindings`/`resetBindings` above).
|
|
652
|
+
// Explicit `options.stores` / `secrets` / `signals` / `clocks` /
|
|
653
|
+
// `gate.policies` / `channel.templates` / `ai` are additive, never
|
|
584
654
|
// silently ignored — deduped by reference so an explicitly-passed decl
|
|
585
655
|
// that is also in the registry is not doubled.
|
|
586
656
|
const registrySnapshot =
|
|
@@ -590,6 +660,8 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
590
660
|
secrets: [],
|
|
591
661
|
requiredEnv: [],
|
|
592
662
|
signals: [],
|
|
663
|
+
clocks: [],
|
|
664
|
+
gates: [],
|
|
593
665
|
channelTemplates: [],
|
|
594
666
|
aiModels: [],
|
|
595
667
|
aiPrompts: [],
|
|
@@ -602,6 +674,8 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
602
674
|
secrets: secretRegistry.slice(),
|
|
603
675
|
requiredEnv: requiredEnvRegistry.slice(),
|
|
604
676
|
signals: signalRegistry.slice(),
|
|
677
|
+
clocks: clockRegistry.slice(),
|
|
678
|
+
gates: gateRegistry.slice(),
|
|
605
679
|
channelTemplates: channelTemplateRegistry.slice(),
|
|
606
680
|
aiModels: aiModelRegistry.slice(),
|
|
607
681
|
aiPrompts: aiPromptRegistry.slice(),
|
|
@@ -614,6 +688,8 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
614
688
|
secretRegistry.length = 0;
|
|
615
689
|
requiredEnvRegistry.length = 0;
|
|
616
690
|
signalRegistry.length = 0;
|
|
691
|
+
clockRegistry.length = 0;
|
|
692
|
+
gateRegistry.length = 0;
|
|
617
693
|
channelTemplateRegistry.length = 0;
|
|
618
694
|
aiModelRegistry.length = 0;
|
|
619
695
|
aiPromptRegistry.length = 0;
|
|
@@ -624,6 +700,8 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
624
700
|
const effectiveStores = mergeUnique(options.stores, registrySnapshot.stores);
|
|
625
701
|
const effectiveSecrets = mergeUnique(options.secrets, registrySnapshot.secrets);
|
|
626
702
|
const effectiveSignals = mergeUnique(options.signals, registrySnapshot.signals);
|
|
703
|
+
const effectiveClocks = mergeUnique(options.clocks, registrySnapshot.clocks);
|
|
704
|
+
const effectivePolicies = mergeUnique(options.gate?.policies, registrySnapshot.gates);
|
|
627
705
|
/**
|
|
628
706
|
* Fold registered `vault.env.required` names into the vault boot options so
|
|
629
707
|
* boot reports missing env vars in the same gap list as missing secrets.
|
|
@@ -658,16 +736,23 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
658
736
|
|
|
659
737
|
// Resolve Gate bag early so auth HTTP Bindings join `adopted` + the router
|
|
660
738
|
// before posture audit (same ensureBoot → doBoot path — never a side channel).
|
|
739
|
+
const mergedGate: GateOptions | undefined =
|
|
740
|
+
effectivePolicies.length > 0 ? { ...options.gate, policies: effectivePolicies } : options.gate;
|
|
661
741
|
const gateConfig: ResolvedGateConfig = resolveGateConfig({
|
|
662
|
-
gate:
|
|
742
|
+
gate: mergedGate,
|
|
663
743
|
env: options.env,
|
|
664
744
|
});
|
|
665
745
|
|
|
666
|
-
/** Retained for test harness / boot merges (includes auto-drained
|
|
667
|
-
const $options: OkeOptions =
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
746
|
+
/** Retained for test harness / boot merges (includes auto-drained decls). */
|
|
747
|
+
const $options: OkeOptions = {
|
|
748
|
+
...options,
|
|
749
|
+
...(effectiveAi !== undefined ? { ai: effectiveAi } : {}),
|
|
750
|
+
...(effectiveStores.length > 0 ? { stores: effectiveStores } : {}),
|
|
751
|
+
...(effectiveSecrets.length > 0 ? { secrets: effectiveSecrets } : {}),
|
|
752
|
+
...(effectiveSignals.length > 0 ? { signals: effectiveSignals } : {}),
|
|
753
|
+
...(effectiveClocks.length > 0 ? { clocks: effectiveClocks } : {}),
|
|
754
|
+
...(mergedGate !== undefined ? { gate: mergedGate } : {}),
|
|
755
|
+
};
|
|
671
756
|
|
|
672
757
|
const appHooks: HookMap = {};
|
|
673
758
|
const unitHooks = new Map<string, HookMap>();
|
|
@@ -701,8 +786,19 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
701
786
|
}
|
|
702
787
|
|
|
703
788
|
const smart = createRouter<Binding>(options.router ?? "default");
|
|
789
|
+
const seenHttpRoutes = new Set<string>();
|
|
704
790
|
for (const b of adopted) {
|
|
705
791
|
if (b.trigger.kind === "http") {
|
|
792
|
+
assertHttpBindingReady(b);
|
|
793
|
+
const key = `${b.trigger.method} ${b.trigger.path}`;
|
|
794
|
+
if (seenHttpRoutes.has(key)) {
|
|
795
|
+
throwOke("HTTP_ROUTE_DUPLICATE", {
|
|
796
|
+
method: b.trigger.method,
|
|
797
|
+
path: b.trigger.path,
|
|
798
|
+
flow: b.flow.name || "(unnamed)",
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
seenHttpRoutes.add(key);
|
|
706
802
|
smart.add(b.trigger.method, b.trigger.path, b);
|
|
707
803
|
compiled.set(b, compileHttpBinding(b, aot));
|
|
708
804
|
}
|
|
@@ -715,6 +811,16 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
715
811
|
adopted.push(b);
|
|
716
812
|
registerFlow(b.flow);
|
|
717
813
|
if (b.trigger.kind === "http") {
|
|
814
|
+
assertHttpBindingReady(b);
|
|
815
|
+
const key = `${b.trigger.method} ${b.trigger.path}`;
|
|
816
|
+
if (seenHttpRoutes.has(key)) {
|
|
817
|
+
throwOke("HTTP_ROUTE_DUPLICATE", {
|
|
818
|
+
method: b.trigger.method,
|
|
819
|
+
path: b.trigger.path,
|
|
820
|
+
flow: b.flow.name || "(unnamed)",
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
seenHttpRoutes.add(key);
|
|
718
824
|
smart.add(b.trigger.method, b.trigger.path, b);
|
|
719
825
|
compiled.set(b, compileHttpBinding(b, aot));
|
|
720
826
|
}
|
|
@@ -788,6 +894,11 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
788
894
|
registerFlow(b.flow);
|
|
789
895
|
}
|
|
790
896
|
|
|
897
|
+
if (registry !== "ignore") {
|
|
898
|
+
const generatedUnits = consumeRegisteredFlowUnits();
|
|
899
|
+
drainGeneratedUnits(generatedUnits, routes, registerFlow);
|
|
900
|
+
}
|
|
901
|
+
|
|
791
902
|
// --- boot (vault → store → signal → clock → channel → AI → runs → caps) ---
|
|
792
903
|
let bootResult: BootResult | undefined;
|
|
793
904
|
let bootPromise: Promise<BootResult> | undefined;
|
|
@@ -963,7 +1074,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
963
1074
|
vault: withRequiredEnv(overrides?.vault ?? options.vault),
|
|
964
1075
|
gates: [...baseGates, ...authGates],
|
|
965
1076
|
signals: overrides?.signals ?? effectiveSignals,
|
|
966
|
-
clocks: overrides?.clocks ??
|
|
1077
|
+
clocks: overrides?.clocks ?? effectiveClocks,
|
|
967
1078
|
stores: overrides?.stores ?? effectiveStores,
|
|
968
1079
|
channel: overrides?.channel ?? effectiveChannel,
|
|
969
1080
|
ai: overrides?.ai ?? effectiveAi,
|
|
@@ -1019,7 +1130,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1019
1130
|
|
|
1020
1131
|
const baseSecrets = overrides?.secrets ?? effectiveSecrets;
|
|
1021
1132
|
const baseSignals = overrides?.signals ?? effectiveSignals;
|
|
1022
|
-
const baseClocks = overrides?.clocks ??
|
|
1133
|
+
const baseClocks = overrides?.clocks ?? effectiveClocks;
|
|
1023
1134
|
const baseChannel = overrides?.channel ?? effectiveChannel;
|
|
1024
1135
|
const mergedCatalog = mergeTemplateCatalogs(baseChannel?.catalog, ...pluginChannelCatalogs);
|
|
1025
1136
|
|
|
@@ -1138,6 +1249,8 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1138
1249
|
readonly bypassGates?: boolean;
|
|
1139
1250
|
/** Console Call API — cleartext store PII. Requires {@link trustedInvoke}. */
|
|
1140
1251
|
readonly revealPii?: boolean;
|
|
1252
|
+
/** Forced RLS bag (Console / Call API). Operator omit = no stamp. */
|
|
1253
|
+
readonly rls?: import("../drivers/pg-rls.ts").RlsIdentity;
|
|
1141
1254
|
/** Frozen origin identity for {@link Fx.principal} across `fx.call`. */
|
|
1142
1255
|
readonly originPrincipal?: FxPrincipal;
|
|
1143
1256
|
/** Explicit locale override for {@link Fx.t} / channel sends. */
|
|
@@ -1189,6 +1302,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1189
1302
|
readonly trustedInvoke?: boolean;
|
|
1190
1303
|
readonly bypassGates?: boolean;
|
|
1191
1304
|
readonly revealPii?: boolean;
|
|
1305
|
+
readonly rls?: import("../drivers/pg-rls.ts").RlsIdentity;
|
|
1192
1306
|
readonly originPrincipal?: FxPrincipal;
|
|
1193
1307
|
readonly locale?: string;
|
|
1194
1308
|
readonly parentId?: string;
|
|
@@ -1339,6 +1453,9 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1339
1453
|
...(principals ? { auth: principals.auth as FxAuth, operator: principals.operator } : {}),
|
|
1340
1454
|
...(extras?.originPrincipal ? { principal: extras.originPrincipal } : {}),
|
|
1341
1455
|
...(extras?.trustedInvoke === true && extras.revealPii === true ? { revealPii: true } : {}),
|
|
1456
|
+
rlsGateNames: gateNamesOf(trigger),
|
|
1457
|
+
rlsBypass: extras?.bypassGates === true || flowDef.plane === "operator",
|
|
1458
|
+
...(extras?.rls ? { rls: extras.rls } : {}),
|
|
1342
1459
|
...(capability ? { capability } : {}),
|
|
1343
1460
|
...(booted
|
|
1344
1461
|
? {
|
|
@@ -44,7 +44,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
44
44
|
const db = store.sql("app", { schema: { notes } });
|
|
45
45
|
let lists = 0;
|
|
46
46
|
const list = on(
|
|
47
|
-
http.get("/notes").
|
|
47
|
+
http.get("/notes").public(),
|
|
48
48
|
flow("notes.list", {
|
|
49
49
|
out: z.array(NoteOut),
|
|
50
50
|
effects: { reads: ["sql:notes"] },
|
|
@@ -80,7 +80,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
80
80
|
resetFlowSeq();
|
|
81
81
|
const db = store.sql("app", { schema: { notes } });
|
|
82
82
|
const list = on(
|
|
83
|
-
http.get("/notes").
|
|
83
|
+
http.get("/notes").public(),
|
|
84
84
|
flow("notes.list", {
|
|
85
85
|
out: z.array(NoteOut),
|
|
86
86
|
effects: { reads: ["sql:notes"] },
|
|
@@ -115,7 +115,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
115
115
|
const db = store.sql("app", { schema: { notes } });
|
|
116
116
|
let lists = 0;
|
|
117
117
|
const list = on(
|
|
118
|
-
http.get("/notes").
|
|
118
|
+
http.get("/notes").public(),
|
|
119
119
|
flow("notes.list", {
|
|
120
120
|
cache: false,
|
|
121
121
|
effects: { reads: ["sql:notes"] },
|
|
@@ -146,7 +146,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
146
146
|
const db = store.sql("app", { schema: { notes } });
|
|
147
147
|
let lists = 0;
|
|
148
148
|
const list = on(
|
|
149
|
-
http.get("/notes").
|
|
149
|
+
http.get("/notes").public(),
|
|
150
150
|
flow("notes.list", {
|
|
151
151
|
out: z.array(NoteOut),
|
|
152
152
|
do: async (_input, fx) => {
|
|
@@ -191,7 +191,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
191
191
|
const db = store.sql("app", { schema: { notes } });
|
|
192
192
|
let lists = 0;
|
|
193
193
|
const list = on(
|
|
194
|
-
http.get("/notes").
|
|
194
|
+
http.get("/notes").public(),
|
|
195
195
|
flow("notes.list", {
|
|
196
196
|
effects: { reads: ["sql:notes"] },
|
|
197
197
|
do: () => {
|
|
@@ -201,7 +201,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
201
201
|
}),
|
|
202
202
|
);
|
|
203
203
|
const create = on(
|
|
204
|
-
http.post("/notes").
|
|
204
|
+
http.post("/notes").public(),
|
|
205
205
|
flow("notes.create", {
|
|
206
206
|
in: z.object({ title: z.string() }),
|
|
207
207
|
effects: { writes: ["sql:notes"] },
|
|
@@ -231,7 +231,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
231
231
|
const db = store.sql("app", { schema: { notes } });
|
|
232
232
|
let lists = 0;
|
|
233
233
|
const list = on(
|
|
234
|
-
http.get("/notes").
|
|
234
|
+
http.get("/notes").public(),
|
|
235
235
|
flow("notes.list", {
|
|
236
236
|
out: z.array(NoteOut),
|
|
237
237
|
do: async (_input, fx) => {
|
|
@@ -242,7 +242,7 @@ describe("automatic tier-1 cache from effects", () => {
|
|
|
242
242
|
}),
|
|
243
243
|
);
|
|
244
244
|
const create = on(
|
|
245
|
-
http.post("/notes").
|
|
245
|
+
http.post("/notes").public(),
|
|
246
246
|
flow("notes.create", {
|
|
247
247
|
in: z.object({ title: z.string() }),
|
|
248
248
|
do: async (input, fx) => {
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `oke({...})` auto-populates `stores` / `secrets` / `signals` /
|
|
3
|
-
* `channel.templates` from the same kind of
|
|
4
|
-
* already proven for `on()`'s trigger drain
|
|
2
|
+
* `oke({...})` auto-populates `stores` / `secrets` / `signals` / `clocks` /
|
|
3
|
+
* `gate.policies` / `channel.templates` from the same kind of
|
|
4
|
+
* module-evaluation registry already proven for `on()`'s trigger drain
|
|
5
|
+
* (`registry-isolation.test.ts`).
|
|
5
6
|
*
|
|
6
|
-
* `store
|
|
7
|
-
* `.template()` push into a shared registry
|
|
8
|
-
* at call time — zero change to how
|
|
9
|
-
*
|
|
10
|
-
* switch that already governs
|
|
7
|
+
* `store.*`, `vault.secret`, `signal()`, `clock()`, `gate.policy` / `.scope`
|
|
8
|
+
* / `.rate`, and medium-binder `.template()` push into a shared registry
|
|
9
|
+
* (`src/kernel/element-registries.ts`) at call time — zero change to how
|
|
10
|
+
* developers call them. `oke()` drains it at construction under the same
|
|
11
|
+
* `registry: "consume" | "keep" | "ignore"` switch that already governs
|
|
12
|
+
* bindings.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import { describe, expect, test } from "bun:test";
|
|
14
16
|
import { channel } from "../elements/channel/declare.ts";
|
|
17
|
+
import { clock } from "../elements/clock/declare.ts";
|
|
18
|
+
import { gate } from "../elements/gate/declare.ts";
|
|
15
19
|
import { signal } from "../elements/signal/declare.ts";
|
|
16
20
|
import { store } from "../elements/store/declare.ts";
|
|
17
21
|
import { vault } from "../elements/vault/declare.ts";
|
|
@@ -20,7 +24,7 @@ import { flow } from "./flow.ts";
|
|
|
20
24
|
import { on, resetBindings } from "./on.ts";
|
|
21
25
|
import { http } from "./triggers.ts";
|
|
22
26
|
|
|
23
|
-
describe("oke() auto-registry — stores/secrets/signals/channel.templates/ai", () => {
|
|
27
|
+
describe("oke() auto-registry — stores/secrets/signals/clocks/gates/channel.templates/ai", () => {
|
|
24
28
|
test("before/after: zero explicit arrays boots identically to the explicit-array form", async () => {
|
|
25
29
|
resetBindings();
|
|
26
30
|
|
|
@@ -161,6 +165,42 @@ describe("oke() auto-registry — stores/secrets/signals/channel.templates/ai",
|
|
|
161
165
|
expect(app.bootResult?.vault?.contracts.has("IGNORED_SECRET")).toBeFalsy();
|
|
162
166
|
});
|
|
163
167
|
|
|
168
|
+
test("store.kv / store.index / clock() / gate.policy auto-drain — no explicit oke() arrays", async () => {
|
|
169
|
+
resetBindings();
|
|
170
|
+
store.kv("drafts", { description: "Compose drafts" });
|
|
171
|
+
store.index("tasks", { description: "Task search" });
|
|
172
|
+
clock("daily-digest", { cron: "0 8 * * *", every: "1d", timezone: "UTC" });
|
|
173
|
+
const member = gate.policy("member", ({ auth }) => !!auth.verified);
|
|
174
|
+
gate.scope("task:write");
|
|
175
|
+
gate.rate({ max: 60, per: "1m", keyBy: "user" });
|
|
176
|
+
|
|
177
|
+
const ping = flow("auto.ping", {
|
|
178
|
+
do: () => ({ ok: true as const }),
|
|
179
|
+
});
|
|
180
|
+
on(http.get("/auto-ping").gate(member), ping);
|
|
181
|
+
|
|
182
|
+
const app = oke({
|
|
183
|
+
name: "auto-kv-clock-gate",
|
|
184
|
+
autoBoot: false,
|
|
185
|
+
startScheduler: false,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(app.$options.stores?.some((s) => s.ref === "kv:drafts")).toBe(true);
|
|
189
|
+
expect(app.$options.stores?.some((s) => s.ref === "index:tasks")).toBe(true);
|
|
190
|
+
expect(app.$options.clocks?.some((c) => c.name === "daily-digest")).toBe(true);
|
|
191
|
+
expect(app.$options.gate?.policies?.some((g) => "name" in g && g.name === "member")).toBe(true);
|
|
192
|
+
expect(app.$options.gate?.policies?.some((g) => "name" in g && g.name === "task:write")).toBe(
|
|
193
|
+
true,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
await app.boot({ env: "test" });
|
|
197
|
+
expect(app.bootResult?.store?.declarations.has("kv:drafts")).toBe(true);
|
|
198
|
+
expect(app.bootResult?.store?.declarations.has("index:tasks")).toBe(true);
|
|
199
|
+
expect(app.bootResult?.clock?.declarations.has("daily-digest")).toBe(true);
|
|
200
|
+
expect(app.bootResult?.gate?.gates.has("member")).toBe(true);
|
|
201
|
+
expect(app.bootResult?.gate?.gates.has("task:write")).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
164
204
|
test('registry: "consume" (default) drains stores/secrets/signals/channel.templates — a later app does not inherit them', async () => {
|
|
165
205
|
store.sql("leak-app", { schema: {} });
|
|
166
206
|
vault.secret("LEAK_SECRET", { dev: "dev-leak" });
|
package/src/kernel/boot.test.ts
CHANGED
|
@@ -116,9 +116,9 @@ describe("boot — lazy element needs", () => {
|
|
|
116
116
|
if (raw.byteLength === 0) continue;
|
|
117
117
|
total += Bun.gzipSync(new Uint8Array(raw)).byteLength;
|
|
118
118
|
}
|
|
119
|
-
// Rebased after
|
|
120
|
-
// (~
|
|
121
|
-
expect(total).toBeLessThan(
|
|
119
|
+
// Rebased after clock / gate / kv / index auto-drain on `oke()`.
|
|
120
|
+
// (~48.3 kB gzip with export externals).
|
|
121
|
+
expect(total).toBeLessThan(49_000);
|
|
122
122
|
} finally {
|
|
123
123
|
await rm(dir, { recursive: true, force: true });
|
|
124
124
|
}
|
|
@@ -227,7 +227,7 @@ describe("boot — HTTP gate wiring on the default path", () => {
|
|
|
227
227
|
resetBindings();
|
|
228
228
|
resetFlowSeq();
|
|
229
229
|
on(
|
|
230
|
-
http.get("/ping").
|
|
230
|
+
http.get("/ping").public(),
|
|
231
231
|
flow("ping-public", {
|
|
232
232
|
do: () => ({ ok: true }),
|
|
233
233
|
}),
|
package/src/kernel/boot.ts
CHANGED
|
@@ -101,10 +101,10 @@ export interface BootOptions {
|
|
|
101
101
|
readonly gates?: readonly GateDecl[];
|
|
102
102
|
/**
|
|
103
103
|
* HTTP auth-posture enforcement at boot.
|
|
104
|
-
* - `"deny"` (default) — every HTTP trigger must carry a gate or `.
|
|
104
|
+
* - `"deny"` (default) — every HTTP trigger must carry a gate or `.public()`
|
|
105
105
|
* - `"allow"` — skips the audit **only** when {@link env} is `"test"`;
|
|
106
106
|
* ignored in local/prod/docker (never a production-wide bypass).
|
|
107
|
-
* Migrate real apps with per-trigger `.
|
|
107
|
+
* Migrate real apps with per-trigger `.public()`.
|
|
108
108
|
*/
|
|
109
109
|
readonly unguardedHttp?: "deny" | "allow";
|
|
110
110
|
/** Signal declarations. */
|