okengine 0.10.1 → 0.10.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/README.md +1 -1
- package/package.json +1 -1
- package/site/content/docs/elements/clock.mdx +2 -2
- package/site/content/docs/elements/flow.mdx +10 -10
- package/site/content/docs/elements/gate.mdx +4 -4
- package/site/content/docs/elements/signal.mdx +4 -7
- package/site/content/docs/elements/store.mdx +1 -1
- package/site/content/docs/elements/vault.mdx +1 -1
- package/site/content/docs/get-started/basic-usage.mdx +1 -3
- package/site/content/docs/get-started/introduction.mdx +1 -1
- package/site/content/docs/reference/cli.md +12 -12
- package/site/content/docs/reference/fx.mdx +3 -4
- 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/apply.ts +13 -0
- package/src/cli/build.test.ts +67 -0
- package/src/cli/build.ts +34 -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 +2 -4
- package/src/cli/dev.ts +37 -16
- 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 +3 -3
- package/src/compiler/fixtures/raw/raw-unannotated.ts +2 -4
- 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 +85 -0
- 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/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/honor-config.test.ts +5 -5
- 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/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;
|
|
@@ -25,7 +25,7 @@ function get(ip?: string): Request {
|
|
|
25
25
|
|
|
26
26
|
describe("ipAllowlist plugin", () => {
|
|
27
27
|
test("allow list: listed IPs pass, others get 403 Forbidden", async () => {
|
|
28
|
-
on(http.get("/x"), flow(
|
|
28
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
29
29
|
const app = oke({ autoBoot: false, name: "ips" }).plug(ipAllowlist({ allow: ["203.0.113.7"] }));
|
|
30
30
|
|
|
31
31
|
const ok = await app.fetch(get("203.0.113.7"));
|
|
@@ -39,7 +39,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
test("deny list: blocked IPs get 403 ip_denied, others pass", async () => {
|
|
42
|
-
on(http.get("/x"), flow(
|
|
42
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
43
43
|
const app = oke({ autoBoot: false, name: "ips-deny" }).plug(
|
|
44
44
|
ipAllowlist({ deny: ["198.51.100.9"] }),
|
|
45
45
|
);
|
|
@@ -54,7 +54,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
test("deny wins over allow on overlap", 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: "ips-both" }).plug(
|
|
59
59
|
ipAllowlist({ allow: ["203.0.113.7"], deny: ["203.0.113.7"] }),
|
|
60
60
|
);
|
|
@@ -64,7 +64,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
test("XFF last hop is the client; a spoofed first hop cannot bypass allow", 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: "ips-xff" }).plug(
|
|
69
69
|
ipAllowlist({ allow: ["203.0.113.7"] }),
|
|
70
70
|
);
|
|
@@ -78,7 +78,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
test("spoofed first hop cannot bypass a deny rule", async () => {
|
|
81
|
-
on(http.get("/x"), flow(
|
|
81
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
82
82
|
const app = oke({ autoBoot: false, name: "ips-xff-deny" }).plug(
|
|
83
83
|
ipAllowlist({ deny: ["198.51.100.9"] }),
|
|
84
84
|
);
|
|
@@ -92,7 +92,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
test("trustedProxyDepth selects the hop behind N trusted proxies", async () => {
|
|
95
|
-
on(http.get("/x"), flow(
|
|
95
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
96
96
|
// Chain: spoofed, real-client, cdn-egress — depth 2 skips the nearest proxy hop.
|
|
97
97
|
const app = oke({ autoBoot: false, name: "ips-depth" }).plug(
|
|
98
98
|
ipAllowlist({ allow: ["203.0.113.7"], trustedProxyDepth: 2 }),
|
|
@@ -113,7 +113,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
test("missing header: denied when allow is set, permitted for deny-only", async () => {
|
|
116
|
-
on(http.get("/x"), flow(
|
|
116
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
117
117
|
const strict = oke({ autoBoot: false, name: "ips-missing-allow" }).plug(
|
|
118
118
|
ipAllowlist({ allow: ["203.0.113.7"] }),
|
|
119
119
|
);
|
|
@@ -123,7 +123,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
123
123
|
|
|
124
124
|
resetBindings();
|
|
125
125
|
resetFlowSeq();
|
|
126
|
-
on(http.get("/x"), flow(
|
|
126
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
127
127
|
|
|
128
128
|
const lax = oke({ autoBoot: false, name: "ips-missing-deny" }).plug(
|
|
129
129
|
ipAllowlist({ deny: ["198.51.100.9"] }),
|
|
@@ -133,7 +133,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
test("custom header name is honored", async () => {
|
|
136
|
-
on(http.get("/x"), flow(
|
|
136
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
137
137
|
const app = oke({ autoBoot: false, name: "ips-custom" }).plug(
|
|
138
138
|
ipAllowlist({ allow: ["203.0.113.7"], header: "x-real-ip" }),
|
|
139
139
|
);
|
|
@@ -107,9 +107,7 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
|
|
|
107
107
|
})
|
|
108
108
|
: magicLinkTemplate;
|
|
109
109
|
|
|
110
|
-
const request = flow({
|
|
111
|
-
name: "auth.requestMagicLink",
|
|
112
|
-
unit: "auth",
|
|
110
|
+
const request = flow("auth.requestMagicLink", {
|
|
113
111
|
plane: "user",
|
|
114
112
|
in: z.object({ email: z.string().min(3) }),
|
|
115
113
|
out: z.object({
|
|
@@ -144,9 +142,7 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
|
|
|
144
142
|
},
|
|
145
143
|
});
|
|
146
144
|
|
|
147
|
-
const verify = flow({
|
|
148
|
-
name: "auth.verifyMagicLink",
|
|
149
|
-
unit: "auth",
|
|
145
|
+
const verify = flow("auth.verifyMagicLink", {
|
|
150
146
|
plane: "user",
|
|
151
147
|
in: z.object({ token: z.string().min(1) }),
|
|
152
148
|
out: SessionTokensOut,
|
|
@@ -17,7 +17,7 @@ beforeEach(() => {
|
|
|
17
17
|
|
|
18
18
|
describe("maintenanceMode plugin", () => {
|
|
19
19
|
test("returns 503 with ServiceUnavailable envelope and Retry-After", async () => {
|
|
20
|
-
on(http.get("/x"), flow(
|
|
20
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
21
21
|
const app = oke({ autoBoot: false, name: "maint" }).plug(maintenanceMode({ retryAfter: 120 }));
|
|
22
22
|
|
|
23
23
|
const res = await app.fetch(new Request("http://localhost/x"));
|
|
@@ -35,7 +35,7 @@ describe("maintenanceMode plugin", () => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
test("enabled: false passes traffic through", async () => {
|
|
38
|
-
on(http.get("/x"), flow(
|
|
38
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
39
39
|
const app = oke({ autoBoot: false, name: "maint-off" }).plug(
|
|
40
40
|
maintenanceMode({ enabled: false }),
|
|
41
41
|
);
|
|
@@ -46,8 +46,8 @@ describe("maintenanceMode plugin", () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
test("allowPaths prefixes keep serving", async () => {
|
|
49
|
-
on(http.get("/health"), flow(
|
|
50
|
-
on(http.get("/x"), flow(
|
|
49
|
+
on(http.get("/health"), flow("health.get", { do: () => ({ up: true }) }));
|
|
50
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
51
51
|
const app = oke({ autoBoot: false, name: "maint-allow" }).plug(
|
|
52
52
|
maintenanceMode({ allowPaths: ["/health"] }),
|
|
53
53
|
);
|
|
@@ -60,7 +60,7 @@ describe("maintenanceMode plugin", () => {
|
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
test("bypass header lets operators through (any non-empty value)", async () => {
|
|
63
|
-
on(http.get("/x"), flow(
|
|
63
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
64
64
|
const app = oke({ autoBoot: false, name: "maint-bypass" }).plug(
|
|
65
65
|
maintenanceMode({ bypassHeader: "x-ops-token" }),
|
|
66
66
|
);
|
|
@@ -75,7 +75,7 @@ describe("maintenanceMode plugin", () => {
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
test("the 503 still flows through onResponse (other plugins stamp it)", async () => {
|
|
78
|
-
on(http.get("/x"), flow(
|
|
78
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
79
79
|
const app = oke({ autoBoot: false, name: "maint-stamp" }).plug(maintenanceMode());
|
|
80
80
|
app.hook("onResponse", (ctx) => {
|
|
81
81
|
if (!ctx.response) return;
|
package/src/plugins/otp.ts
CHANGED
|
@@ -236,9 +236,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
236
236
|
});
|
|
237
237
|
|
|
238
238
|
if (opts.mode === "provider") {
|
|
239
|
-
const request = flow({
|
|
240
|
-
name: "auth.requestOtp",
|
|
241
|
-
unit: "auth",
|
|
239
|
+
const request = flow("auth.requestOtp", {
|
|
242
240
|
plane: "user",
|
|
243
241
|
in: z.object({
|
|
244
242
|
phone: z.string().min(8),
|
|
@@ -277,9 +275,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
277
275
|
},
|
|
278
276
|
});
|
|
279
277
|
|
|
280
|
-
const verify = flow({
|
|
281
|
-
name: "auth.verifyOtp",
|
|
282
|
-
unit: "auth",
|
|
278
|
+
const verify = flow("auth.verifyOtp", {
|
|
283
279
|
plane: "user",
|
|
284
280
|
in: z.object({
|
|
285
281
|
phone: z.string().min(8),
|
|
@@ -347,9 +343,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
347
343
|
}
|
|
348
344
|
|
|
349
345
|
// ── App mode ───────────────────────────────────────────────────────────
|
|
350
|
-
const request = flow({
|
|
351
|
-
name: "auth.requestOtp",
|
|
352
|
-
unit: "auth",
|
|
346
|
+
const request = flow("auth.requestOtp", {
|
|
353
347
|
plane: "user",
|
|
354
348
|
in: z.object({
|
|
355
349
|
email: z.string().min(3).optional(),
|
|
@@ -415,9 +409,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
415
409
|
},
|
|
416
410
|
});
|
|
417
411
|
|
|
418
|
-
const resend = flow({
|
|
419
|
-
name: "auth.resendOtp",
|
|
420
|
-
unit: "auth",
|
|
412
|
+
const resend = flow("auth.resendOtp", {
|
|
421
413
|
plane: "user",
|
|
422
414
|
in: z.object({
|
|
423
415
|
email: z.string().min(3).optional(),
|
|
@@ -486,9 +478,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
486
478
|
},
|
|
487
479
|
});
|
|
488
480
|
|
|
489
|
-
const verify = flow({
|
|
490
|
-
name: "auth.verifyOtp",
|
|
491
|
-
unit: "auth",
|
|
481
|
+
const verify = flow("auth.verifyOtp", {
|
|
492
482
|
plane: "user",
|
|
493
483
|
in: z.object({
|
|
494
484
|
email: z.string().min(3).optional(),
|
package/src/plugins/passkey.ts
CHANGED
|
@@ -87,9 +87,7 @@ export function passkey(opts: PasskeyOptions = {}): PluginDef {
|
|
|
87
87
|
const rpId = opts.rpId ?? "localhost";
|
|
88
88
|
const origins = opts.origins ?? ["http://localhost", "https://localhost"];
|
|
89
89
|
|
|
90
|
-
const registerOptions = flow({
|
|
91
|
-
name: "auth.passkeyRegisterOptions",
|
|
92
|
-
unit: "auth",
|
|
90
|
+
const registerOptions = flow("auth.passkeyRegisterOptions", {
|
|
93
91
|
plane: "user",
|
|
94
92
|
out: z.object({
|
|
95
93
|
challenge: z.string(),
|
|
@@ -115,9 +113,7 @@ export function passkey(opts: PasskeyOptions = {}): PluginDef {
|
|
|
115
113
|
},
|
|
116
114
|
});
|
|
117
115
|
|
|
118
|
-
const register = flow({
|
|
119
|
-
name: "auth.passkeyRegister",
|
|
120
|
-
unit: "auth",
|
|
116
|
+
const register = flow("auth.passkeyRegister", {
|
|
121
117
|
plane: "user",
|
|
122
118
|
in: CeremonyIn.extend({
|
|
123
119
|
publicKey: z.string().min(1),
|
|
@@ -171,9 +167,7 @@ export function passkey(opts: PasskeyOptions = {}): PluginDef {
|
|
|
171
167
|
},
|
|
172
168
|
});
|
|
173
169
|
|
|
174
|
-
const authenticateOptions = flow({
|
|
175
|
-
name: "auth.passkeyAuthenticateOptions",
|
|
176
|
-
unit: "auth",
|
|
170
|
+
const authenticateOptions = flow("auth.passkeyAuthenticateOptions", {
|
|
177
171
|
plane: "user",
|
|
178
172
|
in: z.object({ email: z.string().optional() }),
|
|
179
173
|
out: z.object({
|
|
@@ -199,9 +193,7 @@ export function passkey(opts: PasskeyOptions = {}): PluginDef {
|
|
|
199
193
|
},
|
|
200
194
|
});
|
|
201
195
|
|
|
202
|
-
const authenticate = flow({
|
|
203
|
-
name: "auth.passkeyAuthenticate",
|
|
204
|
-
unit: "auth",
|
|
196
|
+
const authenticate = flow("auth.passkeyAuthenticate", {
|
|
205
197
|
plane: "user",
|
|
206
198
|
in: CeremonyIn.extend({
|
|
207
199
|
challenge: z.string().min(1),
|
|
@@ -59,9 +59,7 @@ export function twoFactor(opts: TwoFactorOptions = {}): PluginDef {
|
|
|
59
59
|
const factors = opts.factors ?? createTwoFactorStore();
|
|
60
60
|
const issuer = opts.issuer ?? "oke";
|
|
61
61
|
|
|
62
|
-
const enable = flow({
|
|
63
|
-
name: "auth.twoFactorEnable",
|
|
64
|
-
unit: "auth",
|
|
62
|
+
const enable = flow("auth.twoFactorEnable", {
|
|
65
63
|
plane: "user",
|
|
66
64
|
out: z.object({
|
|
67
65
|
secret: z.string(),
|
|
@@ -91,9 +89,7 @@ export function twoFactor(opts: TwoFactorOptions = {}): PluginDef {
|
|
|
91
89
|
},
|
|
92
90
|
});
|
|
93
91
|
|
|
94
|
-
const verify = flow({
|
|
95
|
-
name: "auth.twoFactorVerify",
|
|
96
|
-
unit: "auth",
|
|
92
|
+
const verify = flow("auth.twoFactorVerify", {
|
|
97
93
|
plane: "user",
|
|
98
94
|
in: z.object({
|
|
99
95
|
userId: z.string().min(1),
|
|
@@ -127,9 +123,7 @@ export function twoFactor(opts: TwoFactorOptions = {}): PluginDef {
|
|
|
127
123
|
},
|
|
128
124
|
});
|
|
129
125
|
|
|
130
|
-
const disable = flow({
|
|
131
|
-
name: "auth.twoFactorDisable",
|
|
132
|
-
unit: "auth",
|
|
126
|
+
const disable = flow("auth.twoFactorDisable", {
|
|
133
127
|
plane: "user",
|
|
134
128
|
out: z.object({ ok: z.literal(true) }),
|
|
135
129
|
errors: { AuthFailed },
|
package/src/plugins/username.ts
CHANGED
|
@@ -319,9 +319,7 @@ export function username(opts: UsernamePluginOptions = {}): PluginDef {
|
|
|
319
319
|
resolveUsernamePolicy(usernamePolicy);
|
|
320
320
|
const crypto = createBunCrypto();
|
|
321
321
|
|
|
322
|
-
const signUp = flow({
|
|
323
|
-
name: "auth.signUpUsername",
|
|
324
|
-
unit: "auth",
|
|
322
|
+
const signUp = flow("auth.signUpUsername", {
|
|
325
323
|
plane: "user",
|
|
326
324
|
in: UsernameIn,
|
|
327
325
|
out: SessionTokensOut,
|
|
@@ -385,9 +383,7 @@ export function username(opts: UsernamePluginOptions = {}): PluginDef {
|
|
|
385
383
|
},
|
|
386
384
|
});
|
|
387
385
|
|
|
388
|
-
const signIn = flow({
|
|
389
|
-
name: "auth.signInUsername",
|
|
390
|
-
unit: "auth",
|
|
386
|
+
const signIn = flow("auth.signInUsername", {
|
|
391
387
|
plane: "user",
|
|
392
388
|
in: UsernameIn,
|
|
393
389
|
out: SessionTokensOut,
|
package/src/release/measure.ts
CHANGED
|
@@ -302,7 +302,7 @@ const { flow } = await import(${JSON.stringify(paths.flow)});
|
|
|
302
302
|
const { on, resetBindings } = await import(${JSON.stringify(paths.on)});
|
|
303
303
|
const { http } = await import(${JSON.stringify(paths.http)});
|
|
304
304
|
resetBindings();
|
|
305
|
-
on(http.get("/ping"), flow(
|
|
305
|
+
on(http.get("/ping"), flow("ping", { do: () => ({ ok: true }) }));
|
|
306
306
|
const app = oke({ name: "cold-start" });
|
|
307
307
|
const rt = createBunRuntime();
|
|
308
308
|
const server = rt.serve(app, { port: 0, hostname: "127.0.0.1" });
|
package/src/runs/runs.test.ts
CHANGED
|
@@ -59,9 +59,7 @@ describe("zero-instrumentation dimensions", () => {
|
|
|
59
59
|
|
|
60
60
|
on(
|
|
61
61
|
http.get("/book").gate(member),
|
|
62
|
-
flow({
|
|
63
|
-
name: "book.create",
|
|
64
|
-
unit: "bookings",
|
|
62
|
+
flow("bookings.create", {
|
|
65
63
|
plane: "user",
|
|
66
64
|
effects: { reads: ["sql:bookings"] },
|
|
67
65
|
do: async (_input, fx) => {
|
|
@@ -92,7 +90,7 @@ describe("zero-instrumentation dimensions", () => {
|
|
|
92
90
|
const events = await runs.all();
|
|
93
91
|
expect(events.length).toBe(1);
|
|
94
92
|
const e = events[0]!;
|
|
95
|
-
expect(e.flow).toBe("
|
|
93
|
+
expect(e.flow).toBe("bookings.create");
|
|
96
94
|
expect(e.unit).toBe("bookings");
|
|
97
95
|
expect(e.trigger).toBe("http");
|
|
98
96
|
expect(e.plane).toBe("user");
|
|
@@ -103,7 +101,7 @@ describe("zero-instrumentation dimensions", () => {
|
|
|
103
101
|
expect(e.buildVersion).toBe("1.2.3");
|
|
104
102
|
expect(e.logs.length).toBe(1);
|
|
105
103
|
expect(e.logs[0]!.message).toBe("booking started");
|
|
106
|
-
expect(e.dimensions.flow).toBe("
|
|
104
|
+
expect(e.dimensions.flow).toBe("bookings.create");
|
|
107
105
|
expect(e.dimensions.tenant).toBe("org_a41");
|
|
108
106
|
expect(e.dimensions.cache).toBe("hit");
|
|
109
107
|
|
|
@@ -57,15 +57,13 @@ function rawHttp(port: number, payload: string): Promise<string> {
|
|
|
57
57
|
function buildApp() {
|
|
58
58
|
on(
|
|
59
59
|
http.get("/ping").gate(gate.public),
|
|
60
|
-
flow({
|
|
61
|
-
name: "ping",
|
|
60
|
+
flow("ping", {
|
|
62
61
|
do: () => ({ ok: true as const, n: 1 }),
|
|
63
62
|
}),
|
|
64
63
|
);
|
|
65
64
|
on(
|
|
66
65
|
http.get("/notes/:id").gate(gate.public),
|
|
67
|
-
flow({
|
|
68
|
-
name: "notes.get",
|
|
66
|
+
flow("notes.get", {
|
|
69
67
|
do: ({ id }: { id: string }) => ({ id }),
|
|
70
68
|
}),
|
|
71
69
|
);
|
|
@@ -37,9 +37,7 @@ describe("createTestApp — four-applications surface", () => {
|
|
|
37
37
|
|
|
38
38
|
on(
|
|
39
39
|
http.post("/orders").gate(member),
|
|
40
|
-
flow({
|
|
41
|
-
name: "orders.create",
|
|
42
|
-
unit: "orders",
|
|
40
|
+
flow("orders.create", {
|
|
43
41
|
in: z.object({ sku: z.string(), qty: z.number() }),
|
|
44
42
|
out: z.object({ id: z.string() }),
|
|
45
43
|
effects: { emits: ["order-placed"] },
|
|
@@ -53,8 +51,7 @@ describe("createTestApp — four-applications surface", () => {
|
|
|
53
51
|
|
|
54
52
|
on(
|
|
55
53
|
orderPlaced,
|
|
56
|
-
flow({
|
|
57
|
-
name: "orders.onPlaced",
|
|
54
|
+
flow("orders.onPlaced", {
|
|
58
55
|
effects: { sends: ["order-confirmed"] },
|
|
59
56
|
do: async ({ orderId }, fx) => {
|
|
60
57
|
await fx.send(orderConfirmed, {
|
|
@@ -67,8 +64,7 @@ describe("createTestApp — four-applications surface", () => {
|
|
|
67
64
|
|
|
68
65
|
on(
|
|
69
66
|
every("1h"),
|
|
70
|
-
flow({
|
|
71
|
-
name: "orders.expire",
|
|
67
|
+
flow("orders.expire", {
|
|
72
68
|
do: (_i, fx) => {
|
|
73
69
|
fx.log.info("expire");
|
|
74
70
|
},
|
|
@@ -48,8 +48,7 @@ describe("Provisions integration", () => {
|
|
|
48
48
|
}),
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
const chargeOrder = flow({
|
|
52
|
-
name: "payments.chargeOrder",
|
|
51
|
+
const chargeOrder = flow("payments.chargeOrder", {
|
|
53
52
|
durable: true,
|
|
54
53
|
in: z.object({ orderId: z.string() }),
|
|
55
54
|
out: z.boolean(),
|
|
@@ -69,9 +68,7 @@ describe("Provisions integration", () => {
|
|
|
69
68
|
|
|
70
69
|
on(
|
|
71
70
|
http.post("/orders").gate(member, canOrder),
|
|
72
|
-
flow({
|
|
73
|
-
name: "orders.create",
|
|
74
|
-
unit: "orders",
|
|
71
|
+
flow("orders.create", {
|
|
75
72
|
in: z.object({ sku: z.string(), qty: z.number() }),
|
|
76
73
|
out: z.object({ id: z.string() }),
|
|
77
74
|
effects: { emits: ["order-placed"], calls: ["payments.chargeOrder"] },
|
|
@@ -85,8 +82,7 @@ describe("Provisions integration", () => {
|
|
|
85
82
|
|
|
86
83
|
on(
|
|
87
84
|
orderPlaced,
|
|
88
|
-
flow({
|
|
89
|
-
name: "orders.onPlaced",
|
|
85
|
+
flow("orders.onPlaced", {
|
|
90
86
|
effects: { calls: ["payments.chargeOrder"] },
|
|
91
87
|
do: async ({ orderId }, fx) => {
|
|
92
88
|
await fx.call(chargeOrder, { orderId });
|
|
@@ -96,8 +92,7 @@ describe("Provisions integration", () => {
|
|
|
96
92
|
|
|
97
93
|
on(
|
|
98
94
|
orderNews,
|
|
99
|
-
flow({
|
|
100
|
-
name: "notifications.onNews",
|
|
95
|
+
flow("notifications.onNews", {
|
|
101
96
|
effects: { sends: ["order-confirmed"] },
|
|
102
97
|
do: async ({ orderId, status }, fx) => {
|
|
103
98
|
if (status !== "confirmed") return;
|