okengine 0.10.1 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/site/content/docs/ai/mcp.mdx +0 -1
- package/site/content/docs/elements/ai.mdx +0 -6
- package/site/content/docs/elements/channel.mdx +0 -6
- package/site/content/docs/elements/clock.mdx +2 -8
- package/site/content/docs/elements/flow.mdx +10 -17
- package/site/content/docs/elements/gate.mdx +4 -10
- package/site/content/docs/elements/signal.mdx +4 -13
- package/site/content/docs/elements/store.mdx +9 -16
- package/site/content/docs/elements/vault.mdx +1 -7
- package/site/content/docs/get-started/basic-usage.mdx +1 -9
- package/site/content/docs/get-started/introduction.mdx +1 -1
- package/site/content/docs/get-started/why.mdx +1 -2
- package/site/content/docs/index.mdx +0 -1
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/reference/cli.md +12 -12
- package/site/content/docs/reference/fx.mdx +6 -7
- package/src/auth/auth.test.ts +1 -2
- package/src/auth/bindings.ts +5 -15
- package/src/auth/gate-auth.test.ts +2 -2
- package/src/cli/ai-setup/ai-setup.test.ts +3 -1
- package/src/cli/ai-setup/apply.ts +13 -0
- package/src/cli/build.test.ts +67 -0
- package/src/cli/build.ts +33 -0
- package/src/cli/db-seed.ts +1 -1
- package/src/cli/dev-controls.test.ts +43 -48
- package/src/cli/dev-controls.ts +23 -172
- package/src/cli/dev.test.ts +90 -5
- package/src/cli/dev.ts +39 -16
- package/src/cli/doctor.ts +98 -5
- package/src/cli/load-config.images.test.ts +31 -1
- package/src/cli/load-config.ts +2 -2
- package/src/cli/safe-defaults.test.ts +1 -1
- package/src/client/notes-contract.test.ts +3 -4
- package/src/compiler/aot.test.ts +1 -2
- package/src/compiler/extract.test.ts +76 -21
- package/src/compiler/extract.ts +15 -3
- package/src/compiler/fixtures/raw/raw-unannotated.ts +2 -4
- package/src/compiler/fixtures/skyport/oke.config.ts +4 -2
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +4 -8
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -2
- package/src/compiler/fixtures/skyport/src/flows/support/index.ts +1 -2
- package/src/compiler/fixtures/skyport.expected.json +3 -3
- package/src/compiler/fixtures/triggers/five-triggers.ts +5 -10
- package/src/compiler/generate-adopt.test.ts +85 -0
- package/src/compiler/generate-adopt.ts +111 -0
- package/src/config/driver-defaults.test.ts +122 -0
- package/src/config/driver-defaults.ts +186 -0
- package/src/config/index.ts +97 -7
- package/src/console/server/dry-run.audit.test.ts +2 -2
- package/src/console/server/flows.ts +50 -150
- package/src/docker/compose.ts +13 -3
- package/src/docker/images-config.test.ts +168 -0
- package/src/docker/recipes/llama-cpp.ts +10 -1
- package/src/docker/stack-id.ts +1 -0
- package/src/elements/channel/declare.ts +27 -1
- package/src/elements/clock/chaos-child.ts +3 -6
- package/src/elements/clock/chaos.test.ts +1 -2
- package/src/elements/clock.test.ts +2 -4
- package/src/elements/signal/declare.ts +25 -1
- package/src/elements/store/declare.ts +27 -1
- package/src/elements/store/resource.ts +6 -11
- package/src/elements/store/upsert-app.test.ts +1 -2
- package/src/elements/vault/declare.ts +29 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +103 -0
- package/src/kernel/app.ts +66 -9
- package/src/kernel/auto-registry.test.ts +198 -0
- package/src/kernel/boot-bind/channel.ts +12 -4
- package/src/kernel/boot-bind/clock.ts +3 -3
- package/src/kernel/boot-bind/gate.ts +7 -4
- package/src/kernel/boot-bind/honor-config.test.ts +5 -5
- package/src/kernel/boot-bind/journal.ts +3 -3
- package/src/kernel/boot-bind/signal.ts +3 -1
- package/src/kernel/boot-bind/store.test.ts +19 -0
- package/src/kernel/boot-bind/store.ts +24 -9
- package/src/kernel/boot-bind/vault.ts +3 -3
- package/src/kernel/boot.test.ts +19 -15
- package/src/kernel/boot.ts +81 -1
- package/src/kernel/call.test.ts +5 -10
- package/src/kernel/compensate.test.ts +3 -6
- package/src/kernel/concurrency.test.ts +3 -5
- package/src/kernel/correlation.test.ts +4 -8
- package/src/kernel/edge.test.ts +1 -1
- package/src/kernel/effects-stamping.test.ts +4 -7
- package/src/kernel/element-registries.ts +27 -0
- package/src/kernel/errors.ts +11 -0
- package/src/kernel/flow.test.ts +8 -15
- package/src/kernel/flow.ts +12 -14
- package/src/kernel/hook-timing.test.ts +2 -2
- package/src/kernel/hooks.test.ts +5 -10
- package/src/kernel/horizontal-child.ts +5 -10
- package/src/kernel/journal-boot.test.ts +2 -4
- package/src/kernel/pipeline.test.ts +6 -12
- package/src/kernel/plugin/capabilities.test.ts +1 -1
- package/src/kernel/plugin/decorate.test.ts +3 -8
- package/src/kernel/plugin/scoping.test.ts +6 -16
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +1 -2
- package/src/kernel/registry-isolation.test.ts +5 -5
- package/src/kernel/triggers.ts +1 -1
- package/src/plugins/anonymous.ts +1 -3
- package/src/plugins/compression.test.ts +6 -8
- package/src/plugins/config-source.test.ts +1 -1
- package/src/plugins/config-source.ts +1 -2
- package/src/plugins/cors.test.ts +10 -10
- package/src/plugins/csrf.test.ts +2 -2
- package/src/plugins/headers.test.ts +14 -15
- package/src/plugins/ip-allowlist.test.ts +9 -9
- package/src/plugins/magic-link.ts +2 -6
- package/src/plugins/maintenance-mode.test.ts +6 -6
- package/src/plugins/otp.ts +5 -15
- package/src/plugins/passkey.ts +4 -12
- package/src/plugins/two-factor.ts +3 -9
- package/src/plugins/username.ts +2 -6
- package/src/release/measure.ts +1 -1
- package/src/runs/runs.test.ts +3 -5
- package/src/runtime/serve.test.ts +2 -4
- package/src/test/create-test-app.test.ts +3 -7
- package/src/test/provisions.integration.test.ts +4 -9
- package/src/test/reset-element-registries.ts +31 -0
- package/site/content/docs/console/access.mdx +0 -29
- package/site/content/docs/console/ai.mdx +0 -35
- package/site/content/docs/console/architecture.mdx +0 -35
- package/site/content/docs/console/channels.mdx +0 -37
- package/site/content/docs/console/clock.mdx +0 -51
- package/site/content/docs/console/flows.mdx +0 -31
- package/site/content/docs/console/gates.mdx +0 -73
- package/site/content/docs/console/index.mdx +0 -54
- package/site/content/docs/console/manifest-diff.mdx +0 -34
- package/site/content/docs/console/meta.json +0 -24
- package/site/content/docs/console/overview.mdx +0 -40
- package/site/content/docs/console/plugins.mdx +0 -41
- package/site/content/docs/console/privacy.mdx +0 -32
- package/site/content/docs/console/runs.mdx +0 -50
- package/site/content/docs/console/signals.mdx +0 -40
- package/site/content/docs/console/store.mdx +0 -32
- package/site/content/docs/console/tenancy.mdx +0 -32
- package/site/content/docs/console/traces.mdx +0 -38
- package/site/content/docs/console/vault.mdx +0 -41
|
@@ -131,16 +131,13 @@ const storeRt = createStoreRuntime({
|
|
|
131
131
|
});
|
|
132
132
|
storeRt.register(db);
|
|
133
133
|
|
|
134
|
-
const ping = flow({
|
|
135
|
-
name: "horizontal.ping",
|
|
134
|
+
const ping = flow("horizontal.ping", {
|
|
136
135
|
do: () => ({ ok: true as const, instanceId }),
|
|
137
136
|
});
|
|
138
|
-
const rate = flow({
|
|
139
|
-
name: "horizontal.rate",
|
|
137
|
+
const rate = flow("horizontal.rate", {
|
|
140
138
|
do: () => ({ ok: true as const, instanceId }),
|
|
141
139
|
});
|
|
142
|
-
const write = flow({
|
|
143
|
-
name: "horizontal.write",
|
|
140
|
+
const write = flow("horizontal.write", {
|
|
144
141
|
do: async (_input, fx) => {
|
|
145
142
|
const id = crypto.randomUUID();
|
|
146
143
|
await fx
|
|
@@ -155,15 +152,13 @@ const write = flow({
|
|
|
155
152
|
return { id };
|
|
156
153
|
},
|
|
157
154
|
});
|
|
158
|
-
const emit = flow({
|
|
159
|
-
name: "horizontal.emit",
|
|
155
|
+
const emit = flow("horizontal.emit", {
|
|
160
156
|
do: async (_input, fx) => {
|
|
161
157
|
await fx.emit(SIG, { from: instanceId, at: Date.now() });
|
|
162
158
|
return { emitted: true as const };
|
|
163
159
|
},
|
|
164
160
|
});
|
|
165
|
-
const charge = flow({
|
|
166
|
-
name: "horizontal.charge",
|
|
161
|
+
const charge = flow("horizontal.charge", {
|
|
167
162
|
durable: true,
|
|
168
163
|
do: async (_input, fx) => {
|
|
169
164
|
await fx.step("create-intent", async () => {
|
|
@@ -73,8 +73,7 @@ async function waitFor(cond: () => Promise<boolean>, timeoutMs = 5_000): Promise
|
|
|
73
73
|
|
|
74
74
|
/** Flow whose step 2 blocks once per execution; the test unblocks survivors. */
|
|
75
75
|
function blockingFlow(step1Calls: string[], blockers: Array<() => void>): Binding {
|
|
76
|
-
const charge = flow({
|
|
77
|
-
name: "charge",
|
|
76
|
+
const charge = flow("charge", {
|
|
78
77
|
durable: true,
|
|
79
78
|
do: async (_input, fx) => {
|
|
80
79
|
await fx.step("create-intent", () => {
|
|
@@ -96,8 +95,7 @@ function blockingFlow(step1Calls: string[], blockers: Array<() => void>): Bindin
|
|
|
96
95
|
|
|
97
96
|
/** Flow that sleeps `wake`, then records one confirm execution. */
|
|
98
97
|
function sleeperBinding(wake: string, confirms: string[]): Binding {
|
|
99
|
-
const sleeper = flow({
|
|
100
|
-
name: "sleeper",
|
|
98
|
+
const sleeper = flow("sleeper", {
|
|
101
99
|
durable: true,
|
|
102
100
|
do: async (_input, fx) => {
|
|
103
101
|
await fx.step("create-intent", () => ({ id: "pi_1" }));
|
|
@@ -72,8 +72,7 @@ describe("pipeline — Unauthorized for anonymous", () => {
|
|
|
72
72
|
|
|
73
73
|
on(
|
|
74
74
|
http.post("/orders").gate(member),
|
|
75
|
-
flow({
|
|
76
|
-
name: "orders.create",
|
|
75
|
+
flow("orders.create", {
|
|
77
76
|
in: z.object({ sku: z.string() }),
|
|
78
77
|
out: z.object({ id: z.string() }),
|
|
79
78
|
do: (_input, fx) => ({ id: fx.id() }),
|
|
@@ -111,8 +110,7 @@ describe("pipeline — Unauthorized for anonymous", () => {
|
|
|
111
110
|
|
|
112
111
|
on(
|
|
113
112
|
http.post("/orders").gate(member, canOrder),
|
|
114
|
-
flow({
|
|
115
|
-
name: "orders.create2",
|
|
113
|
+
flow("orders.create2", {
|
|
116
114
|
in: z.object({ sku: z.string() }),
|
|
117
115
|
do: () => ({ ok: true }),
|
|
118
116
|
}),
|
|
@@ -150,8 +148,7 @@ describe("pipeline — evaluated gates on the run", () => {
|
|
|
150
148
|
|
|
151
149
|
on(
|
|
152
150
|
http.post("/orders").gate(member, canOrder),
|
|
153
|
-
flow({
|
|
154
|
-
name: "orders.create3",
|
|
151
|
+
flow("orders.create3", {
|
|
155
152
|
in: z.object({ sku: z.string() }),
|
|
156
153
|
do: () => ({ ok: true }),
|
|
157
154
|
}),
|
|
@@ -203,8 +200,7 @@ describe("pipeline — Bearer cryptographic verification", () => {
|
|
|
203
200
|
|
|
204
201
|
on(
|
|
205
202
|
http.get("/secure").gate(member),
|
|
206
|
-
flow({
|
|
207
|
-
name: "secure.get",
|
|
203
|
+
flow("secure.get", {
|
|
208
204
|
do: () => ({ ok: true }),
|
|
209
205
|
}),
|
|
210
206
|
);
|
|
@@ -242,8 +238,7 @@ describe("pipeline — Bearer cryptographic verification", () => {
|
|
|
242
238
|
|
|
243
239
|
on(
|
|
244
240
|
http.get("/secure").gate(member),
|
|
245
|
-
flow({
|
|
246
|
-
name: "secure.get2",
|
|
241
|
+
flow("secure.get2", {
|
|
247
242
|
do: () => ({ ok: true }),
|
|
248
243
|
}),
|
|
249
244
|
);
|
|
@@ -313,8 +308,7 @@ describe("pipeline — Bearer cryptographic verification", () => {
|
|
|
313
308
|
|
|
314
309
|
on(
|
|
315
310
|
http.post("/orders").gate(member),
|
|
316
|
-
flow({
|
|
317
|
-
name: "orders.inject",
|
|
311
|
+
flow("orders.inject", {
|
|
318
312
|
in: z.object({ sku: z.string() }),
|
|
319
313
|
do: () => ({ ok: true }),
|
|
320
314
|
}),
|
|
@@ -25,7 +25,7 @@ describe("plugin capability capture", () => {
|
|
|
25
25
|
.table("audit_events")
|
|
26
26
|
.driver("postgres")
|
|
27
27
|
.image("store.sql", "postgres:16")
|
|
28
|
-
.flow(flow(
|
|
28
|
+
.flow(flow("audit.flush", { do: () => undefined }))
|
|
29
29
|
.client("audit", { subscribe: true });
|
|
30
30
|
|
|
31
31
|
const { api, snapshot } = createRecordingApi({
|
|
@@ -27,8 +27,7 @@ describe("plugin decorate — types and runtime", () => {
|
|
|
27
27
|
let seen: unknown;
|
|
28
28
|
on(
|
|
29
29
|
http.get("/x"),
|
|
30
|
-
flow({
|
|
31
|
-
name: "x",
|
|
30
|
+
flow("x", {
|
|
32
31
|
do: (_input, _fx) => {
|
|
33
32
|
return { ok: true };
|
|
34
33
|
},
|
|
@@ -75,9 +74,7 @@ describe("plugin decorate — types and runtime", () => {
|
|
|
75
74
|
|
|
76
75
|
on(
|
|
77
76
|
http.get("/orders"),
|
|
78
|
-
flow({
|
|
79
|
-
name: "orders.list",
|
|
80
|
-
unit: "orders",
|
|
77
|
+
flow("orders.list", {
|
|
81
78
|
do: () => ({}),
|
|
82
79
|
}).hook("beforeHandle", (ctx) => {
|
|
83
80
|
seen.orders = ctx.decorations.flag;
|
|
@@ -85,9 +82,7 @@ describe("plugin decorate — types and runtime", () => {
|
|
|
85
82
|
);
|
|
86
83
|
on(
|
|
87
84
|
http.get("/payments"),
|
|
88
|
-
flow({
|
|
89
|
-
name: "payments.list",
|
|
90
|
-
unit: "payments",
|
|
85
|
+
flow("payments.list", {
|
|
91
86
|
do: () => ({}),
|
|
92
87
|
}).hook("beforeHandle", (ctx) => {
|
|
93
88
|
seen.payments = ctx.decorations.flag;
|
|
@@ -20,18 +20,14 @@ describe("plugin scoping — attachment point is the scope", () => {
|
|
|
20
20
|
test("unit-scoped plugin hook does not fire for another unit's flows", async () => {
|
|
21
21
|
const fired: string[] = [];
|
|
22
22
|
|
|
23
|
-
const ordersFlow = flow({
|
|
24
|
-
name: "orders.create",
|
|
25
|
-
unit: "orders",
|
|
23
|
+
const ordersFlow = flow("orders.create", {
|
|
26
24
|
do: () => {
|
|
27
25
|
fired.push("orders:handler");
|
|
28
26
|
return { ok: true };
|
|
29
27
|
},
|
|
30
28
|
});
|
|
31
29
|
|
|
32
|
-
const paymentsFlow = flow({
|
|
33
|
-
name: "payments.charge",
|
|
34
|
-
unit: "payments",
|
|
30
|
+
const paymentsFlow = flow("payments.charge", {
|
|
35
31
|
do: () => {
|
|
36
32
|
fired.push("payments:handler");
|
|
37
33
|
return { ok: true };
|
|
@@ -62,9 +58,7 @@ describe("plugin scoping — attachment point is the scope", () => {
|
|
|
62
58
|
|
|
63
59
|
on(
|
|
64
60
|
http.get("/a"),
|
|
65
|
-
flow({
|
|
66
|
-
name: "a",
|
|
67
|
-
unit: "alpha",
|
|
61
|
+
flow("alpha.a", {
|
|
68
62
|
do: () => {
|
|
69
63
|
fired.push("a");
|
|
70
64
|
return {};
|
|
@@ -73,9 +67,7 @@ describe("plugin scoping — attachment point is the scope", () => {
|
|
|
73
67
|
);
|
|
74
68
|
on(
|
|
75
69
|
http.get("/b"),
|
|
76
|
-
flow({
|
|
77
|
-
name: "b",
|
|
78
|
-
unit: "beta",
|
|
70
|
+
flow("beta.b", {
|
|
79
71
|
do: () => {
|
|
80
72
|
fired.push("b");
|
|
81
73
|
return {};
|
|
@@ -98,8 +90,7 @@ describe("plugin scoping — attachment point is the scope", () => {
|
|
|
98
90
|
test("flow-scoped plugin hook fires only for that flow", async () => {
|
|
99
91
|
const fired: string[] = [];
|
|
100
92
|
|
|
101
|
-
const marked = flow({
|
|
102
|
-
name: "marked",
|
|
93
|
+
const marked = flow("marked", {
|
|
103
94
|
do: () => {
|
|
104
95
|
fired.push("marked");
|
|
105
96
|
return {};
|
|
@@ -110,8 +101,7 @@ describe("plugin scoping — attachment point is the scope", () => {
|
|
|
110
101
|
}),
|
|
111
102
|
);
|
|
112
103
|
|
|
113
|
-
const plain = flow({
|
|
114
|
-
name: "plain",
|
|
104
|
+
const plain = flow("plain", {
|
|
115
105
|
do: () => {
|
|
116
106
|
fired.push("plain");
|
|
117
107
|
return {};
|
|
@@ -32,7 +32,7 @@ describe("plugin element contributions", () => {
|
|
|
32
32
|
.table("otp_codes")
|
|
33
33
|
.needs("store.sql");
|
|
34
34
|
|
|
35
|
-
on(http.get("/ok").gate(gate.public), flow(
|
|
35
|
+
on(http.get("/ok").gate(gate.public), flow("ok", { do: () => ({ ok: true }) }));
|
|
36
36
|
|
|
37
37
|
const app = oke({
|
|
38
38
|
name: "plugin-elements",
|
|
@@ -75,7 +75,7 @@ describe("plugin .needs() resolution", () => {
|
|
|
75
75
|
test("boot fails when plugin needs missing peer plugin", async () => {
|
|
76
76
|
resetBindings();
|
|
77
77
|
resetFlowSeq();
|
|
78
|
-
on(http.get("/x").gate(gate.public), flow(
|
|
78
|
+
on(http.get("/x").gate(gate.public), flow("x", { do: () => ({ ok: true }) }));
|
|
79
79
|
const dependent = plugin("needs-auth", { version: "0.0.1" }).needs("auth");
|
|
80
80
|
const app = oke({ name: "needs-boot", gate: { policies: [gate.public] } }).plug(dependent);
|
|
81
81
|
await expect(app.boot({ env: "test" })).rejects.toThrow(PluginNeedsError);
|
package/src/kernel/ready.test.ts
CHANGED
|
@@ -33,8 +33,7 @@ describe("GET /_/ready", () => {
|
|
|
33
33
|
test("orphan_scan then ready when durable journal is bound", async () => {
|
|
34
34
|
const journalStore = createMemoryJournalStore();
|
|
35
35
|
expect(hasJournalLease(journalStore)).toBe(true);
|
|
36
|
-
const charge = flow({
|
|
37
|
-
name: "charge",
|
|
36
|
+
const charge = flow("charge", {
|
|
38
37
|
durable: true,
|
|
39
38
|
do: () => ({ ok: true }),
|
|
40
39
|
});
|
|
@@ -21,11 +21,11 @@ afterEach(() => {
|
|
|
21
21
|
|
|
22
22
|
describe("registry isolation", () => {
|
|
23
23
|
test("consume (default): App B does not serve App A routes", async () => {
|
|
24
|
-
on(http.get("/a"), flow(
|
|
24
|
+
on(http.get("/a"), flow("app-a.ping", { do: () => "a" }));
|
|
25
25
|
const appA = oke({ autoBoot: false, name: "app-a" });
|
|
26
26
|
|
|
27
27
|
// No resetBindings() between the apps — the pre-fix leak scenario.
|
|
28
|
-
on(http.get("/b"), flow(
|
|
28
|
+
on(http.get("/b"), flow("app-b.ping", { do: () => "b" }));
|
|
29
29
|
const appB = oke({ autoBoot: false, name: "app-b" });
|
|
30
30
|
|
|
31
31
|
// Pre-fix this was 200: App B adopted App A's leftover binding.
|
|
@@ -45,7 +45,7 @@ describe("registry isolation", () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test("keep: today's behavior — registry survives construction", async () => {
|
|
48
|
-
on(http.get("/a"), flow(
|
|
48
|
+
on(http.get("/a"), flow("kept.ping", { do: () => "a" }));
|
|
49
49
|
oke({ autoBoot: false, name: "app-a", registry: "keep" });
|
|
50
50
|
expect(listBindings()).toHaveLength(1);
|
|
51
51
|
|
|
@@ -54,11 +54,11 @@ describe("registry isolation", () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
test("ignore: only options.bindings are served", async () => {
|
|
57
|
-
on(http.get("/stray"), flow(
|
|
57
|
+
on(http.get("/stray"), flow("stray.ping", { do: () => "stray" }));
|
|
58
58
|
|
|
59
59
|
const onlyB: Binding = {
|
|
60
60
|
trigger: http.get("/b"),
|
|
61
|
-
flow: flow(
|
|
61
|
+
flow: flow("only-b.ping", { do: () => "b" }),
|
|
62
62
|
};
|
|
63
63
|
const app = oke({ autoBoot: false, name: "app-b", registry: "ignore", bindings: [onlyB] });
|
|
64
64
|
|
package/src/kernel/triggers.ts
CHANGED
|
@@ -323,7 +323,7 @@ export function table(name: string, store?: string): TableHandle {
|
|
|
323
323
|
|
|
324
324
|
/**
|
|
325
325
|
* Explicit internal / call-only trigger.
|
|
326
|
-
* Prefer an untriggered `flow({…})` when no trigger value is needed.
|
|
326
|
+
* Prefer an untriggered `flow(name, {…})` when no trigger value is needed.
|
|
327
327
|
*/
|
|
328
328
|
export const internal: InternalTrigger = { kind: "internal" };
|
|
329
329
|
|
package/src/plugins/anonymous.ts
CHANGED
|
@@ -30,9 +30,7 @@ export interface AnonymousPluginOptions extends AuthMethodOptions {
|
|
|
30
30
|
export function anonymous(opts: AnonymousPluginOptions = {}): PluginDef {
|
|
31
31
|
const runtime = createMethodRuntime(opts);
|
|
32
32
|
|
|
33
|
-
const signIn = flow({
|
|
34
|
-
name: "auth.signInAnonymous",
|
|
35
|
-
unit: "auth",
|
|
33
|
+
const signIn = flow("auth.signInAnonymous", {
|
|
36
34
|
plane: "user",
|
|
37
35
|
out: SessionTokensOut,
|
|
38
36
|
errors: { AuthFailed, AuthRateLimited },
|
|
@@ -37,7 +37,7 @@ describe("acceptsGzip", () => {
|
|
|
37
37
|
|
|
38
38
|
describe("compression plugin", () => {
|
|
39
39
|
test("gzips a large JSON body when the client accepts gzip", async () => {
|
|
40
|
-
on(http.get("/big"), flow(
|
|
40
|
+
on(http.get("/big"), flow("big.get", { do: bigPayload }));
|
|
41
41
|
const app = oke({ autoBoot: false, name: "zip" }).plug(compression());
|
|
42
42
|
|
|
43
43
|
const res = await app.fetch(
|
|
@@ -58,7 +58,7 @@ describe("compression plugin", () => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
test("passes through untouched without Accept-Encoding: gzip", async () => {
|
|
61
|
-
on(http.get("/big"), flow(
|
|
61
|
+
on(http.get("/big"), flow("big.get", { do: bigPayload }));
|
|
62
62
|
const app = oke({ autoBoot: false, name: "zip-plain" }).plug(compression());
|
|
63
63
|
|
|
64
64
|
const res = await app.fetch(new Request("http://localhost/big"));
|
|
@@ -69,7 +69,7 @@ describe("compression plugin", () => {
|
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
test("skips bodies under minSize", async () => {
|
|
72
|
-
on(http.get("/small"), flow(
|
|
72
|
+
on(http.get("/small"), flow("small.get", { do: () => ({ ok: true }) }));
|
|
73
73
|
const app = oke({ autoBoot: false, name: "zip-small" }).plug(compression());
|
|
74
74
|
|
|
75
75
|
const res = await app.fetch(
|
|
@@ -80,7 +80,7 @@ describe("compression plugin", () => {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
test("minSize: 0 compresses even tiny bodies", async () => {
|
|
83
|
-
on(http.get("/small"), flow(
|
|
83
|
+
on(http.get("/small"), flow("small.get", { do: () => ({ ok: true }) }));
|
|
84
84
|
const app = oke({ autoBoot: false, name: "zip-zero" }).plug(compression({ minSize: 0 }));
|
|
85
85
|
|
|
86
86
|
const res = await app.fetch(
|
|
@@ -93,8 +93,7 @@ describe("compression plugin", () => {
|
|
|
93
93
|
test("skips non-matching content types and no-transform responses", async () => {
|
|
94
94
|
on(
|
|
95
95
|
http.get("/img"),
|
|
96
|
-
flow({
|
|
97
|
-
name: "img.get",
|
|
96
|
+
flow("img.get", {
|
|
98
97
|
do: () =>
|
|
99
98
|
new Response(new Uint8Array(4096), {
|
|
100
99
|
headers: { "content-type": "image/png" },
|
|
@@ -103,8 +102,7 @@ describe("compression plugin", () => {
|
|
|
103
102
|
);
|
|
104
103
|
on(
|
|
105
104
|
http.get("/nt"),
|
|
106
|
-
flow({
|
|
107
|
-
name: "nt.get",
|
|
105
|
+
flow("nt.get", {
|
|
108
106
|
do: () =>
|
|
109
107
|
Response.json(
|
|
110
108
|
{ data: bigPayload(), error: null },
|
|
@@ -155,7 +155,7 @@ describe("configSource — plugin wiring", () => {
|
|
|
155
155
|
kv: cfg,
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
on(http.get("/x"), flow(
|
|
158
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
159
159
|
const app = oke({ autoBoot: false, name: "cfg-live" }).plug(maintenanceMode(source));
|
|
160
160
|
|
|
161
161
|
expect((await app.fetch(new Request("http://localhost/x"))).status).toBe(200);
|
|
@@ -116,8 +116,7 @@ export function configSource<T extends object>(options: ConfigSourceOptions<T>):
|
|
|
116
116
|
}
|
|
117
117
|
if (options.db !== undefined) reads.push(options.db.store.ref);
|
|
118
118
|
|
|
119
|
-
return flow({
|
|
120
|
-
name: `${options.plugin}.config-sync`,
|
|
119
|
+
return flow(`${options.plugin}.config-sync`, {
|
|
121
120
|
effects: { reads, writes },
|
|
122
121
|
do: async (_input, fx) => {
|
|
123
122
|
if (options.kv !== undefined) {
|
package/src/plugins/cors.test.ts
CHANGED
|
@@ -28,7 +28,7 @@ function preflight(path = "/x", headers: Record<string, string> = {}): Request {
|
|
|
28
28
|
|
|
29
29
|
describe("cors plugin — preflight", () => {
|
|
30
30
|
test("answers preflight for a path bound to another method (edge)", async () => {
|
|
31
|
-
on(http.get("/x"), flow(
|
|
31
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
32
32
|
const app = oke({ autoBoot: false, name: "cors-pre" }).plug(
|
|
33
33
|
cors({ origin: "https://app.example.com" }),
|
|
34
34
|
);
|
|
@@ -42,7 +42,7 @@ describe("cors plugin — preflight", () => {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
test("denied origins get a 204 without CORS headers — the browser blocks quietly", async () => {
|
|
45
|
-
on(http.get("/x"), flow(
|
|
45
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
46
46
|
const app = oke({ autoBoot: false, name: "cors-deny" }).plug(
|
|
47
47
|
cors({ origin: "https://other.example.com" }),
|
|
48
48
|
);
|
|
@@ -54,7 +54,7 @@ describe("cors plugin — preflight", () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
test("no origin configured answers nothing — cross-origin closed by default", async () => {
|
|
57
|
-
on(http.get("/x"), flow(
|
|
57
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
58
58
|
const app = oke({ autoBoot: false, name: "cors-closed" }).plug(cors());
|
|
59
59
|
|
|
60
60
|
const res = await app.fetch(preflight());
|
|
@@ -62,7 +62,7 @@ describe("cors plugin — preflight", () => {
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test("a plain unmatched OPTIONS without preflight headers still 404s", async () => {
|
|
65
|
-
on(http.get("/x"), flow(
|
|
65
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
66
66
|
const app = oke({ autoBoot: false, name: "cors-404" }).plug(cors({ origin: "*" }));
|
|
67
67
|
|
|
68
68
|
const res = await app.fetch(new Request("http://localhost/x", { method: "OPTIONS" }));
|
|
@@ -70,7 +70,7 @@ describe("cors plugin — preflight", () => {
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
test("reflects request headers by default, honors configured lists and maxAge", async () => {
|
|
73
|
-
on(http.get("/x"), flow(
|
|
73
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
74
74
|
const app = oke({ autoBoot: false, name: "cors-hdr" }).plug(
|
|
75
75
|
cors({ origin: "*", maxAge: 600, allowedHeaders: ["x-custom"] }),
|
|
76
76
|
);
|
|
@@ -91,7 +91,7 @@ describe("cors plugin — preflight", () => {
|
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
test("allowlist + credentials reflects only listed origins", async () => {
|
|
94
|
-
on(http.get("/x"), flow(
|
|
94
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
95
95
|
const app = oke({ autoBoot: false, name: "cors-cred" }).plug(
|
|
96
96
|
cors({ origin: ["https://app.example.com", "https://admin.example.com"], credentials: true }),
|
|
97
97
|
);
|
|
@@ -109,7 +109,7 @@ describe("cors plugin — preflight", () => {
|
|
|
109
109
|
|
|
110
110
|
describe("cors plugin — actual requests", () => {
|
|
111
111
|
test("allowed origins get allow-origin (+ exposed headers) on matched responses", async () => {
|
|
112
|
-
on(http.get("/x"), flow(
|
|
112
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
113
113
|
const app = oke({ autoBoot: false, name: "cors-actual" }).plug(
|
|
114
114
|
cors({ origin: ["https://app.example.com"], exposedHeaders: ["x-total"] }),
|
|
115
115
|
);
|
|
@@ -124,7 +124,7 @@ describe("cors plugin — actual requests", () => {
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
test("allowlist + credentials on matched responses reflects only listed origins", async () => {
|
|
127
|
-
on(http.get("/x"), flow(
|
|
127
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
128
128
|
const app = oke({ autoBoot: false, name: "cors-actual-cred" }).plug(
|
|
129
129
|
cors({ origin: ["https://app.example.com"], credentials: true }),
|
|
130
130
|
);
|
|
@@ -145,7 +145,7 @@ describe("cors plugin — actual requests", () => {
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
test("denied origins get no CORS headers on matched responses", async () => {
|
|
148
|
-
on(http.get("/x"), flow(
|
|
148
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
149
149
|
const app = oke({ autoBoot: false, name: "cors-actual-deny" }).plug(
|
|
150
150
|
cors({ origin: "https://ok.example.com" }),
|
|
151
151
|
);
|
|
@@ -159,7 +159,7 @@ describe("cors plugin — actual requests", () => {
|
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
test("requests without an Origin header are untouched", async () => {
|
|
162
|
-
on(http.get("/x"), flow(
|
|
162
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
163
163
|
const app = oke({ autoBoot: false, name: "cors-no-origin" }).plug(cors({ origin: "*" }));
|
|
164
164
|
|
|
165
165
|
const res = await app.fetch(new Request("http://localhost/x"));
|
package/src/plugins/csrf.test.ts
CHANGED
|
@@ -20,8 +20,8 @@ function mutating(headers: Record<string, string> = {}): Request {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function appWith(options: Parameters<typeof csrf>[0]) {
|
|
23
|
-
on(http.post("/transfer"), flow(
|
|
24
|
-
on(http.get("/balance"), flow(
|
|
23
|
+
on(http.post("/transfer"), flow("transfer.create", { do: () => ({ moved: true }) }));
|
|
24
|
+
on(http.get("/balance"), flow("balance.get", { do: () => ({ balance: 5 }) }));
|
|
25
25
|
return oke({ autoBoot: false, name: `csrf-${crypto.randomUUID()}` }).plug(csrf(options));
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -18,7 +18,7 @@ beforeEach(() => {
|
|
|
18
18
|
|
|
19
19
|
describe("headers plugin", () => {
|
|
20
20
|
test("sets the default trio on a successful response", async () => {
|
|
21
|
-
on(http.get("/x"), flow(
|
|
21
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
22
22
|
const app = oke({ autoBoot: false, name: "sec" }).plug(headers());
|
|
23
23
|
|
|
24
24
|
const res = await app.fetch(new Request("http://localhost/x"));
|
|
@@ -31,7 +31,7 @@ describe("headers plugin", () => {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
test("helmet-parity defaults: agent cluster, dns prefetch, download, cross-domain, xss filter", async () => {
|
|
34
|
-
on(http.get("/x"), flow(
|
|
34
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
35
35
|
const app = oke({ autoBoot: false, name: "sec-parity" }).plug(headers());
|
|
36
36
|
|
|
37
37
|
const res = await app.fetch(new Request("http://localhost/x"));
|
|
@@ -44,7 +44,7 @@ describe("headers plugin", () => {
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
test("each parity default can be switched off", async () => {
|
|
47
|
-
on(http.get("/x"), flow(
|
|
47
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
48
48
|
const app = oke({ autoBoot: false, name: "sec-off" }).plug(
|
|
49
49
|
headers({
|
|
50
50
|
originAgentCluster: false,
|
|
@@ -64,7 +64,7 @@ describe("headers plugin", () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
test("dnsPrefetchControl allow, custom cross-domain policy, COEP stamps when configured", async () => {
|
|
67
|
-
on(http.get("/x"), flow(
|
|
67
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
68
68
|
const app = oke({ autoBoot: false, name: "sec-tuned" }).plug(
|
|
69
69
|
headers({
|
|
70
70
|
dnsPrefetchControl: { allow: true },
|
|
@@ -84,8 +84,7 @@ describe("headers plugin", () => {
|
|
|
84
84
|
const poweredByApp = (options: Parameters<typeof headers>[0], name: string) => {
|
|
85
85
|
on(
|
|
86
86
|
http.get("/x"),
|
|
87
|
-
flow({
|
|
88
|
-
name: "x.get",
|
|
87
|
+
flow("x.get", {
|
|
89
88
|
do: () =>
|
|
90
89
|
new Response("{}", {
|
|
91
90
|
headers: { "content-type": "application/json", "x-powered-by": "Express" },
|
|
@@ -111,7 +110,7 @@ describe("headers plugin", () => {
|
|
|
111
110
|
});
|
|
112
111
|
|
|
113
112
|
test("structured CSP merges over helmet's defaults, camelCase keys, report-only mode", async () => {
|
|
114
|
-
on(http.get("/x"), flow(
|
|
113
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
115
114
|
const app = oke({ autoBoot: false, name: "sec-csp-builder" }).plug(
|
|
116
115
|
headers({
|
|
117
116
|
contentSecurityPolicy: {
|
|
@@ -131,7 +130,7 @@ describe("headers plugin", () => {
|
|
|
131
130
|
});
|
|
132
131
|
|
|
133
132
|
test("structured CSP with useDefaults: false emits exactly the given directives", async () => {
|
|
134
|
-
on(http.get("/x"), flow(
|
|
133
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
135
134
|
const app = oke({ autoBoot: false, name: "sec-csp-bare" }).plug(
|
|
136
135
|
headers({
|
|
137
136
|
contentSecurityPolicy: { useDefaults: false, directives: { "default-src": ["'none'"] } },
|
|
@@ -151,7 +150,7 @@ describe("headers plugin", () => {
|
|
|
151
150
|
test("headers land on failures too (onResponse runs after onError)", async () => {
|
|
152
151
|
on(
|
|
153
152
|
http.get("/deny"),
|
|
154
|
-
flow(
|
|
153
|
+
flow("x.deny", { do: () => ({ ok: true }) }).hook("beforeHandle", () =>
|
|
155
154
|
fail("Forbidden", {}),
|
|
156
155
|
),
|
|
157
156
|
);
|
|
@@ -164,7 +163,7 @@ describe("headers plugin", () => {
|
|
|
164
163
|
});
|
|
165
164
|
|
|
166
165
|
test("adds CSP only when configured", async () => {
|
|
167
|
-
on(http.get("/x"), flow(
|
|
166
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
168
167
|
const app = oke({ autoBoot: false, name: "sec-csp" }).plug(
|
|
169
168
|
headers({ contentSecurityPolicy: "default-src 'self'" }),
|
|
170
169
|
);
|
|
@@ -175,7 +174,7 @@ describe("headers plugin", () => {
|
|
|
175
174
|
});
|
|
176
175
|
|
|
177
176
|
test("HSTS is off by default", async () => {
|
|
178
|
-
on(http.get("/x"), flow(
|
|
177
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
179
178
|
const app = oke({ autoBoot: false, name: "sec-hsts-off" }).plug(headers());
|
|
180
179
|
|
|
181
180
|
const res = await app.fetch(new Request("http://localhost/x"));
|
|
@@ -184,7 +183,7 @@ describe("headers plugin", () => {
|
|
|
184
183
|
});
|
|
185
184
|
|
|
186
185
|
test("hsts: true stamps a one-year max-age", async () => {
|
|
187
|
-
on(http.get("/x"), flow(
|
|
186
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
188
187
|
const app = oke({ autoBoot: false, name: "sec-hsts-on" }).plug(headers({ hsts: true }));
|
|
189
188
|
|
|
190
189
|
const res = await app.fetch(new Request("http://localhost/x"));
|
|
@@ -193,7 +192,7 @@ describe("headers plugin", () => {
|
|
|
193
192
|
});
|
|
194
193
|
|
|
195
194
|
test("hsts object tunes max-age, subdomains, preload", async () => {
|
|
196
|
-
on(http.get("/x"), flow(
|
|
195
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
197
196
|
const app = oke({ autoBoot: false, name: "sec-hsts-tuned" }).plug(
|
|
198
197
|
headers({ hsts: { maxAge: 63072000, includeSubDomains: true, preload: true } }),
|
|
199
198
|
);
|
|
@@ -206,7 +205,7 @@ describe("headers plugin", () => {
|
|
|
206
205
|
});
|
|
207
206
|
|
|
208
207
|
test("permissions-policy and cross-origin policies stamp when configured", async () => {
|
|
209
|
-
on(http.get("/x"), flow(
|
|
208
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
210
209
|
const app = oke({ autoBoot: false, name: "sec-extra" }).plug(
|
|
211
210
|
headers({
|
|
212
211
|
permissionsPolicy: "camera=(), microphone=()",
|
|
@@ -223,7 +222,7 @@ describe("headers plugin", () => {
|
|
|
223
222
|
});
|
|
224
223
|
|
|
225
224
|
test("an explicit app-set value wins by default", async () => {
|
|
226
|
-
on(http.get("/x"), flow(
|
|
225
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
227
226
|
const app = oke({ autoBoot: false, name: "sec-keep" });
|
|
228
227
|
app.hook("onResponse", (ctx) => {
|
|
229
228
|
if (!ctx.response) return;
|