@shivaedev/test-story 0.2.0 → 0.3.0
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/CHANGELOG.md +14 -0
- package/README.md +77 -2
- package/dist/server/domEnvironment.config.d.ts +4 -0
- package/dist/server/domEnvironment.config.d.ts.map +1 -0
- package/dist/server/domEnvironment.config.js +39 -0
- package/dist/server/domEnvironment.config.js.map +1 -0
- package/dist/server/domFetch.d.ts +2 -0
- package/dist/server/domFetch.d.ts.map +1 -0
- package/dist/server/domFetch.js +7 -0
- package/dist/server/domFetch.js.map +1 -0
- package/dist/server/internal/hookInference.d.ts +28 -0
- package/dist/server/internal/hookInference.d.ts.map +1 -0
- package/dist/server/internal/hookInference.js +2 -0
- package/dist/server/internal/hookInference.js.map +1 -0
- package/dist/server/internal/hooked.d.ts +4 -1
- package/dist/server/internal/hooked.d.ts.map +1 -1
- package/dist/server/internal/hooked.js +3 -0
- package/dist/server/internal/hooked.js.map +1 -1
- package/dist/server/internal/routeOutbound.d.ts +3 -0
- package/dist/server/internal/routeOutbound.d.ts.map +1 -0
- package/dist/server/internal/routeOutbound.js +42 -0
- package/dist/server/internal/routeOutbound.js.map +1 -0
- package/dist/server/internal/seed.d.ts +4 -3
- package/dist/server/internal/seed.d.ts.map +1 -1
- package/dist/server/internal/seed.js.map +1 -1
- package/dist/server/internal/storyIt.d.ts.map +1 -1
- package/dist/server/internal/storyIt.js +8 -1
- package/dist/server/internal/storyIt.js.map +1 -1
- package/dist/server/storyKit.d.ts +11 -11
- package/dist/server/storyKit.d.ts.map +1 -1
- package/dist/server/storyKit.js.map +1 -1
- package/docs/roadmap.md +1 -0
- package/package.json +3 -3
- package/src/server/domEnvironment.config.ts +40 -0
- package/src/server/domFetch.ts +6 -0
- package/src/server/internal/hookInference.ts +46 -0
- package/src/server/internal/hooked.ts +11 -3
- package/src/server/internal/routeOutbound.ts +54 -0
- package/src/server/internal/seed.ts +4 -3
- package/src/server/internal/storyIt.ts +10 -1
- package/src/server/storyKit.ts +32 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-10-17
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `@shivaedev/test-story/domEnvironment.config.ts` exports a Vitest environment with Node's global `Request`, `Response`, `Headers` and abort classes for an in-process HTTP host, while happy-dom fetch keeps the client's cookie jar. Fetch accepts native headers and request inputs. Better Auth sign-up, sign-in, session reads and sign-out are tested against the starter's real PostgreSQL host, including its auth client and client response inspection.
|
|
8
|
+
- `domFetch(baseUrl)` from `@shivaedev/test-story/domFetch.ts` sets the happy-dom page origin to its host and resolves relative fetch requests.
|
|
9
|
+
- `storyKit` accepts `outbound: Readonly<Record<string, string>>` to route selected hostnames from Node's built-in `fetch` to fake server origins for each story, then restore the previous dispatcher after success or failure. Other hosts pass through unchanged.
|
|
10
|
+
|
|
11
|
+
## 0.2.1 - 2026-10-16
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Let `create`, stage `after` hooks and traits return an Effect directly, with inferred engine types, checked service requirements and the story's Scope, alongside generators and synchronous functions.
|
|
16
|
+
|
|
3
17
|
## 0.2.0 - 2026-10-13
|
|
4
18
|
|
|
5
19
|
### Breaking
|
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ An application has an **engine**: the real thing a test drives, such as a bakery
|
|
|
47
47
|
|
|
48
48
|
Every story belongs to a **genre**, the kind of engine it runs. The kit names each of its tests after its genre, such as `Bakery Story: steps until the condition holds`, so a report shows which engine a test runs and one run can pick the bakery's stories and leave the others.
|
|
49
49
|
|
|
50
|
-
The package runs on Effect and Vitest, but an engine needs neither: every hook, trait and test body may be a plain function.
|
|
50
|
+
The package runs on Effect and Vitest, but an engine needs neither: every hook, trait and test body may be a plain function. Use a generator function to yield Effects when a hook needs a service or fails with a typed error; the kit runs it, so no definition needs `Effect.gen`. Creation, stage `after` hooks and traits may also be plain functions that return an Effect directly. The kit runs that Effect in the story's Scope, with the services from its layer.
|
|
51
51
|
|
|
52
52
|
The work splits into three layers, and each is written by a different hand at a different time:
|
|
53
53
|
|
|
@@ -107,6 +107,7 @@ This is the bakery kit this package tests itself with. `emptyBakery`, `stillWait
|
|
|
107
107
|
| Hook | Says |
|
|
108
108
|
| --- | --- |
|
|
109
109
|
| `name` | What the engine is called in failures, "the bakery", and the kit's genre, "Bakery Story". |
|
|
110
|
+
| `outbound` | Optional. A map of exact hostnames to fake server origins for the host's Node built-in `fetch`. |
|
|
110
111
|
| `around(effect)` | Optional. Wraps creation, staged seeding and the body; worker Layer acquisition remains outside it. It may require the worker services and per-test Scope. |
|
|
111
112
|
| `clock` | Optional. `"test"` by default, or `"live"` for HTTP and DOM stories. A story’s `options.clock` takes precedence. |
|
|
112
113
|
| `create()` | A fresh engine. Every story starts from a new one. |
|
|
@@ -130,6 +131,21 @@ export default defineConfig({
|
|
|
130
131
|
|
|
131
132
|
`genreTags(...kitNames)` declares one tag per kit, named after it: `bakery-story` and `mill-story` here, and `game-store-story` for a kit named "game store". A kit tags its tests only with a genre the config declares, because Vitest refuses a test whose tag the config leaves out, so a config without the line runs every story untagged and prints nothing about it. With the tags declared, `vitest run --tags-filter=mill-story` runs the mill's stories and skips the bakery's, and `--tags-filter` takes any of Vitest's tag expressions, such as `'!mill-story'`. A root config whose inline projects extend the package's config gets these tags from it, so `--tags-filter` works from the root too.
|
|
132
133
|
|
|
134
|
+
#### Routing outbound HTTP
|
|
135
|
+
|
|
136
|
+
When application code calls a hard-coded third-party URL, set `outbound` on its kit to send those requests to a fake server:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const app = storyKit({
|
|
140
|
+
...appDefinition,
|
|
141
|
+
outbound: { "github.com": fakeUrl, "api.github.com": fakeUrl },
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`appDefinition` holds the application's kit hooks, and `fakeUrl` is the listening fake server's HTTP or HTTPS origin, such as `http://127.0.0.1:4321`. The map matches exact URL hostnames, across ports. It replaces the destination origin while keeping the request's path, query, method, headers and body. For example, a POST to `https://github.com/login/oauth/access_token` reaches the fake's `/login/oauth/access_token`. Other hostnames pass through the previous dispatcher unchanged.
|
|
146
|
+
|
|
147
|
+
Routing uses the global dispatcher that Node's built-in `fetch` already uses. It is installed before `around`, creation, traits and the body, stays active during scoped cleanup, and restores the previous dispatcher after each story, including a failing story. Stories within one worker must run one at a time because that dispatcher is shared; parallelism belongs between workers or inside one story. Routing affects host-side Node fetches.
|
|
148
|
+
|
|
133
149
|
### 2. Once per feature: traits and verbs
|
|
134
150
|
|
|
135
151
|
A trait is a line and a change, tagged with a stage:
|
|
@@ -209,6 +225,8 @@ A spec starts a story, acts through verbs and checks what a user would see. When
|
|
|
209
225
|
|
|
210
226
|
### Running the engine
|
|
211
227
|
|
|
228
|
+
Hooks and steps that wait for real workers can use `withLive(Effect.sleep(...))` or `withLive(eventually(...))` from `@shivaedev/effect-test/withLive.ts` and `@shivaedev/effect-test/eventually.ts`. Both work under the kit's live clock and a story's TestClock override. The wrapped work uses live time without advancing the story's virtual time; outside it, the selected clock still applies. Unwrapped `eventually` uses the active clock and advances TestClock between attempts.
|
|
229
|
+
|
|
212
230
|
`story.runUntil(condition, { maxSteps })` returns an Effect that asks three questions before every step:
|
|
213
231
|
|
|
214
232
|
1. `run.failed` returns a sentence: it dies with it, even when the condition also holds.
|
|
@@ -268,7 +286,64 @@ Application-authored seed builders reuse application schemas, validate overrides
|
|
|
268
286
|
|
|
269
287
|
React screen traits acquire roots in Scope and use a fresh native atom registry for each screen. Cleanup unmounts roots before disposing registries and servers. Screen verbs dispatch DOM events and use bounded observations of rendered output. Select a live clock once on an HTTP/DOM kit; keep TestClock for deterministic local stories. `runUntil` is for engines with a meaningful step hook, not HTTP or DOM polling. Creation forms can use the same pattern with the existing `useCreate` path.
|
|
270
288
|
|
|
271
|
-
The example proves DOM and loopback HTTP behavior, not browser
|
|
289
|
+
The order example proves DOM and loopback HTTP behavior with bearer tokens. For cookie sessions, use the DOM host setup below. These fixtures do not prove browser networking, SSR, mobile or a deployed proxy. Native `RpcTest` can exercise handlers in faster local stories, but omits serialization and transport. Failure inspection is synchronous: keep redacted observed values in the engine instead of trying to query PostgreSQL inside `inspect`.
|
|
290
|
+
|
|
291
|
+
### DOM stories with cookie sessions
|
|
292
|
+
|
|
293
|
+
Install `happy-dom@20.14.6` as a dev dependency. Select the `@shivaedev/test-story/domEnvironment.config.ts` Vitest environment for DOM tests that start their host in the same process:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { fileURLToPath } from "node:url";
|
|
297
|
+
import { defineConfig } from "vitest/config";
|
|
298
|
+
|
|
299
|
+
export default defineConfig({
|
|
300
|
+
test: {
|
|
301
|
+
environment: fileURLToPath(import.meta.resolve("@shivaedev/test-story/domEnvironment.config.ts")),
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The environment runs happy-dom, then restores Node's global `Request`, `Response`, `Headers`, `AbortController` and `AbortSignal` for the host and client request construction. Better Auth can emit `Set-Cookie`, and the host can read incoming cookie headers. Fetch still runs through happy-dom's internal window and cookie jar. The environment translates Node `Headers` and `Request` inputs for that fetch, including requests made by Better Auth's client. Client responses remain happy-dom responses: inspect their status, headers and body methods rather than comparing them with the global Node constructors. The starter's [Vitest config](https://github.com/ShivaeDev/platform/blob/main/examples/starter/vitest.config.ts) selects this environment only for its DOM project while retaining the shared test projects. Use Vitest's standard forks or threads pool.
|
|
307
|
+
|
|
308
|
+
`domFetch(baseUrl)` from `@shivaedev/test-story/domFetch.ts` sets the current window's location to the real HTTP host and returns its bound fetch, resolving relative request strings against that URL. The window keeps and sends cookies, including HttpOnly session cookies. Keep the default `same-origin` credentials policy for this same-origin setup. Set the page origin before creating an auth client or rendering a screen; application code can use the global fetch of that same window.
|
|
309
|
+
|
|
310
|
+
Call it in the kit's `create` hook after starting the host:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { domFetch } from "@shivaedev/test-story/domFetch.ts";
|
|
314
|
+
|
|
315
|
+
const cookieApp = storyKit({
|
|
316
|
+
around: withIsolatedDatabase,
|
|
317
|
+
clock: "live",
|
|
318
|
+
*create() {
|
|
319
|
+
const { baseUrl } = yield* startHost();
|
|
320
|
+
return { fetch: domFetch(baseUrl) };
|
|
321
|
+
},
|
|
322
|
+
layer: postgres,
|
|
323
|
+
name: "cookie auth",
|
|
324
|
+
stages: ["account"],
|
|
325
|
+
verbs: ({ fetch }, story) => authVerbs(fetch, story.tell),
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`startHost`, `postgres` and `authVerbs` are application plumbing. The starter's [cookie application kit](https://github.com/ShivaeDev/platform/blob/main/examples/starter/src/test-support/cookieApp.ts) shows the full setup. Its Better Auth client signs up and signs in, and its verbs read `/api/auth/get-session` and post to `/api/auth/sign-out` through the DOM fetch. They do not copy cookies into request headers or use bearer tokens.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
cookieApp.it("sign-up and sign-in keep their cookie sessions against the PostgreSQL host", [], function* ({ signUp, signIn, signOut, sessionIs }) {
|
|
333
|
+
yield* signUp();
|
|
334
|
+
yield* sessionIs("alice@starter.test");
|
|
335
|
+
yield* signOut();
|
|
336
|
+
yield* sessionIs(null);
|
|
337
|
+
yield* signIn();
|
|
338
|
+
yield* sessionIs("alice@starter.test");
|
|
339
|
+
yield* signOut();
|
|
340
|
+
yield* sessionIs(null);
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
This story proves Better Auth cookie sessions across real loopback HTTP requests from happy-dom with an in-process PostgreSQL host. It also reads sessions through Better Auth's client and checks the fetch response's status, headers and JSON body.
|
|
345
|
+
|
|
346
|
+
`domFetch` uses the current window and its cookie jar; it does not isolate multiple users. Run stories using that window serially, and let the application's cleanup end its sessions, restore the page location and close its host. Cookies follow hostnames, not listener ports, so a different loopback port alone does not isolate accounts.
|
|
272
347
|
|
|
273
348
|
### Install and limits
|
|
274
349
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domEnvironment.config.d.ts","sourceRoot":"","sources":["../../src/server/domEnvironment.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEvE,QAAA,MAAM,cAAc,EAAE,WAmCrB,CAAC;eAEa,cAAc"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { builtinEnvironments } from "vitest/runtime";
|
|
2
|
+
const domEnvironment = {
|
|
3
|
+
name: "test-story-dom",
|
|
4
|
+
async setup(global, options) {
|
|
5
|
+
const { AbortController, AbortSignal, Headers, Request, Response } = global;
|
|
6
|
+
const environment = await builtinEnvironments["happy-dom"].setup(global, options);
|
|
7
|
+
// Vitest's setters also replace happy-dom's internal classes, which its fetch needs.
|
|
8
|
+
for (const [name, value] of Object.entries({ AbortController, AbortSignal, Headers, Request, Response })) {
|
|
9
|
+
Object.defineProperty(global, name, { configurable: true, value, writable: true });
|
|
10
|
+
}
|
|
11
|
+
const transport = global.fetch.bind(global);
|
|
12
|
+
Object.defineProperty(global, "fetch", {
|
|
13
|
+
configurable: true,
|
|
14
|
+
value: (input, init) => {
|
|
15
|
+
if (input instanceof Request) {
|
|
16
|
+
const request = new Request(input, init);
|
|
17
|
+
return transport(request.url, {
|
|
18
|
+
body: request.body,
|
|
19
|
+
cache: request.cache,
|
|
20
|
+
credentials: request.credentials,
|
|
21
|
+
headers: [...request.headers],
|
|
22
|
+
method: request.method,
|
|
23
|
+
mode: request.mode,
|
|
24
|
+
redirect: request.redirect,
|
|
25
|
+
referrer: request.referrer,
|
|
26
|
+
referrerPolicy: request.referrerPolicy,
|
|
27
|
+
signal: request.signal,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return transport(input, init?.headers instanceof Headers ? { ...init, headers: [...init.headers] } : init);
|
|
31
|
+
},
|
|
32
|
+
writable: true,
|
|
33
|
+
});
|
|
34
|
+
return environment;
|
|
35
|
+
},
|
|
36
|
+
viteEnvironment: "client",
|
|
37
|
+
};
|
|
38
|
+
export default domEnvironment;
|
|
39
|
+
//# sourceMappingURL=domEnvironment.config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domEnvironment.config.js","sourceRoot":"","sources":["../../src/server/domEnvironment.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAoB,MAAM,gBAAgB,CAAC;AAEvE,MAAM,cAAc,GAAgB;IACnC,IAAI,EAAE,gBAAgB;IACtB,KAAK,CAAC,KAAK,CAAC,MAAyB,EAAE,OAAO;QAC7C,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;QAC5E,MAAM,WAAW,GAAG,MAAM,mBAAmB,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClF,qFAAqF;QACrF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;YAC1G,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE;YACtC,YAAY,EAAE,IAAI;YAClB,KAAK,EAAE,CAAC,KAAwB,EAAE,IAAkB,EAAE,EAAE;gBACvD,IAAI,KAAK,YAAY,OAAO,EAAE,CAAC;oBAC9B,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;oBACzC,OAAO,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE;wBAC7B,IAAI,EAAE,OAAO,CAAC,IAAI;wBAClB,KAAK,EAAE,OAAO,CAAC,KAAK;wBACpB,WAAW,EAAE,OAAO,CAAC,WAAW;wBAChC,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;wBAC7B,MAAM,EAAE,OAAO,CAAC,MAAM;wBACtB,IAAI,EAAE,OAAO,CAAC,IAAI;wBAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;wBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;wBAC1B,cAAc,EAAE,OAAO,CAAC,cAAc;wBACtC,MAAM,EAAE,OAAO,CAAC,MAAM;qBACtB,CAAC,CAAC;gBACJ,CAAC;gBACD,OAAO,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,YAAY,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5G,CAAC;YACD,QAAQ,EAAE,IAAI;SACd,CAAC,CAAC;QACH,OAAO,WAAW,CAAC;IACpB,CAAC;IACD,eAAe,EAAE,QAAQ;CACzB,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domFetch.d.ts","sourceRoot":"","sources":["../../src/server/domFetch.ts"],"names":[],"mappings":"AAAA,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,UAAU,CAAC,KAAK,CAKjE"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function domFetch(baseUrl) {
|
|
2
|
+
const base = new URL(baseUrl);
|
|
3
|
+
window.location.href = base.href;
|
|
4
|
+
const transport = window.fetch.bind(window);
|
|
5
|
+
return (input, init) => transport(typeof input === "string" ? new URL(input, base).href : input, init);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=domFetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domFetch.js","sourceRoot":"","sources":["../../src/server/domFetch.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,QAAQ,CAAC,OAAe;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACjC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACxG,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Effect, Layer, Scope } from "effect";
|
|
2
|
+
import type { AnyEffect, Hook, HookEffects, HookValue, ServicesOf } from "#internal/hooked.ts";
|
|
3
|
+
import type { StoryKitDefinition } from "#storyKit.ts";
|
|
4
|
+
export type AfterResults<TStage extends string> = {
|
|
5
|
+
readonly [TKey in TStage]?: Generator<AnyEffect, void, never> | Effect.Effect<void, unknown, unknown> | void;
|
|
6
|
+
};
|
|
7
|
+
type LayerFor<THooks, TProvided> = [Exclude<ServicesOf<THooks>, Scope.Scope>] extends [TProvided] ? unknown : {
|
|
8
|
+
readonly layer: Layer.Layer<Exclude<ServicesOf<THooks>, Scope.Scope>, unknown, never>;
|
|
9
|
+
};
|
|
10
|
+
export type InferredDefinition<TCreate extends () => unknown, TStage extends string, TVerbs, TProvided, TAfter extends AfterResults<TStage>, TStep extends AnyEffect, TFailed extends AnyEffect, TDiagnose extends AnyEffect> = Omit<StoryKitDefinition<HookValue<ReturnType<TCreate>>, TStage, TVerbs, TProvided, never, Record<never, never>, TStep, TFailed, TDiagnose>, "create" | "after"> & {
|
|
11
|
+
readonly create: TCreate;
|
|
12
|
+
readonly after?: {
|
|
13
|
+
readonly [TKey in keyof TAfter]: (engine: HookValue<ReturnType<TCreate>>) => TAfter[TKey];
|
|
14
|
+
};
|
|
15
|
+
} & LayerFor<HookEffects<ReturnType<TCreate> | TAfter[keyof TAfter]> | TStep | TFailed | TDiagnose, TProvided>;
|
|
16
|
+
type AfterEffects<TAfter> = {
|
|
17
|
+
readonly [TKey in keyof TAfter]-?: AnyEffect extends TAfter[TKey] ? never : TAfter[TKey];
|
|
18
|
+
}[keyof TAfter];
|
|
19
|
+
export type LegacyDefinition<TEngine, TStage extends string, TVerbs, TProvided, TCreate extends AnyEffect, TAfter extends {
|
|
20
|
+
readonly [TKey in TStage]?: AnyEffect;
|
|
21
|
+
}, TStep extends AnyEffect, TFailed extends AnyEffect, TDiagnose extends AnyEffect> = Omit<StoryKitDefinition<TEngine, TStage, TVerbs, TProvided, TCreate, TAfter, TStep, TFailed, TDiagnose>, "create" | "after"> & {
|
|
22
|
+
readonly create: Hook<[], TCreate, TEngine> & (TEngine extends AnyEffect ? never : unknown);
|
|
23
|
+
readonly after?: {
|
|
24
|
+
readonly [TKey in keyof TAfter]: Hook<[engine: TEngine], TAfter[TKey] & AnyEffect, void>;
|
|
25
|
+
};
|
|
26
|
+
} & LayerFor<TCreate | AfterEffects<TAfter> | TStep | TFailed | TDiagnose, TProvided>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=hookInference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hookInference.d.ts","sourceRoot":"","sources":["../../../src/server/internal/hookInference.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hookInference.js","sourceRoot":"","sources":["../../../src/server/internal/hookInference.ts"],"names":[],"mappings":""}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
export type AnyEffect = Effect.Effect<unknown, unknown, unknown>;
|
|
3
3
|
export type Hook<TArguments extends readonly unknown[], TEffect extends AnyEffect, A> = (...input: TArguments) => Generator<TEffect, A, never> | A;
|
|
4
|
+
export type EffectHook<TArguments extends readonly unknown[], TEffect extends AnyEffect, A> = (...input: TArguments) => Generator<TEffect, A, never> | (TEffect & Effect.Effect<A, ErrorOf<TEffect>, ServicesOf<TEffect>>) | A;
|
|
5
|
+
export type HookValue<T> = T extends Generator<AnyEffect, infer A, never> ? A : T extends Effect.Effect<infer A, unknown, unknown> ? A : T;
|
|
6
|
+
export type HookEffects<T> = T extends Generator<infer TEffect, unknown, never> ? Extract<TEffect, AnyEffect> : T extends AnyEffect ? T : never;
|
|
4
7
|
export type ErrorOf<TEffect> = [TEffect] extends [never] ? never : [TEffect] extends [Effect.Effect<infer _A, infer E, infer _R>] ? E : never;
|
|
5
8
|
export type ServicesOf<TEffect> = [TEffect] extends [never] ? never : [TEffect] extends [Effect.Effect<infer _A, infer _E, infer R>] ? R : never;
|
|
6
|
-
export declare function hooked<TEffect extends AnyEffect, A>(call:
|
|
9
|
+
export declare function hooked<TEffect extends AnyEffect, A>(call: EffectHook<[], TEffect, A>): Effect.Effect<A, ErrorOf<TEffect>, ServicesOf<TEffect>>;
|
|
7
10
|
//# sourceMappingURL=hooked.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooked.d.ts","sourceRoot":"","sources":["../../../src/server/internal/hooked.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAEjE,MAAM,MAAM,IAAI,CAAC,UAAU,SAAS,SAAS,OAAO,EAAE,EAAE,OAAO,SAAS,SAAS,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,UAAU,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;AAEnJ,MAAM,MAAM,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE9I,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAMjJ,wBAAgB,MAAM,CAAC,OAAO,SAAS,SAAS,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"hooked.d.ts","sourceRoot":"","sources":["../../../src/server/internal/hooked.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAEjE,MAAM,MAAM,IAAI,CAAC,UAAU,SAAS,SAAS,OAAO,EAAE,EAAE,OAAO,SAAS,SAAS,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,UAAU,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;AAEnJ,MAAM,MAAM,UAAU,CAAC,UAAU,SAAS,SAAS,OAAO,EAAE,EAAE,OAAO,SAAS,SAAS,EAAE,CAAC,IAAI,CAC7F,GAAG,KAAK,EAAE,UAAU,KAChB,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAE5G,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC3I,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,CAAC,MAAM,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,SAAS,SAAS,GAAG,CAAC,GAAG,KAAK,CAAC;AAEhJ,MAAM,MAAM,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE9I,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAMjJ,wBAAgB,MAAM,CAAC,OAAO,SAAS,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAQ9I"}
|
|
@@ -5,6 +5,9 @@ function isGenerator(value) {
|
|
|
5
5
|
export function hooked(call) {
|
|
6
6
|
return Effect.suspend(() => {
|
|
7
7
|
const result = call();
|
|
8
|
+
if (Effect.isEffect(result)) {
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
8
11
|
return isGenerator(result) ? Effect.gen(() => result) : Effect.succeed(result);
|
|
9
12
|
});
|
|
10
13
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooked.js","sourceRoot":"","sources":["../../../src/server/internal/hooked.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"hooked.js","sourceRoot":"","sources":["../../../src/server/internal/hooked.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAiBhC,SAAS,WAAW,CAAa,KAAuC;IACvE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACvI,CAAC;AAED,MAAM,UAAU,MAAM,CAA+B,IAAgC;IACpF,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;QAC1B,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC;QACtB,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,OAAO,MAAM,CAAC;QACf,CAAC;QACD,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeOutbound.d.ts","sourceRoot":"","sources":["../../../src/server/internal/routeOutbound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAgDhC,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,4DAKrE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
function isDispatcher(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && "dispatch" in value && typeof value.dispatch === "function";
|
|
4
|
+
}
|
|
5
|
+
async function install(routes) {
|
|
6
|
+
const origins = new Map(Object.entries(routes).map(([hostname, target]) => [hostname, new URL(target).origin]));
|
|
7
|
+
// A data URL initializes Node's bundled Undici without sending an HTTP request.
|
|
8
|
+
await fetch("data:,");
|
|
9
|
+
const slots = ["undici.globalDispatcher.1", "undici.globalDispatcher.2"].flatMap((name) => {
|
|
10
|
+
const key = Symbol.for(name);
|
|
11
|
+
const previous = Reflect.get(globalThis, key);
|
|
12
|
+
return isDispatcher(previous) ? [{ key, previous }] : [];
|
|
13
|
+
});
|
|
14
|
+
if (slots.length === 0) {
|
|
15
|
+
throw new Error("test-story outbound routing needs Node's built-in fetch dispatcher. Run the stories in Node.js 24 or newer.");
|
|
16
|
+
}
|
|
17
|
+
function restore() {
|
|
18
|
+
for (const { key, previous } of slots) {
|
|
19
|
+
Object.defineProperty(globalThis, key, { value: previous });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
for (const { key, previous } of slots) {
|
|
24
|
+
const router = {
|
|
25
|
+
dispatch(options, handler) {
|
|
26
|
+
const target = origins.get(new URL(options.origin).hostname);
|
|
27
|
+
return previous.dispatch(target === undefined ? options : { ...options, origin: target }, handler);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
Object.defineProperty(globalThis, key, { value: router });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
restore();
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
return restore;
|
|
38
|
+
}
|
|
39
|
+
export function routeOutbound(routes) {
|
|
40
|
+
return Effect.acquireRelease(Effect.promise(() => install(routes)), (restore) => Effect.sync(restore)).pipe(Effect.asVoid);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=routeOutbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeOutbound.js","sourceRoot":"","sources":["../../../src/server/internal/routeOutbound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAUhC,SAAS,YAAY,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,UAAU,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,UAAU,CAAC;AACnH,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,MAAwC;IAC9D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAChH,gFAAgF;IAChF,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;IACtB,MAAM,KAAK,GAAG,CAAC,2BAA2B,EAAE,2BAA2B,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACzF,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,QAAQ,GAAY,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACvD,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1D,CAAC,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,6GAA6G,CAAC,CAAC;IAChI,CAAC;IACD,SAAS,OAAO;QACf,KAAK,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,KAAK,EAAE,CAAC;YACvC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7D,CAAC;IACF,CAAC;IACD,IAAI,CAAC;QACJ,KAAK,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,KAAK,EAAE,CAAC;YACvC,MAAM,MAAM,GAAe;gBAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO;oBACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC;oBAC7D,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;gBACpG,CAAC;aACD,CAAC;YACF,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3D,CAAC;IACF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,EAAE,CAAC;QACV,MAAM,KAAK,CAAC;IACb,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAwC;IACrE,OAAO,MAAM,CAAC,cAAc,CAC3B,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EACrC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CACjC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect, type Scope } from "effect";
|
|
2
2
|
import type { AnyTestLayer } from "@shivaedev/effect-test/any-test-layer.ts";
|
|
3
3
|
import type { EffectClock } from "@shivaedev/effect-test/types.ts";
|
|
4
|
-
import { type AnyEffect, type Hook } from "#internal/hooked.ts";
|
|
4
|
+
import { type AnyEffect, type EffectHook, type Hook } from "#internal/hooked.ts";
|
|
5
5
|
import { type Narration } from "#internal/narration.ts";
|
|
6
6
|
import type { LooseRunHooks } from "#internal/runUntil.ts";
|
|
7
7
|
import type { Parts } from "#internal/traits.ts";
|
|
@@ -14,13 +14,14 @@ export interface LooseStory {
|
|
|
14
14
|
readonly tell: (line: string) => void;
|
|
15
15
|
}
|
|
16
16
|
export interface LooseDefinition {
|
|
17
|
-
readonly after?: Readonly<Partial<Record<string,
|
|
17
|
+
readonly after?: Readonly<Partial<Record<string, EffectHook<[engine: unknown], AnyEffect, void>>>>;
|
|
18
18
|
readonly around?: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, unknown, R | Scope.Scope>;
|
|
19
19
|
readonly clock?: EffectClock;
|
|
20
|
-
readonly create:
|
|
20
|
+
readonly create: EffectHook<[], AnyEffect, unknown>;
|
|
21
21
|
readonly inspect?: (engine: unknown) => unknown;
|
|
22
22
|
readonly layer?: AnyTestLayer;
|
|
23
23
|
readonly name: string;
|
|
24
|
+
readonly outbound?: Readonly<Record<string, string>>;
|
|
24
25
|
readonly run?: LooseRunHooks<unknown>;
|
|
25
26
|
readonly stages: readonly string[];
|
|
26
27
|
readonly verbs: (engine: unknown, story: LooseStory) => object;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../../src/server/internal/seed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,IAAI,EAAU,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../../src/server/internal/seed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAU,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAEnG,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5I,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACnG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1G,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC;CAC/D;AAED,eAAO,MAAM,IAAI,6IAuBf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../src/server/internal/seed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAC;AAGnD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,
|
|
1
|
+
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../src/server/internal/seed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAC;AAGnD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAA8C,MAAM,EAAE,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAkB,SAAS,EAAE,MAAM,wBAAwB,CAAC;AA4BnE,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,UAA2B,EAAE,MAAe,EAAE,SAAoB,EAAE,KAA4B;IAChJ,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;YAC7C,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;gBAC1B,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CACnD,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,EAAE;oBAC3B,IAAI,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;wBACpC,OAAO,MAAM,CAAC,SAAS,CAAC;oBACzB,CAAC;oBACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACnC,SAAS,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;oBAC7C,OAAO,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;gBAChE,CAAC,CAAC,CACF,CAAC;YACH,CAAC;QACF,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QACpC,CAAC;IACF,CAAC;AACF,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storyIt.d.ts","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGrF,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"storyIt.d.ts","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGrF,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,qBAAqB,CAAC;AAE7D,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGrE,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;AAE1G,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,iBAAiB,KAAK,IAAI,CAAC;AAE9H,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;CACnD;AAiCD,wBAAgB,OAAO,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAuB5D"}
|
|
@@ -2,6 +2,7 @@ import { Effect, Layer } from "effect";
|
|
|
2
2
|
import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
|
|
3
3
|
import { genreOf } from "#internal/genre.ts";
|
|
4
4
|
import { hooked } from "#internal/hooked.ts";
|
|
5
|
+
import { routeOutbound } from "#internal/routeOutbound.ts";
|
|
5
6
|
import { start } from "#internal/start.ts";
|
|
6
7
|
function withTags(options, tags) {
|
|
7
8
|
if (typeof options === "number") {
|
|
@@ -34,8 +35,14 @@ function declaring(definition, genre, declare) {
|
|
|
34
35
|
}
|
|
35
36
|
export function storyIt(definition) {
|
|
36
37
|
const genre = genreOf(definition.name);
|
|
38
|
+
const outbound = definition.outbound;
|
|
37
39
|
const { effectApp } = makeEffectIt({
|
|
38
|
-
around:
|
|
40
|
+
around: outbound === undefined
|
|
41
|
+
? definition.around
|
|
42
|
+
: (effect) => Effect.gen(function* () {
|
|
43
|
+
yield* routeOutbound(outbound);
|
|
44
|
+
return yield* definition.around?.(effect) ?? effect;
|
|
45
|
+
}),
|
|
39
46
|
clock: definition.clock,
|
|
40
47
|
layer: definition.layer ?? Layer.empty,
|
|
41
48
|
makeHarness: () => Effect.void,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storyIt.js","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAc,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAkB,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"storyIt.js","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import type { TestContext } from "@effect/vitest";
|
|
2
2
|
import type { Effect, Layer, Scope } from "effect";
|
|
3
3
|
import type { EffectClock, EffectTestOptions } from "@shivaedev/effect-test/types.ts";
|
|
4
|
-
import { type AnyEffect, type ErrorOf, type Hook, type ServicesOf } from "#internal/hooked.ts";
|
|
4
|
+
import { type AnyEffect, type EffectHook, type ErrorOf, type Hook, type HookValue, type ServicesOf } from "#internal/hooked.ts";
|
|
5
|
+
import type { AfterResults, InferredDefinition, LegacyDefinition } from "#internal/hookInference.ts";
|
|
5
6
|
import { type Parts } from "#internal/traits.ts";
|
|
6
7
|
export type Trait<TEngine, TStage extends string, R = never> = Parts<TStage, (engine: TEngine) => Effect.Effect<void, unknown, R>>;
|
|
7
8
|
type TraitServices<TTrait> = TTrait extends Parts<string, (engine: never) => Effect.Effect<void, unknown, infer R>> ? R : never;
|
|
8
9
|
type Needs<TProvided> = Effect.Effect<unknown, unknown, TProvided>;
|
|
9
|
-
type AfterEffects<TAfter> = {
|
|
10
|
-
readonly [TKey in keyof TAfter]-?: AnyEffect extends TAfter[TKey] ? never : TAfter[TKey];
|
|
11
|
-
}[keyof TAfter];
|
|
12
|
-
type LayerFor<THooks, TProvided> = [Exclude<ServicesOf<THooks>, Scope.Scope>] extends [TProvided] ? unknown : {
|
|
13
|
-
readonly layer: Layer.Layer<Exclude<ServicesOf<THooks>, Scope.Scope>, unknown, never>;
|
|
14
|
-
};
|
|
15
10
|
export interface RunUntilOptions {
|
|
16
11
|
readonly maxSteps?: number | undefined;
|
|
17
12
|
}
|
|
@@ -31,14 +26,15 @@ export interface StoryKitDefinition<TEngine, TStage extends string, TVerbs, TPro
|
|
|
31
26
|
readonly [TKey in TStage]?: AnyEffect;
|
|
32
27
|
}, TStep extends AnyEffect, TFailed extends AnyEffect, TDiagnose extends AnyEffect> {
|
|
33
28
|
readonly after?: {
|
|
34
|
-
readonly [TKey in keyof TAfter]:
|
|
29
|
+
readonly [TKey in keyof TAfter]: EffectHook<[engine: TEngine], TAfter[TKey] & AnyEffect, void>;
|
|
35
30
|
};
|
|
36
31
|
readonly around?: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, unknown, R | NoInfer<TProvided> | Scope.Scope>;
|
|
37
32
|
readonly clock?: EffectClock;
|
|
38
|
-
readonly create:
|
|
33
|
+
readonly create: EffectHook<[], TCreate, TEngine>;
|
|
39
34
|
readonly inspect?: (engine: TEngine) => unknown;
|
|
40
35
|
readonly layer?: Layer.Layer<TProvided, unknown, never>;
|
|
41
36
|
readonly name: string;
|
|
37
|
+
readonly outbound?: Readonly<Record<string, string>>;
|
|
42
38
|
readonly run?: RunHooks<TEngine, TStep, TFailed, TDiagnose>;
|
|
43
39
|
readonly stages: readonly [TStage, ...TStage[]];
|
|
44
40
|
readonly verbs: (engine: TEngine, story: Story<TEngine, TStep | TFailed | TDiagnose>) => TVerbs;
|
|
@@ -57,11 +53,15 @@ export interface StoryIt<TEngine, TStage extends string, TVerbs, TRun extends An
|
|
|
57
53
|
}
|
|
58
54
|
export interface StoryKit<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided> {
|
|
59
55
|
readonly it: StoryIt<TEngine, TStage, TVerbs, TRun, TProvided>;
|
|
60
|
-
readonly trait:
|
|
56
|
+
readonly trait: {
|
|
57
|
+
<TEffect extends AnyEffect = never>(stage: TStage, line: string, apply: Hook<[engine: TEngine], TEffect, void>): Trait<TEngine, TStage, ServicesOf<TEffect>>;
|
|
58
|
+
<E, R>(stage: TStage, line: string, apply: (engine: TEngine) => Effect.Effect<void, E, R>): Trait<TEngine, TStage, R>;
|
|
59
|
+
};
|
|
61
60
|
readonly traits: <const TGiven extends readonly Trait<TEngine, TStage, unknown>[]>(...given: TGiven) => Trait<TEngine, TStage, TraitServices<TGiven[number]>>;
|
|
62
61
|
}
|
|
62
|
+
export declare function storyKit<TCreate extends () => unknown, const TStage extends string, TVerbs extends object, TProvided = never, TAfter extends AfterResults<TStage> = Record<never, never>, TStep extends AnyEffect = never, TFailed extends AnyEffect = never, TDiagnose extends AnyEffect = never>(definition: InferredDefinition<TCreate, TStage, TVerbs, TProvided, TAfter, TStep, TFailed, TDiagnose>): StoryKit<HookValue<ReturnType<TCreate>>, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
|
|
63
63
|
export declare function storyKit<TEngine, const TStage extends string, TVerbs extends object, TProvided = never, TCreate extends AnyEffect = never, TAfter extends {
|
|
64
64
|
readonly [TKey in TStage]?: AnyEffect;
|
|
65
|
-
} = Record<never, never>, TStep extends AnyEffect = never, TFailed extends AnyEffect = never, TDiagnose extends AnyEffect = never>(definition:
|
|
65
|
+
} = Record<never, never>, TStep extends AnyEffect = never, TFailed extends AnyEffect = never, TDiagnose extends AnyEffect = never>(definition: LegacyDefinition<TEngine, TStage, TVerbs, TProvided, TCreate, TAfter, TStep, TFailed, TDiagnose>): StoryKit<TEngine, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
|
|
66
66
|
export {};
|
|
67
67
|
//# sourceMappingURL=storyKit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storyKit.d.ts","sourceRoot":"","sources":["../../src/server/storyKit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACtF,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAU,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"storyKit.d.ts","sourceRoot":"","sources":["../../src/server/storyKit.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storyKit.js","sourceRoot":"","sources":["../../src/server/storyKit.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"storyKit.js","sourceRoot":"","sources":["../../src/server/storyKit.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4E,MAAM,EAAmB,MAAM,qBAAqB,CAAC;AAGxI,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAc,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAuHhE,MAAM,UAAU,QAAQ,CAAC,UAA2B;IACnD,OAAO;QACN,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC;QACvB,KAAK,EAAE,CAAC,KAAa,EAAE,IAAY,EAAE,KAAqD,EAAE,EAAE,CAC7F,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,MAAe,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QACrE,MAAM;KACN,CAAC;AACH,CAAC"}
|
package/docs/roadmap.md
CHANGED
|
@@ -12,6 +12,7 @@ to each other rather than repeating a completion checklist.
|
|
|
12
12
|
- [x] The kit's own `it`, built on `@shivaedev/effect-test`, which hands the body the verbs and the story and names every test after the kit's genre. Vitest tags per genre are opt-in through `genreTags(...kitNames)` in the Vitest config.
|
|
13
13
|
- [x] `story.runUntil(condition)`: steps the engine until the condition holds, stops on what `run.failed` reports, and stops after a step budget with a diagnosis, keeping every hook's typed failure in its error channel.
|
|
14
14
|
- [x] A failed test prints a short guide, its story, where it stopped with the spec line there, and the engine's state, inline or in a file under `node_modules/.cache/test-story/`, broken at 120 columns.
|
|
15
|
+
- [x] `domEnvironment` keeps Node fetch classes for an in-process host and happy-dom fetch for the client; `domFetch(baseUrl)` connects the window to that host, with a starter story proving Better Auth sign-up, sign-in, session reads and sign-out on PostgreSQL through cookies.
|
|
15
16
|
|
|
16
17
|
## Next
|
|
17
18
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/test-story",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Tests that read like stories over the real engine: a story kit per engine with its own it, traits per feature, and a failure that prints the story, where it stopped and the engine's state",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/test-story#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shivaedev/effect-test": "0.
|
|
46
|
+
"@shivaedev/effect-test": "0.5.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@effect/vitest": "4.0.4",
|
|
50
|
-
"@shivaedev/quality": "0.
|
|
50
|
+
"@shivaedev/quality": "0.22.0",
|
|
51
51
|
"@types/node": "24.19.1",
|
|
52
52
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
53
53
|
"effect": "4.0.4",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { builtinEnvironments, type Environment } from "vitest/runtime";
|
|
2
|
+
|
|
3
|
+
const domEnvironment: Environment = {
|
|
4
|
+
name: "test-story-dom",
|
|
5
|
+
async setup(global: typeof globalThis, options) {
|
|
6
|
+
const { AbortController, AbortSignal, Headers, Request, Response } = global;
|
|
7
|
+
const environment = await builtinEnvironments["happy-dom"].setup(global, options);
|
|
8
|
+
// Vitest's setters also replace happy-dom's internal classes, which its fetch needs.
|
|
9
|
+
for (const [name, value] of Object.entries({ AbortController, AbortSignal, Headers, Request, Response })) {
|
|
10
|
+
Object.defineProperty(global, name, { configurable: true, value, writable: true });
|
|
11
|
+
}
|
|
12
|
+
const transport = global.fetch.bind(global);
|
|
13
|
+
Object.defineProperty(global, "fetch", {
|
|
14
|
+
configurable: true,
|
|
15
|
+
value: (input: RequestInfo | URL, init?: RequestInit) => {
|
|
16
|
+
if (input instanceof Request) {
|
|
17
|
+
const request = new Request(input, init);
|
|
18
|
+
return transport(request.url, {
|
|
19
|
+
body: request.body,
|
|
20
|
+
cache: request.cache,
|
|
21
|
+
credentials: request.credentials,
|
|
22
|
+
headers: [...request.headers],
|
|
23
|
+
method: request.method,
|
|
24
|
+
mode: request.mode,
|
|
25
|
+
redirect: request.redirect,
|
|
26
|
+
referrer: request.referrer,
|
|
27
|
+
referrerPolicy: request.referrerPolicy,
|
|
28
|
+
signal: request.signal,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return transport(input, init?.headers instanceof Headers ? { ...init, headers: [...init.headers] } : init);
|
|
32
|
+
},
|
|
33
|
+
writable: true,
|
|
34
|
+
});
|
|
35
|
+
return environment;
|
|
36
|
+
},
|
|
37
|
+
viteEnvironment: "client",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default domEnvironment;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function domFetch(baseUrl: string): typeof globalThis.fetch {
|
|
2
|
+
const base = new URL(baseUrl);
|
|
3
|
+
window.location.href = base.href;
|
|
4
|
+
const transport = window.fetch.bind(window);
|
|
5
|
+
return (input, init) => transport(typeof input === "string" ? new URL(input, base).href : input, init);
|
|
6
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Effect, Layer, Scope } from "effect";
|
|
2
|
+
import type { AnyEffect, Hook, HookEffects, HookValue, ServicesOf } from "#internal/hooked.ts";
|
|
3
|
+
import type { StoryKitDefinition } from "#storyKit.ts";
|
|
4
|
+
|
|
5
|
+
export type AfterResults<TStage extends string> = {
|
|
6
|
+
readonly [TKey in TStage]?: Generator<AnyEffect, void, never> | Effect.Effect<void, unknown, unknown> | void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type LayerFor<THooks, TProvided> = [Exclude<ServicesOf<THooks>, Scope.Scope>] extends [TProvided]
|
|
10
|
+
? unknown
|
|
11
|
+
: { readonly layer: Layer.Layer<Exclude<ServicesOf<THooks>, Scope.Scope>, unknown, never> };
|
|
12
|
+
|
|
13
|
+
export type InferredDefinition<
|
|
14
|
+
TCreate extends () => unknown,
|
|
15
|
+
TStage extends string,
|
|
16
|
+
TVerbs,
|
|
17
|
+
TProvided,
|
|
18
|
+
TAfter extends AfterResults<TStage>,
|
|
19
|
+
TStep extends AnyEffect,
|
|
20
|
+
TFailed extends AnyEffect,
|
|
21
|
+
TDiagnose extends AnyEffect,
|
|
22
|
+
> = Omit<
|
|
23
|
+
StoryKitDefinition<HookValue<ReturnType<TCreate>>, TStage, TVerbs, TProvided, never, Record<never, never>, TStep, TFailed, TDiagnose>,
|
|
24
|
+
"create" | "after"
|
|
25
|
+
> & {
|
|
26
|
+
readonly create: TCreate;
|
|
27
|
+
readonly after?: { readonly [TKey in keyof TAfter]: (engine: HookValue<ReturnType<TCreate>>) => TAfter[TKey] };
|
|
28
|
+
} & LayerFor<HookEffects<ReturnType<TCreate> | TAfter[keyof TAfter]> | TStep | TFailed | TDiagnose, TProvided>;
|
|
29
|
+
|
|
30
|
+
// A synchronous after hook has no effect to infer, so TypeScript leaves it at the AnyEffect constraint.
|
|
31
|
+
type AfterEffects<TAfter> = { readonly [TKey in keyof TAfter]-?: AnyEffect extends TAfter[TKey] ? never : TAfter[TKey] }[keyof TAfter];
|
|
32
|
+
|
|
33
|
+
export type LegacyDefinition<
|
|
34
|
+
TEngine,
|
|
35
|
+
TStage extends string,
|
|
36
|
+
TVerbs,
|
|
37
|
+
TProvided,
|
|
38
|
+
TCreate extends AnyEffect,
|
|
39
|
+
TAfter extends { readonly [TKey in TStage]?: AnyEffect },
|
|
40
|
+
TStep extends AnyEffect,
|
|
41
|
+
TFailed extends AnyEffect,
|
|
42
|
+
TDiagnose extends AnyEffect,
|
|
43
|
+
> = Omit<StoryKitDefinition<TEngine, TStage, TVerbs, TProvided, TCreate, TAfter, TStep, TFailed, TDiagnose>, "create" | "after"> & {
|
|
44
|
+
readonly create: Hook<[], TCreate, TEngine> & (TEngine extends AnyEffect ? never : unknown);
|
|
45
|
+
readonly after?: { readonly [TKey in keyof TAfter]: Hook<[engine: TEngine], TAfter[TKey] & AnyEffect, void> };
|
|
46
|
+
} & LayerFor<TCreate | AfterEffects<TAfter> | TStep | TFailed | TDiagnose, TProvided>;
|
|
@@ -4,6 +4,13 @@ export type AnyEffect = Effect.Effect<unknown, unknown, unknown>;
|
|
|
4
4
|
|
|
5
5
|
export type Hook<TArguments extends readonly unknown[], TEffect extends AnyEffect, A> = (...input: TArguments) => Generator<TEffect, A, never> | A;
|
|
6
6
|
|
|
7
|
+
export type EffectHook<TArguments extends readonly unknown[], TEffect extends AnyEffect, A> = (
|
|
8
|
+
...input: TArguments
|
|
9
|
+
) => Generator<TEffect, A, never> | (TEffect & Effect.Effect<A, ErrorOf<TEffect>, ServicesOf<TEffect>>) | A;
|
|
10
|
+
|
|
11
|
+
export type HookValue<T> = T extends Generator<AnyEffect, infer A, never> ? A : T extends Effect.Effect<infer A, unknown, unknown> ? A : T;
|
|
12
|
+
export type HookEffects<T> = T extends Generator<infer TEffect, unknown, never> ? Extract<TEffect, AnyEffect> : T extends AnyEffect ? T : never;
|
|
13
|
+
|
|
7
14
|
export type ErrorOf<TEffect> = [TEffect] extends [never] ? never : [TEffect] extends [Effect.Effect<infer _A, infer E, infer _R>] ? E : never;
|
|
8
15
|
|
|
9
16
|
export type ServicesOf<TEffect> = [TEffect] extends [never] ? never : [TEffect] extends [Effect.Effect<infer _A, infer _E, infer R>] ? R : never;
|
|
@@ -12,11 +19,12 @@ function isGenerator<TEffect, A>(value: Generator<TEffect, A, never> | A): value
|
|
|
12
19
|
return typeof value === "object" && value !== null && Symbol.iterator in value && "next" in value && typeof value.next === "function";
|
|
13
20
|
}
|
|
14
21
|
|
|
15
|
-
export function hooked<TEffect extends AnyEffect, A>(
|
|
16
|
-
call: () => Generator<TEffect, A, never> | A,
|
|
17
|
-
): Effect.Effect<A, ErrorOf<TEffect>, ServicesOf<TEffect>> {
|
|
22
|
+
export function hooked<TEffect extends AnyEffect, A>(call: EffectHook<[], TEffect, A>): Effect.Effect<A, ErrorOf<TEffect>, ServicesOf<TEffect>> {
|
|
18
23
|
return Effect.suspend(() => {
|
|
19
24
|
const result = call();
|
|
25
|
+
if (Effect.isEffect(result)) {
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
20
28
|
return isGenerator(result) ? Effect.gen(() => result) : Effect.succeed(result);
|
|
21
29
|
});
|
|
22
30
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
3
|
+
interface DispatchOptions {
|
|
4
|
+
readonly origin: string | URL;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface Dispatcher {
|
|
8
|
+
readonly dispatch: (options: DispatchOptions, handler: unknown) => boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isDispatcher(value: unknown): value is Dispatcher {
|
|
12
|
+
return typeof value === "object" && value !== null && "dispatch" in value && typeof value.dispatch === "function";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function install(routes: Readonly<Record<string, string>>) {
|
|
16
|
+
const origins = new Map(Object.entries(routes).map(([hostname, target]) => [hostname, new URL(target).origin]));
|
|
17
|
+
// A data URL initializes Node's bundled Undici without sending an HTTP request.
|
|
18
|
+
await fetch("data:,");
|
|
19
|
+
const slots = ["undici.globalDispatcher.1", "undici.globalDispatcher.2"].flatMap((name) => {
|
|
20
|
+
const key = Symbol.for(name);
|
|
21
|
+
const previous: unknown = Reflect.get(globalThis, key);
|
|
22
|
+
return isDispatcher(previous) ? [{ key, previous }] : [];
|
|
23
|
+
});
|
|
24
|
+
if (slots.length === 0) {
|
|
25
|
+
throw new Error("test-story outbound routing needs Node's built-in fetch dispatcher. Run the stories in Node.js 24 or newer.");
|
|
26
|
+
}
|
|
27
|
+
function restore() {
|
|
28
|
+
for (const { key, previous } of slots) {
|
|
29
|
+
Object.defineProperty(globalThis, key, { value: previous });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
for (const { key, previous } of slots) {
|
|
34
|
+
const router: Dispatcher = {
|
|
35
|
+
dispatch(options, handler) {
|
|
36
|
+
const target = origins.get(new URL(options.origin).hostname);
|
|
37
|
+
return previous.dispatch(target === undefined ? options : { ...options, origin: target }, handler);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
Object.defineProperty(globalThis, key, { value: router });
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
restore();
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
return restore;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function routeOutbound(routes: Readonly<Record<string, string>>) {
|
|
50
|
+
return Effect.acquireRelease(
|
|
51
|
+
Effect.promise(() => install(routes)),
|
|
52
|
+
(restore) => Effect.sync(restore),
|
|
53
|
+
).pipe(Effect.asVoid);
|
|
54
|
+
}
|
|
@@ -2,7 +2,7 @@ import { Cause, Effect, type Scope } from "effect";
|
|
|
2
2
|
import type { AnyTestLayer } from "@shivaedev/effect-test/any-test-layer.ts";
|
|
3
3
|
import type { EffectClock } from "@shivaedev/effect-test/types.ts";
|
|
4
4
|
import { refusal } from "#internal/errors.ts";
|
|
5
|
-
import { type AnyEffect, type Hook, hooked } from "#internal/hooked.ts";
|
|
5
|
+
import { type AnyEffect, type EffectHook, type Hook, hooked } from "#internal/hooked.ts";
|
|
6
6
|
import { type Narration, tellGiven } from "#internal/narration.ts";
|
|
7
7
|
import type { LooseRunHooks } from "#internal/runUntil.ts";
|
|
8
8
|
import type { Parts } from "#internal/traits.ts";
|
|
@@ -18,13 +18,14 @@ export interface LooseStory {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface LooseDefinition {
|
|
21
|
-
readonly after?: Readonly<Partial<Record<string,
|
|
21
|
+
readonly after?: Readonly<Partial<Record<string, EffectHook<[engine: unknown], AnyEffect, void>>>>;
|
|
22
22
|
readonly around?: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, unknown, R | Scope.Scope>;
|
|
23
23
|
readonly clock?: EffectClock;
|
|
24
|
-
readonly create:
|
|
24
|
+
readonly create: EffectHook<[], AnyEffect, unknown>;
|
|
25
25
|
readonly inspect?: (engine: unknown) => unknown;
|
|
26
26
|
readonly layer?: AnyTestLayer;
|
|
27
27
|
readonly name: string;
|
|
28
|
+
readonly outbound?: Readonly<Record<string, string>>;
|
|
28
29
|
readonly run?: LooseRunHooks<unknown>;
|
|
29
30
|
readonly stages: readonly string[];
|
|
30
31
|
readonly verbs: (engine: unknown, story: LooseStory) => object;
|
|
@@ -4,6 +4,7 @@ import type { EffectTest, EffectTestOptions } from "@shivaedev/effect-test/types
|
|
|
4
4
|
import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
|
|
5
5
|
import { type Genre, genreOf } from "#internal/genre.ts";
|
|
6
6
|
import { type AnyEffect, hooked } from "#internal/hooked.ts";
|
|
7
|
+
import { routeOutbound } from "#internal/routeOutbound.ts";
|
|
7
8
|
import type { LooseDefinition, LooseTrait } from "#internal/seed.ts";
|
|
8
9
|
import { start } from "#internal/start.ts";
|
|
9
10
|
|
|
@@ -52,8 +53,16 @@ function declaring(definition: LooseDefinition, genre: Genre, declare: EffectTes
|
|
|
52
53
|
|
|
53
54
|
export function storyIt(definition: LooseDefinition): LooseIt {
|
|
54
55
|
const genre = genreOf(definition.name);
|
|
56
|
+
const outbound = definition.outbound;
|
|
55
57
|
const { effectApp } = makeEffectIt({
|
|
56
|
-
around:
|
|
58
|
+
around:
|
|
59
|
+
outbound === undefined
|
|
60
|
+
? definition.around
|
|
61
|
+
: (effect) =>
|
|
62
|
+
Effect.gen(function* () {
|
|
63
|
+
yield* routeOutbound(outbound);
|
|
64
|
+
return yield* definition.around?.(effect) ?? effect;
|
|
65
|
+
}),
|
|
57
66
|
clock: definition.clock,
|
|
58
67
|
layer: definition.layer ?? Layer.empty,
|
|
59
68
|
makeHarness: () => Effect.void,
|
package/src/server/storyKit.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { TestContext } from "@effect/vitest";
|
|
2
2
|
import type { Effect, Layer, Scope } from "effect";
|
|
3
3
|
import type { EffectClock, EffectTestOptions } from "@shivaedev/effect-test/types.ts";
|
|
4
|
-
import { type AnyEffect, type ErrorOf, type Hook, hooked, type ServicesOf } from "#internal/hooked.ts";
|
|
5
|
-
import type {
|
|
6
|
-
import
|
|
4
|
+
import { type AnyEffect, type EffectHook, type ErrorOf, type Hook, type HookValue, hooked, type ServicesOf } from "#internal/hooked.ts";
|
|
5
|
+
import type { AfterResults, InferredDefinition, LegacyDefinition } from "#internal/hookInference.ts";
|
|
6
|
+
import type { LooseDefinition } from "#internal/seed.ts";
|
|
7
|
+
import { storyIt } from "#internal/storyIt.ts";
|
|
7
8
|
import { type Parts, trait, traits } from "#internal/traits.ts";
|
|
8
9
|
|
|
9
10
|
export type Trait<TEngine, TStage extends string, R = never> = Parts<TStage, (engine: TEngine) => Effect.Effect<void, unknown, R>>;
|
|
@@ -12,13 +13,6 @@ type TraitServices<TTrait> = TTrait extends Parts<string, (engine: never) => Eff
|
|
|
12
13
|
|
|
13
14
|
type Needs<TProvided> = Effect.Effect<unknown, unknown, TProvided>;
|
|
14
15
|
|
|
15
|
-
// A plain after hook yields nothing to infer, so TypeScript leaves its effect at the AnyEffect constraint.
|
|
16
|
-
type AfterEffects<TAfter> = { readonly [TKey in keyof TAfter]-?: AnyEffect extends TAfter[TKey] ? never : TAfter[TKey] }[keyof TAfter];
|
|
17
|
-
|
|
18
|
-
type LayerFor<THooks, TProvided> = [Exclude<ServicesOf<THooks>, Scope.Scope>] extends [TProvided]
|
|
19
|
-
? unknown
|
|
20
|
-
: { readonly layer: Layer.Layer<Exclude<ServicesOf<THooks>, Scope.Scope>, unknown, never> };
|
|
21
|
-
|
|
22
16
|
export interface RunUntilOptions {
|
|
23
17
|
readonly maxSteps?: number | undefined;
|
|
24
18
|
}
|
|
@@ -51,13 +45,14 @@ export interface StoryKitDefinition<
|
|
|
51
45
|
TFailed extends AnyEffect,
|
|
52
46
|
TDiagnose extends AnyEffect,
|
|
53
47
|
> {
|
|
54
|
-
readonly after?: { readonly [TKey in keyof TAfter]:
|
|
48
|
+
readonly after?: { readonly [TKey in keyof TAfter]: EffectHook<[engine: TEngine], TAfter[TKey] & AnyEffect, void> };
|
|
55
49
|
readonly around?: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, unknown, R | NoInfer<TProvided> | Scope.Scope>;
|
|
56
50
|
readonly clock?: EffectClock;
|
|
57
|
-
readonly create:
|
|
51
|
+
readonly create: EffectHook<[], TCreate, TEngine>;
|
|
58
52
|
readonly inspect?: (engine: TEngine) => unknown;
|
|
59
53
|
readonly layer?: Layer.Layer<TProvided, unknown, never>;
|
|
60
54
|
readonly name: string;
|
|
55
|
+
readonly outbound?: Readonly<Record<string, string>>;
|
|
61
56
|
readonly run?: RunHooks<TEngine, TStep, TFailed, TDiagnose>;
|
|
62
57
|
readonly stages: readonly [TStage, ...TStage[]];
|
|
63
58
|
readonly verbs: (engine: TEngine, story: Story<TEngine, TStep | TFailed | TDiagnose>) => TVerbs;
|
|
@@ -90,23 +85,32 @@ export interface StoryIt<TEngine, TStage extends string, TVerbs, TRun extends An
|
|
|
90
85
|
|
|
91
86
|
export interface StoryKit<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided> {
|
|
92
87
|
readonly it: StoryIt<TEngine, TStage, TVerbs, TRun, TProvided>;
|
|
93
|
-
readonly trait:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
88
|
+
readonly trait: {
|
|
89
|
+
<TEffect extends AnyEffect = never>(
|
|
90
|
+
stage: TStage,
|
|
91
|
+
line: string,
|
|
92
|
+
apply: Hook<[engine: TEngine], TEffect, void>,
|
|
93
|
+
): Trait<TEngine, TStage, ServicesOf<TEffect>>;
|
|
94
|
+
<E, R>(stage: TStage, line: string, apply: (engine: TEngine) => Effect.Effect<void, E, R>): Trait<TEngine, TStage, R>;
|
|
95
|
+
};
|
|
98
96
|
readonly traits: <const TGiven extends readonly Trait<TEngine, TStage, unknown>[]>(
|
|
99
97
|
...given: TGiven
|
|
100
98
|
) => Trait<TEngine, TStage, TraitServices<TGiven[number]>>;
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
export function storyKit<
|
|
102
|
+
TCreate extends () => unknown,
|
|
103
|
+
const TStage extends string,
|
|
104
|
+
TVerbs extends object,
|
|
105
|
+
TProvided = never,
|
|
106
|
+
TAfter extends AfterResults<TStage> = Record<never, never>,
|
|
107
|
+
TStep extends AnyEffect = never,
|
|
108
|
+
TFailed extends AnyEffect = never,
|
|
109
|
+
TDiagnose extends AnyEffect = never,
|
|
110
|
+
>(
|
|
111
|
+
definition: InferredDefinition<TCreate, TStage, TVerbs, TProvided, TAfter, TStep, TFailed, TDiagnose>,
|
|
112
|
+
): StoryKit<HookValue<ReturnType<TCreate>>, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
|
|
113
|
+
// Keep explicit engine and effect type arguments accepted by existing kits.
|
|
110
114
|
export function storyKit<
|
|
111
115
|
TEngine,
|
|
112
116
|
const TStage extends string,
|
|
@@ -118,13 +122,13 @@ export function storyKit<
|
|
|
118
122
|
TFailed extends AnyEffect = never,
|
|
119
123
|
TDiagnose extends AnyEffect = never,
|
|
120
124
|
>(
|
|
121
|
-
definition:
|
|
122
|
-
LayerFor<TCreate | AfterEffects<TAfter> | TStep | TFailed | TDiagnose, TProvided>,
|
|
125
|
+
definition: LegacyDefinition<TEngine, TStage, TVerbs, TProvided, TCreate, TAfter, TStep, TFailed, TDiagnose>,
|
|
123
126
|
): StoryKit<TEngine, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
|
|
124
|
-
export function storyKit(definition: LooseDefinition):
|
|
127
|
+
export function storyKit(definition: LooseDefinition): unknown {
|
|
125
128
|
return {
|
|
126
129
|
it: storyIt(definition),
|
|
127
|
-
trait: (stage, line, apply
|
|
130
|
+
trait: (stage: string, line: string, apply: EffectHook<[engine: unknown], AnyEffect, void>) =>
|
|
131
|
+
trait(stage, line, (engine: unknown) => hooked(() => apply(engine))),
|
|
128
132
|
traits,
|
|
129
133
|
};
|
|
130
134
|
}
|