@stratal/testing 0.0.27 → 0.1.1
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 +570 -0
- package/README.md +137 -36
- package/dist/database/index.d.mts +2 -2
- package/dist/database/index.mjs +2 -2
- package/dist/database-Rojs69r3.mjs +453 -0
- package/dist/database-Rojs69r3.mjs.map +1 -0
- package/dist/{decorate-B7nr7eBl.mjs → decorate-RQD1h28J.mjs} +1 -1
- package/dist/feature-flags/index.mjs +1 -1
- package/dist/{feature-flags-BiLhfSGh.mjs → feature-flags-CZ1a4M2g.mjs} +2 -2
- package/dist/{feature-flags-BiLhfSGh.mjs.map → feature-flags-CZ1a4M2g.mjs.map} +1 -1
- package/dist/index-B8Mw1Dxk.d.mts +175 -0
- package/dist/index-B8Mw1Dxk.d.mts.map +1 -0
- package/dist/{index-CrHzUDKX.d.mts → index-D7FM6EKp.d.mts} +21 -3
- package/dist/index-D7FM6EKp.d.mts.map +1 -0
- package/dist/index-qgWNJRdC.d.mts.map +1 -1
- package/dist/index.d.mts +105 -46
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +218 -108
- package/dist/index.mjs.map +1 -1
- package/dist/mocks/cloudflare-workers.d.mts +24 -0
- package/dist/mocks/cloudflare-workers.d.mts.map +1 -0
- package/dist/mocks/cloudflare-workers.mjs +28 -0
- package/dist/mocks/cloudflare-workers.mjs.map +1 -0
- package/dist/mocks/index.d.mts +2 -2
- package/dist/mocks/index.mjs +2 -2
- package/dist/mocks/noop-rate-limiter-store.d.mts +1 -3
- package/dist/mocks/noop-rate-limiter-store.d.mts.map +1 -1
- package/dist/mocks/zenstack-language.d.mts +26 -29
- package/dist/mocks/zenstack-language.d.mts.map +1 -1
- package/dist/mocks/zenstack-language.mjs +1 -1
- package/dist/mocks/zenstack-language.mjs.map +1 -1
- package/dist/storage/index.d.mts +1 -1
- package/dist/storage/index.mjs +1 -1
- package/dist/{storage-DhoxWqyF.mjs → storage-BiGamMA8.mjs} +73 -6
- package/dist/storage-BiGamMA8.mjs.map +1 -0
- package/dist/test-worker-exports-dCRARYEc.d.mts +144 -0
- package/dist/test-worker-exports-dCRARYEc.d.mts.map +1 -0
- package/dist/test-workers-cache-D02Pt_dE.mjs +183 -0
- package/dist/test-workers-cache-D02Pt_dE.mjs.map +1 -0
- package/dist/vitest-plugin/index.d.mts +14 -28
- package/dist/vitest-plugin/index.d.mts.map +1 -1
- package/dist/vitest-plugin/index.mjs +32 -20
- package/dist/vitest-plugin/index.mjs.map +1 -1
- package/package.json +30 -21
- package/dist/database-B02eYKhE.mjs +0 -334
- package/dist/database-B02eYKhE.mjs.map +0 -1
- package/dist/index-BIr5nLof.d.mts +0 -122
- package/dist/index-BIr5nLof.d.mts.map +0 -1
- package/dist/index-CrHzUDKX.d.mts.map +0 -1
- package/dist/storage-DhoxWqyF.mjs.map +0 -1
- package/dist/test-email-provider-B7cjj97-.d.mts +0 -21
- package/dist/test-email-provider-B7cjj97-.d.mts.map +0 -1
- package/dist/test-email-provider-Dr-nhE0x.mjs +0 -34
- package/dist/test-email-provider-Dr-nhE0x.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-workers-cache-D02Pt_dE.mjs","names":[],"sources":["../src/mocks/test-email-provider.ts","../src/mocks/test-worker-exports.ts","../src/mocks/test-workers-cache.ts"],"sourcesContent":["import type {\n\tEmailBatchSendResult,\n\tEmailSendResult,\n\tIEmailProvider,\n\tResolvedEmailMessage,\n} from 'stratal/email'\n\n/**\n * In-memory email provider for tests.\n *\n * The sync queue provider runs `EmailConsumer` inline on dispatch, which would\n * otherwise open a real SMTP connection from the test worker. The testing\n * module builder installs this provider by default (overridable via\n * `overrideProvider(EMAIL_TOKENS.EmailProviderFactory)`), recording every\n * message so tests can assert on what was sent.\n */\nexport class TestEmailProvider implements IEmailProvider {\n\t/** Every message handed to the provider, in send order. */\n\treadonly sent: ResolvedEmailMessage[] = []\n\n\tsend(message: ResolvedEmailMessage): Promise<EmailSendResult> {\n\t\tthis.sent.push(message)\n\t\treturn Promise.resolve({ messageId: `test-${this.sent.length}`, accepted: true })\n\t}\n\n\tasync sendBatch(messages: ResolvedEmailMessage[]): Promise<EmailBatchSendResult> {\n\t\tconst results = await Promise.all(messages.map((message) => this.send(message)))\n\t\treturn {\n\t\t\ttotal: results.length,\n\t\t\tsuccessful: results.length,\n\t\t\tfailed: 0,\n\t\t\tresults,\n\t\t}\n\t}\n}\n","import type { PurgeSpec } from 'stratal/response-cache'\nimport type { TestWorkersCache } from './test-workers-cache'\n\n/** One `ctx.exports.<Name>.fetch()` the gateway made, recorded for assertion. */\nexport interface LoopbackCall {\n /** The export name the gateway dispatched to — `gateway: { entrypoint }`. */\n entrypoint: string\n method: string\n url: string\n /** The resolved partitions the gateway placed in `ctx.props`. */\n props: Record<string, unknown>\n}\n\n/** Runs a loopback in cached mode. Installed by the testing module builder. */\nexport type LoopbackHandler = (\n request: Request,\n props: Record<string, unknown>,\n) => Promise<Response>\n\n/**\n * In-memory stand-in for `ctx.exports` — the loopback bindings a Worker has to\n * its own top-level exports.\n *\n * Neither Miniflare nor workerd populates `ExecutionContext.exports` for a\n * `Test.createTestingModule()` app, so without this every consumer that adopts\n * `ResponseCacheModule.forRoot({ gateway: { entrypoint } })` would have their\n * whole suite fail on the first request: the gateway's boot verification\n * throws `ResponseCacheConfigError` the moment it cannot reach the configured\n * entrypoint. That is the same usability defect {@link TestWorkersCache} fixes\n * for `ctx.cache`, and it is fixed the same way — installed by default, opt out\n * with `Test.createTestingModule({ cache: false })`.\n *\n * The stub is not inert. A dispatched request is re-run through the *same*\n * Hono app with an unmarked execution context carrying the gateway's chosen\n * `props` — exactly what the real cached entrypoint does — so a partitioned\n * route is genuinely exercised end to end rather than short-circuited. Every\n * call is recorded, in order, for assertion via `module.gateway.loopbacks`,\n * mirroring `module.cache.purges`:\n *\n * ```typescript\n * await module.http.get('/dashboard').withHeaders({ ... }).send()\n * expect(module.gateway.loopbacks).toEqual([\n * { entrypoint: 'Cached', method: 'GET', url: '...', props: { user: 'u-1' } },\n * ])\n * ```\n *\n * An empty `loopbacks` after a request to a partitioned route is itself the\n * assertion for the fail-closed cases — an unresolved partition, a non-GET, a\n * route with no `partitionBy` — all of which must run inline.\n *\n * Purges forwarded over RPC (`ctx.exports.<Name>.purge(spec)`) land on the same\n * {@link TestWorkersCache} as a direct `ctx.cache.purge()`, so\n * `module.cache.purges` reads identically whether or not a gateway is\n * configured.\n */\nexport class TestWorkerExports {\n /** Every loopback dispatch, in call order. */\n readonly loopbacks: LoopbackCall[] = []\n\n private handler: LoopbackHandler | undefined\n private readonly stubs = new Map<string, unknown>()\n\n constructor(private readonly cache: TestWorkersCache | undefined) {}\n\n /**\n * Install the function that actually serves a loopback.\n *\n * Set after the `Application` exists, because the handler needs its Hono\n * app — while the execution context carrying this stub has to be built\n * before it, to be passed to the constructor.\n *\n * @internal\n */\n setHandler(handler: LoopbackHandler): void {\n this.handler = handler\n }\n\n /**\n * The object to install as `ctx.exports`.\n *\n * A `Proxy` rather than a fixed record because the export name is the\n * consumer's to choose — the framework only ever sees the string in\n * `gateway: { entrypoint }`. Answering for every name keeps a typo'd\n * entrypoint from being caught here, in a stub, instead of in the\n * consumer's real Wrangler config where it matters; that trade is\n * deliberate, since a suite failing on a name the test never mentions is\n * far more confusing than a deploy-time error.\n *\n * A typo is meant to be caught earlier still: `gateway.entrypoint` is typed\n * as `CachedEntrypointName`, so once the consumer has run `wrangler types` a\n * wrong name is a compile error at the config site — before this stub, and\n * before deploy. This Proxy is the runtime fallback for projects without\n * generated types, not the primary guard.\n */\n get context(): Record<string, unknown> {\n return new Proxy(\n {},\n {\n get: (_target, property) =>\n typeof property === 'string' ? this.stubFor(property) : undefined,\n has: () => true,\n ownKeys: () => [...this.stubs.keys()],\n getOwnPropertyDescriptor: () => ({ enumerable: true, configurable: true }),\n },\n )\n }\n\n /**\n * A loopback binding, shaped like the real one:\n * `Fetcher<T> & ((opts: { props }) => Fetcher<T>)`. Both the callable form\n * (which is how `ctx.props` is chosen) and the direct form are supported,\n * so whichever the framework uses is genuinely exercised.\n */\n private stubFor(entrypoint: string): unknown {\n const existing = this.stubs.get(entrypoint)\n if (existing) return existing\n\n const bound = (props: Record<string, unknown>) => ({\n fetch: (request: Request) => this.dispatch(entrypoint, request, props),\n purge: (spec: PurgeSpec) => this.purge(spec),\n })\n\n const stub = Object.assign(\n (options?: { props?: Record<string, unknown> }) => bound(options?.props ?? {}),\n bound({}),\n )\n\n this.stubs.set(entrypoint, stub)\n return stub\n }\n\n private async dispatch(\n entrypoint: string,\n request: Request,\n props: Record<string, unknown>,\n ): Promise<Response> {\n this.loopbacks.push({ entrypoint, method: request.method, url: request.url, props })\n\n if (!this.handler) {\n throw new Error(\n '[stratal:testing] A gateway loopback was dispatched before the testing module finished ' +\n 'compiling. This is a bug in @stratal/testing — please report it.',\n )\n }\n\n return this.handler(request, props)\n }\n\n private async purge(spec: PurgeSpec): Promise<{ success: boolean }> {\n // Recorded on the same stub a direct `ctx.cache.purge()` writes to, so\n // `module.cache.purges` reads the same with or without a gateway.\n await this.cache?.purge(spec)\n return { success: true }\n }\n}\n","import type { PurgeSpec, WorkersCache } from 'stratal/response-cache'\n\n/**\n * In-memory stand-in for Cloudflare Workers Caching's `ctx.cache`.\n *\n * Neither Miniflare nor workerd populates `ExecutionContext.cache` locally,\n * which would otherwise leave every `@Cacheable` route in a test serving\n * uncached and stamped `private, no-store` — so a spec asserting the headers\n * the route declares would read the ones an unconfigured runtime produces. The\n * testing module builder installs this by default so those routes are testable\n * with zero configuration: `@Cacheable` responses carry real\n * `Cache-Control`/`Cache-Tag` headers, and `@PurgesCache` purges are recorded\n * rather than skipped.\n *\n * Every spec passed to `purge()` is recorded, in call order, for assertion\n * via `module.cache.purges`. Purges always succeed — this stub only proves an\n * app's cache configuration is correct, not Workers Caching's own failure\n * modes (that's covered inside `stratal` itself, against a mocked\n * `WorkersCache`). Opt out with `Test.createTestingModule({ cache: false })`\n * to reproduce a runtime where Workers Caching is genuinely unconfigured —\n * e.g. to test the `ResponseCacheConfigError` boot guard.\n */\nexport class TestWorkersCache implements WorkersCache {\n /** Every spec passed to `purge()`, in call order. */\n readonly purges: PurgeSpec[] = []\n\n purge(spec: PurgeSpec): Promise<{ success: boolean }> {\n this.purges.push(spec)\n return Promise.resolve({ success: true })\n }\n}\n"],"mappings":";;;;;;;;;;AAgBA,IAAa,oBAAb,MAAyD;;CAExD,OAAwC,CAAC;CAEzC,KAAK,SAAyD;EAC7D,KAAK,KAAK,KAAK,OAAO;EACtB,OAAO,QAAQ,QAAQ;GAAE,WAAW,QAAQ,KAAK,KAAK;GAAU,UAAU;EAAK,CAAC;CACjF;CAEA,MAAM,UAAU,UAAiE;EAChF,MAAM,UAAU,MAAM,QAAQ,IAAI,SAAS,KAAK,YAAY,KAAK,KAAK,OAAO,CAAC,CAAC;EAC/E,OAAO;GACN,OAAO,QAAQ;GACf,YAAY,QAAQ;GACpB,QAAQ;GACR;EACD;CACD;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACqBA,IAAa,oBAAb,MAA+B;CAOA;;CAL7B,YAAqC,CAAC;CAEtC;CACA,wBAAyB,IAAI,IAAqB;CAElD,YAAY,OAAsD;EAArC,KAAA,QAAA;CAAsC;;;;;;;;;;CAWnE,WAAW,SAAgC;EACzC,KAAK,UAAU;CACjB;;;;;;;;;;;;;;;;;;CAmBA,IAAI,UAAmC;EACrC,OAAO,IAAI,MACT,CAAC,GACD;GACE,MAAM,SAAS,aACb,OAAO,aAAa,WAAW,KAAK,QAAQ,QAAQ,IAAI,KAAA;GAC1D,WAAW;GACX,eAAe,CAAC,GAAG,KAAK,MAAM,KAAK,CAAC;GACpC,iCAAiC;IAAE,YAAY;IAAM,cAAc;GAAK;EAC1E,CACF;CACF;;;;;;;CAQA,QAAgB,YAA6B;EAC3C,MAAM,WAAW,KAAK,MAAM,IAAI,UAAU;EAC1C,IAAI,UAAU,OAAO;EAErB,MAAM,SAAS,WAAoC;GACjD,QAAQ,YAAqB,KAAK,SAAS,YAAY,SAAS,KAAK;GACrE,QAAQ,SAAoB,KAAK,MAAM,IAAI;EAC7C;EAEA,MAAM,OAAO,OAAO,QACjB,YAAkD,MAAM,SAAS,SAAS,CAAC,CAAC,GAC7E,MAAM,CAAC,CAAC,CACV;EAEA,KAAK,MAAM,IAAI,YAAY,IAAI;EAC/B,OAAO;CACT;CAEA,MAAc,SACZ,YACA,SACA,OACmB;EACnB,KAAK,UAAU,KAAK;GAAE;GAAY,QAAQ,QAAQ;GAAQ,KAAK,QAAQ;GAAK;EAAM,CAAC;EAEnF,IAAI,CAAC,KAAK,SACR,MAAM,IAAI,MACR,yJAEF;EAGF,OAAO,KAAK,QAAQ,SAAS,KAAK;CACpC;CAEA,MAAc,MAAM,MAAgD;EAGlE,MAAM,KAAK,OAAO,MAAM,IAAI;EAC5B,OAAO,EAAE,SAAS,KAAK;CACzB;AACF;;;;;;;;;;;;;;;;;;;;;;;ACpIA,IAAa,mBAAb,MAAsD;;CAEpD,SAA+B,CAAC;CAEhC,MAAM,MAAgD;EACpD,KAAK,OAAO,KAAK,IAAI;EACrB,OAAO,QAAQ,QAAQ,EAAE,SAAS,KAAK,CAAC;CAC1C;AACF"}
|
|
@@ -1,35 +1,21 @@
|
|
|
1
|
-
import { r as DatabaseIsolation } from "../index-BIr5nLof.mjs";
|
|
2
1
|
import { StratalEnv } from "stratal";
|
|
3
|
-
import { cloudflareTest } from "@cloudflare/vitest-
|
|
2
|
+
import { cloudflareTest } from "@cloudflare/vitest-plugin";
|
|
4
3
|
import { Plugin } from "vite";
|
|
5
|
-
|
|
6
4
|
//#region src/vitest-plugin/stratal-test.d.ts
|
|
7
5
|
type CloudflareTestOptions = Parameters<typeof cloudflareTest>[0];
|
|
8
6
|
/** String keys of `StratalEnv` whose value is a Hyperdrive binding. */
|
|
9
|
-
type HyperdriveKeys = Extract<{ [K in keyof StratalEnv]-?: StratalEnv[K] extends Hyperdrive ? K : never }[keyof StratalEnv], string>;
|
|
7
|
+
type HyperdriveKeys = Extract<{ [K in keyof StratalEnv]-?: StratalEnv[K] extends Hyperdrive ? K : never; }[keyof StratalEnv], string>;
|
|
10
8
|
/**
|
|
11
9
|
* Names of declared Hyperdrive bindings, drawn from the consumer's augmented
|
|
12
10
|
* `StratalEnv` (which extends `Cloudflare.Env`). Falls back to `string` only
|
|
13
11
|
* when no Hyperdrive binding is declared (nothing to constrain to).
|
|
14
12
|
*/
|
|
15
13
|
type HyperdriveBindingName = [HyperdriveKeys] extends [never] ? string : HyperdriveKeys;
|
|
16
|
-
/** Stratal-
|
|
14
|
+
/** Stratal per-worker database configuration for {@link stratalTest}. */
|
|
17
15
|
interface StratalTestDatabaseOptions {
|
|
18
16
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* - `'shared'` — all test files share one database (serial; today's behaviour).
|
|
22
|
-
* - `'database'` — each test file gets its own database cloned from a migrated
|
|
23
|
-
* template (dropped on teardown), and file parallelism is enabled.
|
|
24
|
-
*
|
|
25
|
-
* Pair with `createTestDatabaseGlobalSetup({ isolation })` from
|
|
26
|
-
* `@stratal/testing/database` in your Vitest `globalSetup`.
|
|
27
|
-
*/
|
|
28
|
-
isolation?: DatabaseIsolation;
|
|
29
|
-
/**
|
|
30
|
-
* Name of the Hyperdrive binding to isolate per test file. Defaults to `'DB'`.
|
|
31
|
-
* Constrained to Hyperdrive binding names declared on `Cloudflare.Env` /
|
|
32
|
-
* `StratalEnv`.
|
|
17
|
+
* Name of the Hyperdrive binding to point at this worker's database.
|
|
18
|
+
* Defaults to `'DB'`. Constrained to declared Hyperdrive bindings.
|
|
33
19
|
*/
|
|
34
20
|
binding?: HyperdriveBindingName;
|
|
35
21
|
}
|
|
@@ -47,7 +33,7 @@ type StratalTestOptions = WorkersPoolOptions & {
|
|
|
47
33
|
* root Vite instance doesn't resolve.
|
|
48
34
|
*
|
|
49
35
|
* Must be used at the **root** `defineConfig` level so that the
|
|
50
|
-
* `@cloudflare/vitest-
|
|
36
|
+
* `@cloudflare/vitest-plugin` module fallback resolver (which uses the root Vite
|
|
51
37
|
* instance) resolves pg sub-deps correctly.
|
|
52
38
|
*
|
|
53
39
|
* @example
|
|
@@ -66,7 +52,7 @@ type StratalTestOptions = WorkersPoolOptions & {
|
|
|
66
52
|
* })
|
|
67
53
|
* ```
|
|
68
54
|
*/
|
|
69
|
-
declare const fixPgCjs: () => Plugin;
|
|
55
|
+
export declare const fixPgCjs: () => Plugin;
|
|
70
56
|
/**
|
|
71
57
|
* Returns a Vite plugin that forces CJS resolution for `@noble/hashes` subpaths
|
|
72
58
|
* used by `@paralleldrive/cuid2@2.x` (the version `@zenstackhq/orm` depends on).
|
|
@@ -96,21 +82,21 @@ declare const fixPgCjs: () => Plugin;
|
|
|
96
82
|
* })
|
|
97
83
|
* ```
|
|
98
84
|
*/
|
|
99
|
-
declare const fixNobleHashesCjs: () => Plugin;
|
|
85
|
+
export declare const fixNobleHashesCjs: () => Plugin;
|
|
100
86
|
/**
|
|
101
87
|
* Returns Vite plugins for Stratal tests running in the Cloudflare Workers (workerd) environment.
|
|
102
88
|
*
|
|
103
|
-
* Includes the cloudflare pool plugin and Stratal alias plugin. Pass
|
|
104
|
-
* `database
|
|
105
|
-
*
|
|
106
|
-
*
|
|
89
|
+
* Includes the cloudflare pool plugin and Stratal alias plugin. Pass a
|
|
90
|
+
* `database` option to give each test file a leased database holding a fresh
|
|
91
|
+
* clone of a migrated template, and to enable file parallelism. Use inside a
|
|
92
|
+
* project-level `plugins` array.
|
|
107
93
|
*
|
|
108
94
|
* **Note:** `fixPgCjs()` must be registered separately at the root `defineConfig` level.
|
|
109
95
|
*
|
|
110
96
|
* @param options - `cloudflareTest()` options plus Stratal `database` options
|
|
111
97
|
* @returns An array of Vite plugins
|
|
112
98
|
*/
|
|
113
|
-
declare function stratalTest(options?: StratalTestOptions): Plugin[];
|
|
99
|
+
export declare function stratalTest(options?: StratalTestOptions): Plugin[];
|
|
114
100
|
//#endregion
|
|
115
|
-
export {
|
|
101
|
+
export type { StratalTestDatabaseOptions };
|
|
116
102
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vitest-plugin/stratal-test.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vitest-plugin/stratal-test.ts"],"mappings":";;;;KAYK,wBAAwB,kBAAkB;;KAG1C,iBAAiB,WACjB,WAAW,eAAe,WAAW,WAAW,aAAa,mBAAkB;;;;;;KAS/E,yBAAyB,2CAA2C;;UAGxD;;;;;EAKf,UAAU;;KAGP,qBAAqB,QAAQ,2BAA2B;KACxD,qBAAqB;EAAuB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsC/C,gBAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2Cf,yBAAwB;;;;;;;;;;;;;;wBAwFrB,YAAY,UAAS,qBAA0B"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as BINDING_ENV_VAR } from "../database-Rojs69r3.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { DB_SHARED_POOL_ENV } from "@stratal/framework/database";
|
|
5
|
+
import { cloudflareTest } from "@cloudflare/vitest-plugin";
|
|
5
6
|
//#region src/vitest-plugin/stratal-test.ts
|
|
6
7
|
const require = createRequire(import.meta.url);
|
|
7
|
-
const pgCjsResolvers = new Map([
|
|
8
|
+
const pgCjsResolvers = /* @__PURE__ */ new Map([
|
|
8
9
|
["pg-protocol", () => require.resolve("pg-protocol")],
|
|
9
10
|
["pg-connection-string", () => require.resolve("pg-connection-string")],
|
|
10
11
|
["pg-pool", () => require.resolve("pg-pool")],
|
|
@@ -20,7 +21,7 @@ const pgCjsResolvers = new Map([
|
|
|
20
21
|
* root Vite instance doesn't resolve.
|
|
21
22
|
*
|
|
22
23
|
* Must be used at the **root** `defineConfig` level so that the
|
|
23
|
-
* `@cloudflare/vitest-
|
|
24
|
+
* `@cloudflare/vitest-plugin` module fallback resolver (which uses the root Vite
|
|
24
25
|
* instance) resolves pg sub-deps correctly.
|
|
25
26
|
*
|
|
26
27
|
* @example
|
|
@@ -95,7 +96,8 @@ const fixNobleHashesCjs = () => {
|
|
|
95
96
|
} catch {
|
|
96
97
|
return;
|
|
97
98
|
}
|
|
98
|
-
const
|
|
99
|
+
const cuid2Path = createRequire(zenstackPath).resolve("@paralleldrive/cuid2");
|
|
100
|
+
const cuid2Require = createRequire(cuid2Path);
|
|
99
101
|
resolved = new Map(ids.map((id) => [id, cuid2Require.resolve(id)]));
|
|
100
102
|
},
|
|
101
103
|
resolveId(id) {
|
|
@@ -103,9 +105,10 @@ const fixNobleHashesCjs = () => {
|
|
|
103
105
|
}
|
|
104
106
|
};
|
|
105
107
|
};
|
|
106
|
-
|
|
108
|
+
/** Builds the Stratal defaults plugin returned by {@link stratalTest}; exported for direct unit testing. */
|
|
109
|
+
const createStratalPlugin = (databaseEnabled) => ({
|
|
107
110
|
name: "stratal-test",
|
|
108
|
-
config() {
|
|
111
|
+
config(userConfig) {
|
|
109
112
|
const config = {
|
|
110
113
|
resolve: { alias: {
|
|
111
114
|
tslib: "tslib/tslib.es6.mjs",
|
|
@@ -115,20 +118,25 @@ const createStratalPlugin = (isolation) => ({
|
|
|
115
118
|
} },
|
|
116
119
|
ssr: { noExternal: ["@zenstackhq/better-auth"] }
|
|
117
120
|
};
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
isolate: true
|
|
121
|
-
|
|
121
|
+
if (databaseEnabled) {
|
|
122
|
+
const existing = userConfig.test ?? {};
|
|
123
|
+
if (existing.isolate === false) throw new Error("[stratal-testing] stratalTest({ database }) needs `isolate: true`. Each test file leases its database for the life of its isolate, so files sharing an isolate would share a database. Remove `isolate: false` from this project.");
|
|
124
|
+
config.test = {
|
|
125
|
+
fileParallelism: existing.fileParallelism ?? true,
|
|
126
|
+
isolate: true,
|
|
127
|
+
hookTimeout: existing.hookTimeout ?? 3e4
|
|
128
|
+
};
|
|
129
|
+
}
|
|
122
130
|
return config;
|
|
123
131
|
}
|
|
124
132
|
});
|
|
125
133
|
/**
|
|
126
134
|
* Returns Vite plugins for Stratal tests running in the Cloudflare Workers (workerd) environment.
|
|
127
135
|
*
|
|
128
|
-
* Includes the cloudflare pool plugin and Stratal alias plugin. Pass
|
|
129
|
-
* `database
|
|
130
|
-
*
|
|
131
|
-
*
|
|
136
|
+
* Includes the cloudflare pool plugin and Stratal alias plugin. Pass a
|
|
137
|
+
* `database` option to give each test file a leased database holding a fresh
|
|
138
|
+
* clone of a migrated template, and to enable file parallelism. Use inside a
|
|
139
|
+
* project-level `plugins` array.
|
|
132
140
|
*
|
|
133
141
|
* **Note:** `fixPgCjs()` must be registered separately at the root `defineConfig` level.
|
|
134
142
|
*
|
|
@@ -137,19 +145,23 @@ const createStratalPlugin = (isolation) => ({
|
|
|
137
145
|
*/
|
|
138
146
|
function stratalTest(options = {}) {
|
|
139
147
|
const { database, ...cfOptions } = options;
|
|
140
|
-
const
|
|
148
|
+
const databaseEnabled = database !== void 0;
|
|
141
149
|
const binding = database?.binding ?? "DB";
|
|
142
|
-
|
|
150
|
+
const dbEnv = databaseEnabled ? {
|
|
151
|
+
[BINDING_ENV_VAR]: binding,
|
|
152
|
+
[DB_SHARED_POOL_ENV]: "true"
|
|
153
|
+
} : {};
|
|
154
|
+
const merged = {
|
|
143
155
|
...cfOptions,
|
|
144
156
|
miniflare: {
|
|
145
157
|
...cfOptions.miniflare,
|
|
146
158
|
bindings: {
|
|
147
159
|
...cfOptions.miniflare?.bindings,
|
|
148
|
-
|
|
149
|
-
[BINDING_ENV_VAR]: binding
|
|
160
|
+
...dbEnv
|
|
150
161
|
}
|
|
151
162
|
}
|
|
152
|
-
}
|
|
163
|
+
};
|
|
164
|
+
return [cloudflareTest(merged), createStratalPlugin(databaseEnabled)];
|
|
153
165
|
}
|
|
154
166
|
//#endregion
|
|
155
167
|
export { fixNobleHashesCjs, fixPgCjs, stratalTest };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vitest-plugin/stratal-test.ts"],"sourcesContent":["import { createRequire } from 'node:module'\nimport path from 'node:path'\n\nimport { cloudflareTest } from '@cloudflare/vitest-pool-workers'\nimport type { StratalEnv } from 'stratal'\nimport type { Plugin, UserConfig } from 'vite'\nimport type { TestUserConfig } from 'vitest/config'\nimport { BINDING_ENV_VAR, DEFAULT_DB_BINDING, ISOLATION_ENV_VAR, normalizeIsolation, type DatabaseIsolation } from '../database'\n\nconst require = createRequire(import.meta.url)\n\ntype CloudflareTestOptions = Parameters<typeof cloudflareTest>[0]\n\n/** String keys of `StratalEnv` whose value is a Hyperdrive binding. */\ntype HyperdriveKeys = Extract<\n { [K in keyof StratalEnv]-?: StratalEnv[K] extends Hyperdrive ? K : never }[keyof StratalEnv],\n string\n>\n\n/**\n * Names of declared Hyperdrive bindings, drawn from the consumer's augmented\n * `StratalEnv` (which extends `Cloudflare.Env`). Falls back to `string` only\n * when no Hyperdrive binding is declared (nothing to constrain to).\n */\ntype HyperdriveBindingName = [HyperdriveKeys] extends [never] ? string : HyperdriveKeys\n\n/** Stratal-specific test database configuration for {@link stratalTest}. */\nexport interface StratalTestDatabaseOptions {\n /**\n * Database isolation mode for parallel runs. Defaults to `'shared'`.\n *\n * - `'shared'` — all test files share one database (serial; today's behaviour).\n * - `'database'` — each test file gets its own database cloned from a migrated\n * template (dropped on teardown), and file parallelism is enabled.\n *\n * Pair with `createTestDatabaseGlobalSetup({ isolation })` from\n * `@stratal/testing/database` in your Vitest `globalSetup`.\n */\n isolation?: DatabaseIsolation\n /**\n * Name of the Hyperdrive binding to isolate per test file. Defaults to `'DB'`.\n * Constrained to Hyperdrive binding names declared on `Cloudflare.Env` /\n * `StratalEnv`.\n */\n binding?: HyperdriveBindingName\n}\n\ntype WorkersPoolOptions = Exclude<CloudflareTestOptions, (...args: never[]) => unknown>\ntype StratalTestOptions = WorkersPoolOptions & { database?: StratalTestDatabaseOptions }\n\nconst pgCjsResolvers = new Map<string, () => string>([\n ['pg-protocol', () => require.resolve('pg-protocol')],\n ['pg-connection-string', () => require.resolve('pg-connection-string')],\n ['pg-pool', () => require.resolve('pg-pool')],\n ['pg-cloudflare', () => path.join(path.dirname(require.resolve('pg-cloudflare')), 'index.js')],\n])\n\n/**\n * Returns a Vite plugin that forces CJS resolution for `pg` sub-dependencies.\n *\n * `pg` is CJS but its dependencies (`pg-protocol`, `pg-connection-string`, `pg-pool`) ship\n * dual CJS/ESM exports. In workerd, the module fallback resolver prefers the ESM condition,\n * causing `SyntaxError: Cannot use import statement outside a module` when CJS `pg` does\n * `require()`. Additionally, `pg-cloudflare` uses a `workerd` export condition that the\n * root Vite instance doesn't resolve.\n *\n * Must be used at the **root** `defineConfig` level so that the\n * `@cloudflare/vitest-pool-workers` module fallback resolver (which uses the root Vite\n * instance) resolves pg sub-deps correctly.\n *\n * @example\n * ```ts\n * import { fixPgCjs, stratalTest } from '@stratal/testing/vitest-plugin'\n * import { defineConfig } from 'vitest/config'\n *\n * export default defineConfig({\n * plugins: [fixPgCjs()],\n * test: {\n * projects: [{\n * plugins: [stratalTest({ wrangler: { configPath: './wrangler.jsonc' } })],\n * test: { name: 'e2e', include: ['test/e2e/**\\/*.spec.ts'] },\n * }],\n * },\n * })\n * ```\n */\nexport const fixPgCjs = (): Plugin => ({\n name: 'stratal-pg-cjs',\n enforce: 'pre',\n resolveId(id) {\n const resolver = pgCjsResolvers.get(id)\n if (!resolver) return\n try {\n return resolver()\n } catch {\n return\n }\n },\n})\n\n/**\n * Returns a Vite plugin that forces CJS resolution for `@noble/hashes` subpaths\n * used by `@paralleldrive/cuid2@2.x` (the version `@zenstackhq/orm` depends on).\n *\n * cuid2@2.x is CJS and does `require(\"@noble/hashes/sha3\")` without the `.js`\n * extension. In a workspace that also installs `@noble/hashes@2.x` (ESM-only),\n * the hoisted v2 package has no extensionless `./sha3` entry in its exports\n * map, so Vite/workerd resolution fails. This plugin routes the extensionless\n * subpaths through the consumer's nested `@noble/hashes@1.x` (a sibling of\n * cuid2 under `@zenstackhq/orm/node_modules`), which ships proper CJS exports.\n *\n * If the consumer doesn't depend on `@zenstackhq/orm`, the plugin is a no-op.\n * Otherwise it throws loudly at config-resolution time if the resolution chain\n * is unexpectedly broken — surfacing dependency drift instead of letting the\n * symptom resurface as an opaque test failure.\n *\n * Must be used at the **root** `defineConfig` level (same constraint as\n * `fixPgCjs`).\n *\n * @example\n * ```ts\n * import { fixNobleHashesCjs, fixPgCjs, stratalTest } from '@stratal/testing/vitest-plugin'\n *\n * export default defineConfig({\n * plugins: [fixPgCjs(), fixNobleHashesCjs()],\n * // ...\n * })\n * ```\n */\nexport const fixNobleHashesCjs = (): Plugin => {\n const ids = ['@noble/hashes/sha3', '@noble/hashes/crypto']\n let resolved: Map<string, string> | null = null\n\n return {\n name: 'stratal-noble-hashes-cjs',\n enforce: 'pre',\n configResolved(config) {\n const consumerRequire = createRequire(path.join(config.root, 'noop.js'))\n let zenstackPath: string\n try {\n zenstackPath = consumerRequire.resolve('@zenstackhq/orm')\n } catch {\n // Consumer doesn't use ZenStack — nothing to fix.\n return\n }\n const cuid2Path = createRequire(zenstackPath).resolve('@paralleldrive/cuid2')\n const cuid2Require = createRequire(cuid2Path)\n resolved = new Map<string, string>(\n ids.map((id) => [id, cuid2Require.resolve(id)]),\n )\n },\n resolveId(id) {\n return resolved?.get(id)\n },\n }\n}\n\nconst createStratalPlugin = (isolation: DatabaseIsolation): Plugin => ({\n name: 'stratal-test',\n config() {\n const config: UserConfig & { test?: TestUserConfig } = {\n resolve: {\n alias: {\n tslib: 'tslib/tslib.es6.mjs',\n '@zenstackhq/language/ast': '@stratal/testing/mocks/zenstack-language',\n '@zenstackhq/language/utils': '@stratal/testing/mocks/zenstack-language',\n '@zenstackhq/language': '@stratal/testing/mocks/zenstack-language',\n },\n },\n ssr: {\n noExternal: ['@zenstackhq/better-auth'],\n },\n }\n\n // In 'database' mode each test file owns its database, so enable file\n // parallelism + isolation. In 'shared' mode leave these untouched so the\n // project's own defaults stand (forcing them would alter maxWorkers and\n // can collide with sibling projects' sequence.groupOrder).\n if (isolation === 'database') {\n config.test = { fileParallelism: true, isolate: true }\n }\n\n return config\n },\n})\n\n/**\n * Returns Vite plugins for Stratal tests running in the Cloudflare Workers (workerd) environment.\n *\n * Includes the cloudflare pool plugin and Stratal alias plugin. Pass\n * `database: { isolation: 'database' }` to give each test file its own\n * database (cloned from a migrated template, dropped on teardown) and enable\n * file parallelism. Use inside a project-level `plugins` array.\n *\n * **Note:** `fixPgCjs()` must be registered separately at the root `defineConfig` level.\n *\n * @param options - `cloudflareTest()` options plus Stratal `database` options\n * @returns An array of Vite plugins\n */\nexport function stratalTest(options: StratalTestOptions = {}): Plugin[] {\n const { database, ...cfOptions } = options\n const isolation = normalizeIsolation(database?.isolation)\n const binding = database?.binding ?? DEFAULT_DB_BINDING\n\n // Expose the mode + binding name to the worker as env vars so\n // TestingModule.compile() can decide whether and what to provision.\n const merged = {\n ...cfOptions,\n miniflare: {\n ...cfOptions.miniflare,\n bindings: {\n ...(cfOptions.miniflare?.bindings as Record<string, unknown> | undefined),\n [ISOLATION_ENV_VAR]: isolation,\n [BINDING_ENV_VAR]: binding,\n },\n },\n }\n\n return [cloudflareTest(merged) as unknown as Plugin, createStratalPlugin(isolation)]\n}\n"],"mappings":";;;;;AASA,MAAM,UAAU,cAAc,OAAO,KAAK,GAAG;AAyC7C,MAAM,iBAAiB,IAAI,IAA0B;CACnD,CAAC,qBAAqB,QAAQ,QAAQ,aAAa,CAAC;CACpD,CAAC,8BAA8B,QAAQ,QAAQ,sBAAsB,CAAC;CACtE,CAAC,iBAAiB,QAAQ,QAAQ,SAAS,CAAC;CAC5C,CAAC,uBAAuB,KAAK,KAAK,KAAK,QAAQ,QAAQ,QAAQ,eAAe,CAAC,GAAG,UAAU,CAAC;AAC/F,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BD,MAAa,kBAA0B;CACrC,MAAM;CACN,SAAS;CACT,UAAU,IAAI;EACZ,MAAM,WAAW,eAAe,IAAI,EAAE;EACtC,IAAI,CAAC,UAAU;EACf,IAAI;GACF,OAAO,SAAS;EAClB,QAAQ;GACN;EACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,0BAAkC;CAC7C,MAAM,MAAM,CAAC,sBAAsB,sBAAsB;CACzD,IAAI,WAAuC;CAE3C,OAAO;EACL,MAAM;EACN,SAAS;EACT,eAAe,QAAQ;GACrB,MAAM,kBAAkB,cAAc,KAAK,KAAK,OAAO,MAAM,SAAS,CAAC;GACvE,IAAI;GACJ,IAAI;IACF,eAAe,gBAAgB,QAAQ,iBAAiB;GAC1D,QAAQ;IAEN;GACF;GAEA,MAAM,eAAe,cADH,cAAc,YAAY,EAAE,QAAQ,sBACX,CAAC;GAC5C,WAAW,IAAI,IACb,IAAI,KAAK,OAAO,CAAC,IAAI,aAAa,QAAQ,EAAE,CAAC,CAAC,CAChD;EACF;EACA,UAAU,IAAI;GACZ,OAAO,UAAU,IAAI,EAAE;EACzB;CACF;AACF;AAEA,MAAM,uBAAuB,eAA0C;CACrE,MAAM;CACN,SAAS;EACP,MAAM,SAAiD;GACrD,SAAS,EACP,OAAO;IACL,OAAO;IACP,4BAA4B;IAC5B,8BAA8B;IAC9B,wBAAwB;GAC1B,EACF;GACA,KAAK,EACH,YAAY,CAAC,yBAAyB,EACxC;EACF;EAMA,IAAI,cAAc,YAChB,OAAO,OAAO;GAAE,iBAAiB;GAAM,SAAS;EAAK;EAGvD,OAAO;CACT;AACF;;;;;;;;;;;;;;AAeA,SAAgB,YAAY,UAA8B,CAAC,GAAa;CACtE,MAAM,EAAE,UAAU,GAAG,cAAc;CACnC,MAAM,YAAY,mBAAmB,UAAU,SAAS;CACxD,MAAM,UAAU,UAAU,WAAA;CAgB1B,OAAO,CAAC,eAAe;EAXrB,GAAG;EACH,WAAW;GACT,GAAG,UAAU;GACb,UAAU;IACR,GAAI,UAAU,WAAW;KACxB,oBAAoB;KACpB,kBAAkB;GACrB;EACF;CAG0B,CAAC,GAAwB,oBAAoB,SAAS,CAAC;AACrF"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vitest-plugin/stratal-test.ts"],"sourcesContent":["import { createRequire } from 'node:module'\nimport path from 'node:path'\n\nimport { cloudflareTest } from '@cloudflare/vitest-plugin'\nimport type { StratalEnv } from 'stratal'\nimport type { Plugin, UserConfig } from 'vite'\nimport type { TestUserConfig } from 'vitest/config'\nimport { DB_SHARED_POOL_ENV } from '@stratal/framework/database'\nimport { BINDING_ENV_VAR, DEFAULT_DB_BINDING } from '../database'\n\nconst require = createRequire(import.meta.url)\n\ntype CloudflareTestOptions = Parameters<typeof cloudflareTest>[0]\n\n/** String keys of `StratalEnv` whose value is a Hyperdrive binding. */\ntype HyperdriveKeys = Extract<\n { [K in keyof StratalEnv]-?: StratalEnv[K] extends Hyperdrive ? K : never }[keyof StratalEnv],\n string\n>\n\n/**\n * Names of declared Hyperdrive bindings, drawn from the consumer's augmented\n * `StratalEnv` (which extends `Cloudflare.Env`). Falls back to `string` only\n * when no Hyperdrive binding is declared (nothing to constrain to).\n */\ntype HyperdriveBindingName = [HyperdriveKeys] extends [never] ? string : HyperdriveKeys\n\n/** Stratal per-worker database configuration for {@link stratalTest}. */\nexport interface StratalTestDatabaseOptions {\n /**\n * Name of the Hyperdrive binding to point at this worker's database.\n * Defaults to `'DB'`. Constrained to declared Hyperdrive bindings.\n */\n binding?: HyperdriveBindingName\n}\n\ntype WorkersPoolOptions = Exclude<CloudflareTestOptions, (...args: never[]) => unknown>\ntype StratalTestOptions = WorkersPoolOptions & { database?: StratalTestDatabaseOptions }\n\nconst pgCjsResolvers = new Map<string, () => string>([\n ['pg-protocol', () => require.resolve('pg-protocol')],\n ['pg-connection-string', () => require.resolve('pg-connection-string')],\n ['pg-pool', () => require.resolve('pg-pool')],\n ['pg-cloudflare', () => path.join(path.dirname(require.resolve('pg-cloudflare')), 'index.js')],\n])\n\n/**\n * Returns a Vite plugin that forces CJS resolution for `pg` sub-dependencies.\n *\n * `pg` is CJS but its dependencies (`pg-protocol`, `pg-connection-string`, `pg-pool`) ship\n * dual CJS/ESM exports. In workerd, the module fallback resolver prefers the ESM condition,\n * causing `SyntaxError: Cannot use import statement outside a module` when CJS `pg` does\n * `require()`. Additionally, `pg-cloudflare` uses a `workerd` export condition that the\n * root Vite instance doesn't resolve.\n *\n * Must be used at the **root** `defineConfig` level so that the\n * `@cloudflare/vitest-plugin` module fallback resolver (which uses the root Vite\n * instance) resolves pg sub-deps correctly.\n *\n * @example\n * ```ts\n * import { fixPgCjs, stratalTest } from '@stratal/testing/vitest-plugin'\n * import { defineConfig } from 'vitest/config'\n *\n * export default defineConfig({\n * plugins: [fixPgCjs()],\n * test: {\n * projects: [{\n * plugins: [stratalTest({ wrangler: { configPath: './wrangler.jsonc' } })],\n * test: { name: 'e2e', include: ['test/e2e/**\\/*.spec.ts'] },\n * }],\n * },\n * })\n * ```\n */\nexport const fixPgCjs = (): Plugin => ({\n name: 'stratal-pg-cjs',\n enforce: 'pre',\n resolveId(id) {\n const resolver = pgCjsResolvers.get(id)\n if (!resolver) return\n try {\n return resolver()\n } catch {\n return\n }\n },\n})\n\n/**\n * Returns a Vite plugin that forces CJS resolution for `@noble/hashes` subpaths\n * used by `@paralleldrive/cuid2@2.x` (the version `@zenstackhq/orm` depends on).\n *\n * cuid2@2.x is CJS and does `require(\"@noble/hashes/sha3\")` without the `.js`\n * extension. In a workspace that also installs `@noble/hashes@2.x` (ESM-only),\n * the hoisted v2 package has no extensionless `./sha3` entry in its exports\n * map, so Vite/workerd resolution fails. This plugin routes the extensionless\n * subpaths through the consumer's nested `@noble/hashes@1.x` (a sibling of\n * cuid2 under `@zenstackhq/orm/node_modules`), which ships proper CJS exports.\n *\n * If the consumer doesn't depend on `@zenstackhq/orm`, the plugin is a no-op.\n * Otherwise it throws loudly at config-resolution time if the resolution chain\n * is unexpectedly broken — surfacing dependency drift instead of letting the\n * symptom resurface as an opaque test failure.\n *\n * Must be used at the **root** `defineConfig` level (same constraint as\n * `fixPgCjs`).\n *\n * @example\n * ```ts\n * import { fixNobleHashesCjs, fixPgCjs, stratalTest } from '@stratal/testing/vitest-plugin'\n *\n * export default defineConfig({\n * plugins: [fixPgCjs(), fixNobleHashesCjs()],\n * // ...\n * })\n * ```\n */\nexport const fixNobleHashesCjs = (): Plugin => {\n const ids = ['@noble/hashes/sha3', '@noble/hashes/crypto']\n let resolved: Map<string, string> | null = null\n\n return {\n name: 'stratal-noble-hashes-cjs',\n enforce: 'pre',\n configResolved(config) {\n const consumerRequire = createRequire(path.join(config.root, 'noop.js'))\n let zenstackPath: string\n try {\n zenstackPath = consumerRequire.resolve('@zenstackhq/orm')\n } catch {\n // Consumer doesn't use ZenStack — nothing to fix.\n return\n }\n const cuid2Path = createRequire(zenstackPath).resolve('@paralleldrive/cuid2')\n const cuid2Require = createRequire(cuid2Path)\n resolved = new Map<string, string>(\n ids.map((id) => [id, cuid2Require.resolve(id)]),\n )\n },\n resolveId(id) {\n return resolved?.get(id)\n },\n }\n}\n\n/** Builds the Stratal defaults plugin returned by {@link stratalTest}; exported for direct unit testing. */\nexport const createStratalPlugin = (databaseEnabled: boolean): Plugin => ({\n name: 'stratal-test',\n config(userConfig: UserConfig & { test?: TestUserConfig }) {\n const config: UserConfig & { test?: TestUserConfig } = {\n resolve: {\n alias: {\n tslib: 'tslib/tslib.es6.mjs',\n '@zenstackhq/language/ast': '@stratal/testing/mocks/zenstack-language',\n '@zenstackhq/language/utils': '@stratal/testing/mocks/zenstack-language',\n '@zenstackhq/language': '@stratal/testing/mocks/zenstack-language',\n },\n },\n ssr: {\n noExternal: ['@zenstackhq/better-auth'],\n },\n }\n // Each test file leases a worker database for as long as its isolate lives\n // and gets a fresh clone of the migrated template in it. `isolate` is\n // required, not a default: the lease is released when the pool disposes the\n // file's isolate, so a shared isolate would hand the next file the previous\n // file's database, rows and all. Setup hooks do real DB work (the clone\n // itself — a `CREATE DATABASE ... TEMPLATE` serialized across concurrent\n // files by a Postgres advisory lock — plus per-test tenant/seed provisioning\n // in `beforeAll`) that routinely exceeds Vitest's 10s default hook timeout\n // under a full worker slot. `fileParallelism` and `hookTimeout` are\n // defaults, not floors: each only applies where the consuming project's own\n // `test` config leaves it unset.\n if (databaseEnabled) {\n const existing = userConfig.test ?? {}\n if (existing.isolate === false) {\n throw new Error(\n '[stratal-testing] stratalTest({ database }) needs `isolate: true`. Each test file leases its ' +\n 'database for the life of its isolate, so files sharing an isolate would share a database. ' +\n 'Remove `isolate: false` from this project.',\n )\n }\n config.test = {\n fileParallelism: existing.fileParallelism ?? true,\n isolate: true,\n hookTimeout: existing.hookTimeout ?? 30_000,\n }\n }\n return config\n },\n})\n\n/**\n * Returns Vite plugins for Stratal tests running in the Cloudflare Workers (workerd) environment.\n *\n * Includes the cloudflare pool plugin and Stratal alias plugin. Pass a\n * `database` option to give each test file a leased database holding a fresh\n * clone of a migrated template, and to enable file parallelism. Use inside a\n * project-level `plugins` array.\n *\n * **Note:** `fixPgCjs()` must be registered separately at the root `defineConfig` level.\n *\n * @param options - `cloudflareTest()` options plus Stratal `database` options\n * @returns An array of Vite plugins\n */\nexport function stratalTest(options: StratalTestOptions = {}): Plugin[] {\n const { database, ...cfOptions } = options\n const databaseEnabled = database !== undefined\n const binding = database?.binding ?? DEFAULT_DB_BINDING\n\n // Inject the DB-isolation env ONLY when the consumer opted in via `database`.\n // Its presence is how the testing-module builder tells \"isolation requested\"\n // from \"plain app\" — so it can hard-error on `database: {}` with no connection\n // string instead of silently running parallel files without isolation.\n // Annotated rather than inferred: the ternary would otherwise widen to\n // `{ …: string } | { …?: undefined }`, and spreading that union produces\n // optionally-`undefined` bindings, which miniflare's `Record<string, Json>`\n // rejects.\n const dbEnv: Record<string, string> = databaseEnabled\n ? {\n [BINDING_ENV_VAR]: binding,\n // Run the consuming app's DB connections on ONE shared pool per\n // connection: the test runtime hits a direct Postgres with no Hyperdrive\n // to multiplex, so a fresh pool per request resolution would exhaust\n // `max_connections` across parallel files. The framework's\n // `createPoolFactory(env, …)` reads this and memoizes the pool; in\n // dev/staging/prod the flag is absent → fresh-per-resolution (Hyperdrive\n // multiplexes). See `@stratal/framework/database`.\n [DB_SHARED_POOL_ENV]: 'true',\n }\n : {}\n const merged = {\n ...cfOptions,\n miniflare: {\n ...cfOptions.miniflare,\n bindings: {\n ...cfOptions.miniflare?.bindings,\n ...dbEnv,\n },\n },\n }\n return [cloudflareTest(merged), createStratalPlugin(databaseEnabled)]\n}\n"],"mappings":";;;;;;AAUA,MAAM,UAAU,cAAc,YAAY,GAAG;AA6B7C,MAAM,iCAAiB,IAAI,IAA0B;CACnD,CAAC,qBAAqB,QAAQ,QAAQ,aAAa,CAAC;CACpD,CAAC,8BAA8B,QAAQ,QAAQ,sBAAsB,CAAC;CACtE,CAAC,iBAAiB,QAAQ,QAAQ,SAAS,CAAC;CAC5C,CAAC,uBAAuB,KAAK,KAAK,KAAK,QAAQ,QAAQ,QAAQ,eAAe,CAAC,GAAG,UAAU,CAAC;AAC/F,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BD,MAAa,kBAA0B;CACrC,MAAM;CACN,SAAS;CACT,UAAU,IAAI;EACZ,MAAM,WAAW,eAAe,IAAI,EAAE;EACtC,IAAI,CAAC,UAAU;EACf,IAAI;GACF,OAAO,SAAS;EAClB,QAAQ;GACN;EACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,0BAAkC;CAC7C,MAAM,MAAM,CAAC,sBAAsB,sBAAsB;CACzD,IAAI,WAAuC;CAE3C,OAAO;EACL,MAAM;EACN,SAAS;EACT,eAAe,QAAQ;GACrB,MAAM,kBAAkB,cAAc,KAAK,KAAK,OAAO,MAAM,SAAS,CAAC;GACvE,IAAI;GACJ,IAAI;IACF,eAAe,gBAAgB,QAAQ,iBAAiB;GAC1D,QAAQ;IAEN;GACF;GACA,MAAM,YAAY,cAAc,YAAY,CAAC,CAAC,QAAQ,sBAAsB;GAC5E,MAAM,eAAe,cAAc,SAAS;GAC5C,WAAW,IAAI,IACb,IAAI,KAAK,OAAO,CAAC,IAAI,aAAa,QAAQ,EAAE,CAAC,CAAC,CAChD;EACF;EACA,UAAU,IAAI;GACZ,OAAO,UAAU,IAAI,EAAE;EACzB;CACF;AACF;;AAGA,MAAa,uBAAuB,qBAAsC;CACxE,MAAM;CACN,OAAO,YAAoD;EACzD,MAAM,SAAiD;GACrD,SAAS,EACP,OAAO;IACL,OAAO;IACP,4BAA4B;IAC5B,8BAA8B;IAC9B,wBAAwB;GAC1B,EACF;GACA,KAAK,EACH,YAAY,CAAC,yBAAyB,EACxC;EACF;EAYA,IAAI,iBAAiB;GACnB,MAAM,WAAW,WAAW,QAAQ,CAAC;GACrC,IAAI,SAAS,YAAY,OACvB,MAAM,IAAI,MACR,mOAGF;GAEF,OAAO,OAAO;IACZ,iBAAiB,SAAS,mBAAmB;IAC7C,SAAS;IACT,aAAa,SAAS,eAAe;GACvC;EACF;EACA,OAAO;CACT;AACF;;;;;;;;;;;;;;AAeA,SAAgB,YAAY,UAA8B,CAAC,GAAa;CACtE,MAAM,EAAE,UAAU,GAAG,cAAc;CACnC,MAAM,kBAAkB,aAAa,KAAA;CACrC,MAAM,UAAU,UAAU,WAAA;CAU1B,MAAM,QAAgC,kBAClC;GACG,kBAAkB;GAQlB,qBAAqB;CACxB,IACA,CAAC;CACL,MAAM,SAAS;EACb,GAAG;EACH,WAAW;GACT,GAAG,UAAU;GACb,UAAU;IACR,GAAG,UAAU,WAAW;IACxB,GAAG;GACL;EACF;CACF;CACA,OAAO,CAAC,eAAe,MAAM,GAAG,oBAAoB,eAAe,CAAC;AACtE"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stratal/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Testing utilities and mocks for Stratal framework applications",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"license": "MIT",
|
|
7
|
-
"author":
|
|
8
|
-
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "Temitayo Fadojutimi",
|
|
10
|
+
"url": "https://x.com/adesege_"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://stratal.dev",
|
|
9
13
|
"repository": {
|
|
10
14
|
"type": "git",
|
|
11
15
|
"url": "git+https://github.com/strataljs/stratal.git",
|
|
@@ -49,6 +53,10 @@
|
|
|
49
53
|
"types": "./dist/mocks/index.d.mts",
|
|
50
54
|
"import": "./dist/mocks/index.mjs"
|
|
51
55
|
},
|
|
56
|
+
"./mocks/cloudflare-workers": {
|
|
57
|
+
"types": "./dist/mocks/cloudflare-workers.d.mts",
|
|
58
|
+
"import": "./dist/mocks/cloudflare-workers.mjs"
|
|
59
|
+
},
|
|
52
60
|
"./mocks/noop-rate-limiter-store": {
|
|
53
61
|
"types": "./dist/mocks/noop-rate-limiter-store.d.mts",
|
|
54
62
|
"import": "./dist/mocks/noop-rate-limiter-store.mjs"
|
|
@@ -69,21 +77,22 @@
|
|
|
69
77
|
},
|
|
70
78
|
"scripts": {
|
|
71
79
|
"build": "tsdown",
|
|
80
|
+
"test": "vitest run",
|
|
72
81
|
"typecheck": "tsc --noEmit",
|
|
73
82
|
"lint": "npx oxlint .",
|
|
74
83
|
"lint:fix": "npx oxlint --fix ."
|
|
75
84
|
},
|
|
76
85
|
"dependencies": {
|
|
77
|
-
"@cloudflare/vitest-
|
|
86
|
+
"@cloudflare/vitest-plugin": "^1.1.13",
|
|
78
87
|
"@golevelup/ts-vitest": "^4.0.0",
|
|
79
|
-
"msw": "^2.
|
|
88
|
+
"msw": "^2.15.0"
|
|
80
89
|
},
|
|
81
90
|
"peerDependencies": {
|
|
82
91
|
"@better-auth/core": ">=1.6",
|
|
83
|
-
"@stratal/framework": ">=0.
|
|
92
|
+
"@stratal/framework": ">=0.1.1",
|
|
84
93
|
"better-auth": ">=1.4",
|
|
85
94
|
"pg": "^8.0.0",
|
|
86
|
-
"stratal": ">=0.
|
|
95
|
+
"stratal": ">=0.1.1",
|
|
87
96
|
"vitest": "^4.1.0"
|
|
88
97
|
},
|
|
89
98
|
"peerDependenciesMeta": {
|
|
@@ -101,18 +110,18 @@
|
|
|
101
110
|
}
|
|
102
111
|
},
|
|
103
112
|
"devDependencies": {
|
|
104
|
-
"@better-auth/core": "^1.
|
|
105
|
-
"@cloudflare/workers-types": "
|
|
106
|
-
"@stratal/framework": "
|
|
107
|
-
"@types/node": "^
|
|
108
|
-
"@types/pg": "^8.
|
|
109
|
-
"@vitest/runner": "~4.1.
|
|
110
|
-
"@vitest/snapshot": "~4.1.
|
|
111
|
-
"better-auth": "^1.
|
|
112
|
-
"pg": "^8.
|
|
113
|
-
"stratal": "
|
|
114
|
-
"tsdown": "^0.
|
|
115
|
-
"typescript": "^
|
|
116
|
-
"vitest": "~4.1.
|
|
113
|
+
"@better-auth/core": "^1.7.5",
|
|
114
|
+
"@cloudflare/workers-types": "5.20260919.1",
|
|
115
|
+
"@stratal/framework": "0.1.1",
|
|
116
|
+
"@types/node": "^26.6.2",
|
|
117
|
+
"@types/pg": "^8.23.1",
|
|
118
|
+
"@vitest/runner": "~4.1.11",
|
|
119
|
+
"@vitest/snapshot": "~4.1.11",
|
|
120
|
+
"better-auth": "^1.7.5",
|
|
121
|
+
"pg": "^8.23.0",
|
|
122
|
+
"stratal": "0.1.1",
|
|
123
|
+
"tsdown": "^0.23.0",
|
|
124
|
+
"typescript": "^7.0.2",
|
|
125
|
+
"vitest": "~4.1.11"
|
|
117
126
|
}
|
|
118
|
-
}
|
|
127
|
+
}
|