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/kernel/flow.test.ts
CHANGED
|
@@ -11,8 +11,7 @@ beforeEach(() => {
|
|
|
11
11
|
|
|
12
12
|
describe("flow — one species", () => {
|
|
13
13
|
test("flow() returns a branded FlowDef", () => {
|
|
14
|
-
const f = flow({
|
|
15
|
-
name: "ping",
|
|
14
|
+
const f = flow("ping", {
|
|
16
15
|
do: () => ({ ok: true }),
|
|
17
16
|
});
|
|
18
17
|
expect(isFlow(f)).toBe(true);
|
|
@@ -22,7 +21,7 @@ describe("flow — one species", () => {
|
|
|
22
21
|
});
|
|
23
22
|
|
|
24
23
|
test("on() returns the same flow object and records the trigger", () => {
|
|
25
|
-
const f = flow(
|
|
24
|
+
const f = flow("create", { do: () => 1 });
|
|
26
25
|
const bound = on(http.post("/notes"), f);
|
|
27
26
|
// Same runtime object; trigger stamp is type-only so identities differ at the type level.
|
|
28
27
|
expect(bound as object).toBe(f);
|
|
@@ -33,8 +32,7 @@ describe("flow — one species", () => {
|
|
|
33
32
|
|
|
34
33
|
test("the same flow object is reachable from HTTP and signal triggers", async () => {
|
|
35
34
|
const calls: string[] = [];
|
|
36
|
-
const shared = flow({
|
|
37
|
-
name: "shared.work",
|
|
35
|
+
const shared = flow("shared.work", {
|
|
38
36
|
do: (input: { n: number }) => {
|
|
39
37
|
calls.push(`n=${input.n}`);
|
|
40
38
|
return { n: input.n * 2 };
|
|
@@ -65,8 +63,7 @@ describe("five trigger kinds", () => {
|
|
|
65
63
|
|
|
66
64
|
const httpFlow = on(
|
|
67
65
|
http.get("/:code"),
|
|
68
|
-
flow({
|
|
69
|
-
name: "t.http",
|
|
66
|
+
flow("t.http", {
|
|
70
67
|
do: ({ code }: { code: string }) => {
|
|
71
68
|
seen.push(`http:${code}`);
|
|
72
69
|
return { code };
|
|
@@ -76,8 +73,7 @@ describe("five trigger kinds", () => {
|
|
|
76
73
|
|
|
77
74
|
on(
|
|
78
75
|
every("1h"),
|
|
79
|
-
flow({
|
|
80
|
-
name: "t.every",
|
|
76
|
+
flow("t.every", {
|
|
81
77
|
do: () => {
|
|
82
78
|
seen.push("every");
|
|
83
79
|
},
|
|
@@ -86,8 +82,7 @@ describe("five trigger kinds", () => {
|
|
|
86
82
|
|
|
87
83
|
on(
|
|
88
84
|
{ name: "link-clicked", delivery: "once" },
|
|
89
|
-
flow({
|
|
90
|
-
name: "t.signal",
|
|
85
|
+
flow("t.signal", {
|
|
91
86
|
do: ({ code }: { code: string }) => {
|
|
92
87
|
seen.push(`signal:${code}`);
|
|
93
88
|
},
|
|
@@ -96,8 +91,7 @@ describe("five trigger kinds", () => {
|
|
|
96
91
|
|
|
97
92
|
on(
|
|
98
93
|
table("orders").changed("status"),
|
|
99
|
-
flow({
|
|
100
|
-
name: "t.cdc",
|
|
94
|
+
flow("t.cdc", {
|
|
101
95
|
do: (p: { before: { status: string }; after: { status: string } }) => {
|
|
102
96
|
seen.push(`cdc:${p.before.status}->${p.after.status}`);
|
|
103
97
|
},
|
|
@@ -106,8 +100,7 @@ describe("five trigger kinds", () => {
|
|
|
106
100
|
|
|
107
101
|
on(
|
|
108
102
|
internal,
|
|
109
|
-
flow({
|
|
110
|
-
name: "t.internal",
|
|
103
|
+
flow("t.internal", {
|
|
111
104
|
do: () => {
|
|
112
105
|
seen.push("internal");
|
|
113
106
|
return { ok: true };
|
package/src/kernel/flow.ts
CHANGED
|
@@ -38,10 +38,6 @@ export type FlowHandler<I = unknown, O = unknown> = (
|
|
|
38
38
|
|
|
39
39
|
/** Options for {@link flow}. */
|
|
40
40
|
export interface FlowOptions<I = unknown, O = unknown, E extends FlowErrorMap = FlowErrorMap> {
|
|
41
|
-
/** Optional stable name (used by `fx.call` and the Manifest). */
|
|
42
|
-
readonly name?: string;
|
|
43
|
-
/** Optional unit scope for unit-level hooks. */
|
|
44
|
-
readonly unit?: string;
|
|
45
41
|
/** Input schema (Standard Schema when present). */
|
|
46
42
|
readonly in?: SchemaInput;
|
|
47
43
|
/** Output schema (Standard Schema when present). */
|
|
@@ -155,7 +151,7 @@ export interface FlowDef<
|
|
|
155
151
|
readonly [triggerPhantom]?: T;
|
|
156
152
|
/** Stable name (auto-assigned when omitted). */
|
|
157
153
|
readonly name: string;
|
|
158
|
-
/**
|
|
154
|
+
/** Unit scope — derived from `name`'s first dot segment (e.g. `"auth.refresh"` → `"auth"`). */
|
|
159
155
|
readonly unit: string | undefined;
|
|
160
156
|
/** Input schema. */
|
|
161
157
|
readonly in: SchemaInput | undefined;
|
|
@@ -231,28 +227,30 @@ export interface FlowDef<
|
|
|
231
227
|
/** Unique brand symbol for {@link FlowDef}. */
|
|
232
228
|
export const flowBrand: unique symbol = Symbol("oke.flow");
|
|
233
229
|
|
|
234
|
-
let flowSeq = 0;
|
|
235
|
-
|
|
236
230
|
/**
|
|
237
231
|
* Define a Flow — the one species of backend behavior.
|
|
238
232
|
*
|
|
239
233
|
* Input / output types are inferred from Standard Schema `in` / `out` when
|
|
240
234
|
* present; otherwise from the `do` handler signature.
|
|
241
235
|
*
|
|
236
|
+
* @param name - Stable name (used by `fx.call` and the Manifest)
|
|
242
237
|
* @param options - Contract and handler
|
|
243
238
|
*/
|
|
244
239
|
export function flow<Opts extends FlowOptions<any, any, any>>(
|
|
245
|
-
|
|
240
|
+
name: string,
|
|
241
|
+
options: Opts & { readonly name?: never },
|
|
246
242
|
): FlowDef<InferFlowIn<Opts>, InferFlowOut<Opts>, InferFlowErrors<Opts>> {
|
|
247
|
-
const name = options.name ?? `flow_${++flowSeq}`;
|
|
248
243
|
const triggers: Trigger[] = [];
|
|
249
244
|
const hooks: Partial<Record<HookStage, HookFn[]>> = {};
|
|
250
245
|
const pendingPlugins: PluginDef[] = [];
|
|
251
246
|
|
|
247
|
+
const dot = name.indexOf(".");
|
|
248
|
+
const unit = dot > 0 ? name.slice(0, dot) : undefined;
|
|
249
|
+
|
|
252
250
|
const def: FlowDef<InferFlowIn<Opts>, InferFlowOut<Opts>, InferFlowErrors<Opts>> = {
|
|
253
251
|
[flowBrand]: true,
|
|
254
252
|
name,
|
|
255
|
-
unit
|
|
253
|
+
unit,
|
|
256
254
|
in: options.in,
|
|
257
255
|
out: options.out,
|
|
258
256
|
errors: (options.errors ?? undefined) as InferFlowErrors<Opts> | undefined,
|
|
@@ -316,10 +314,10 @@ export function isFlow(value: unknown): value is AnyFlowDef {
|
|
|
316
314
|
}
|
|
317
315
|
|
|
318
316
|
/**
|
|
319
|
-
*
|
|
317
|
+
* No-op. `name` is a required positional argument now, so there is no
|
|
318
|
+
* auto-name sequence left to reset — kept so existing test `beforeEach`
|
|
319
|
+
* blocks don't need touching.
|
|
320
320
|
*
|
|
321
321
|
* @internal
|
|
322
322
|
*/
|
|
323
|
-
export function resetFlowSeq(): void {
|
|
324
|
-
flowSeq = 0;
|
|
325
|
-
}
|
|
323
|
+
export function resetFlowSeq(): void {}
|
|
@@ -27,7 +27,7 @@ describe("hook timing", () => {
|
|
|
27
27
|
});
|
|
28
28
|
const ctx: InvocationContext = {
|
|
29
29
|
trigger: { kind: "internal" },
|
|
30
|
-
flow: flow(
|
|
30
|
+
flow: flow("t", { do: () => ({ ok: true }) }),
|
|
31
31
|
input: undefined,
|
|
32
32
|
params: {},
|
|
33
33
|
state: {},
|
|
@@ -56,7 +56,7 @@ describe("hook timing", () => {
|
|
|
56
56
|
const hooks = registry.hooksAt("app", undefined, "x");
|
|
57
57
|
const ctx: InvocationContext = {
|
|
58
58
|
trigger: { kind: "internal" },
|
|
59
|
-
flow: flow(
|
|
59
|
+
flow: flow("x", { do: () => undefined }),
|
|
60
60
|
input: undefined,
|
|
61
61
|
params: {},
|
|
62
62
|
state: {},
|
package/src/kernel/hooks.test.ts
CHANGED
|
@@ -40,7 +40,7 @@ describe("hooks — order and composition", () => {
|
|
|
40
40
|
void fn(
|
|
41
41
|
{
|
|
42
42
|
trigger: { kind: "internal" },
|
|
43
|
-
flow: flow(
|
|
43
|
+
flow: flow("x", { do: () => undefined }),
|
|
44
44
|
input: undefined,
|
|
45
45
|
params: {},
|
|
46
46
|
state: {},
|
|
@@ -57,9 +57,7 @@ describe("hooks — order and composition", () => {
|
|
|
57
57
|
test("pipeline fires stages in order around the handler", async () => {
|
|
58
58
|
const order: string[] = [];
|
|
59
59
|
|
|
60
|
-
const f = flow({
|
|
61
|
-
name: "ordered",
|
|
62
|
-
unit: "links",
|
|
60
|
+
const f = flow("links.ordered", {
|
|
63
61
|
do: () => {
|
|
64
62
|
order.push("handler");
|
|
65
63
|
return { ok: true };
|
|
@@ -119,8 +117,7 @@ describe("hooks — order and composition", () => {
|
|
|
119
117
|
|
|
120
118
|
on(
|
|
121
119
|
http.get("/sc"),
|
|
122
|
-
flow({
|
|
123
|
-
name: "sc",
|
|
120
|
+
flow("sc", {
|
|
124
121
|
do: () => {
|
|
125
122
|
order.push("handler");
|
|
126
123
|
return { ok: true };
|
|
@@ -151,8 +148,7 @@ describe("hooks — order and composition", () => {
|
|
|
151
148
|
|
|
152
149
|
on(
|
|
153
150
|
http.get("/data"),
|
|
154
|
-
flow({
|
|
155
|
-
name: "data",
|
|
151
|
+
flow("data", {
|
|
156
152
|
do: () => ({ ok: true }),
|
|
157
153
|
}),
|
|
158
154
|
);
|
|
@@ -184,8 +180,7 @@ describe("hooks — order and composition", () => {
|
|
|
184
180
|
|
|
185
181
|
on(
|
|
186
182
|
http.get("/fail"),
|
|
187
|
-
flow({
|
|
188
|
-
name: "fail-flow",
|
|
183
|
+
flow("fail-flow", {
|
|
189
184
|
do: () => {
|
|
190
185
|
order.push("handler");
|
|
191
186
|
},
|
|
@@ -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) {
|