@rexezuge/tooling 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +297 -2
- package/dist/eslint.d.ts +120 -0
- package/dist/eslint.d.ts.map +1 -0
- package/dist/eslint.js +551 -0
- package/dist/eslint.js.map +1 -0
- package/dist/functions/pages-proxy.d.ts +113 -0
- package/dist/functions/pages-proxy.d.ts.map +1 -0
- package/dist/functions/pages-proxy.js +131 -0
- package/dist/functions/pages-proxy.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/scripts/backup/d1-target.d.ts +35 -0
- package/dist/scripts/backup/d1-target.d.ts.map +1 -0
- package/dist/scripts/backup/d1-target.js +31 -0
- package/dist/scripts/backup/d1-target.js.map +1 -0
- package/dist/scripts/backup/destination-config.d.ts +61 -0
- package/dist/scripts/backup/destination-config.d.ts.map +1 -0
- package/dist/scripts/backup/destination-config.js +57 -0
- package/dist/scripts/backup/destination-config.js.map +1 -0
- package/dist/scripts/backup/encrypt-backup.d.ts +35 -0
- package/dist/scripts/backup/encrypt-backup.d.ts.map +1 -0
- package/dist/scripts/backup/encrypt-backup.js +98 -0
- package/dist/scripts/backup/encrypt-backup.js.map +1 -0
- package/dist/scripts/backup/naming.d.ts +44 -0
- package/dist/scripts/backup/naming.d.ts.map +1 -0
- package/dist/scripts/backup/naming.js +58 -0
- package/dist/scripts/backup/naming.js.map +1 -0
- package/dist/scripts/check-god-files.d.ts +164 -0
- package/dist/scripts/check-god-files.d.ts.map +1 -0
- package/dist/scripts/check-god-files.js +271 -0
- package/dist/scripts/check-god-files.js.map +1 -0
- package/dist/scripts/ensure-spa-shell-stub.d.ts +3 -0
- package/dist/scripts/ensure-spa-shell-stub.d.ts.map +1 -0
- package/dist/scripts/ensure-spa-shell-stub.js +30 -0
- package/dist/scripts/ensure-spa-shell-stub.js.map +1 -0
- package/dist/scripts/init-secrets.d.ts +63 -0
- package/dist/scripts/init-secrets.d.ts.map +1 -0
- package/dist/scripts/init-secrets.js +240 -0
- package/dist/scripts/init-secrets.js.map +1 -0
- package/dist/scripts/lib/cli-args.d.ts +78 -0
- package/dist/scripts/lib/cli-args.d.ts.map +1 -0
- package/dist/scripts/lib/cli-args.js +116 -0
- package/dist/scripts/lib/cli-args.js.map +1 -0
- package/dist/scripts/lib/github-actions.d.ts +26 -0
- package/dist/scripts/lib/github-actions.d.ts.map +1 -0
- package/dist/scripts/lib/github-actions.js +38 -0
- package/dist/scripts/lib/github-actions.js.map +1 -0
- package/dist/scripts/lib/wrangler-table.d.ts +46 -0
- package/dist/scripts/lib/wrangler-table.d.ts.map +1 -0
- package/dist/scripts/lib/wrangler-table.js +99 -0
- package/dist/scripts/lib/wrangler-table.js.map +1 -0
- package/dist/scripts/migrations-lock.d.ts +3 -0
- package/dist/scripts/migrations-lock.d.ts.map +1 -0
- package/dist/scripts/migrations-lock.js +46 -0
- package/dist/scripts/migrations-lock.js.map +1 -0
- package/dist/scripts/prepare-wrangler-config.d.ts +3 -0
- package/dist/scripts/prepare-wrangler-config.d.ts.map +1 -0
- package/dist/scripts/prepare-wrangler-config.js +50 -0
- package/dist/scripts/prepare-wrangler-config.js.map +1 -0
- package/dist/scripts/spa-shell.d.ts +41 -0
- package/dist/scripts/spa-shell.d.ts.map +1 -0
- package/dist/scripts/spa-shell.js +155 -0
- package/dist/scripts/spa-shell.js.map +1 -0
- package/dist/scripts/validate-locales.d.ts +26 -0
- package/dist/scripts/validate-locales.d.ts.map +1 -0
- package/dist/scripts/validate-locales.js +350 -0
- package/dist/scripts/validate-locales.js.map +1 -0
- package/dist/scripts/verify-migrations.d.ts +62 -0
- package/dist/scripts/verify-migrations.d.ts.map +1 -0
- package/dist/scripts/verify-migrations.js +302 -0
- package/dist/scripts/verify-migrations.js.map +1 -0
- package/dist/scripts/verify-spa-shell.d.ts +3 -0
- package/dist/scripts/verify-spa-shell.d.ts.map +1 -0
- package/dist/scripts/verify-spa-shell.js +53 -0
- package/dist/scripts/verify-spa-shell.js.map +1 -0
- package/dist/scripts/wrangler-config/cli.d.ts +22 -0
- package/dist/scripts/wrangler-config/cli.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/cli.js +51 -0
- package/dist/scripts/wrangler-config/cli.js.map +1 -0
- package/dist/scripts/wrangler-config/patches.d.ts +51 -0
- package/dist/scripts/wrangler-config/patches.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/patches.js +140 -0
- package/dist/scripts/wrangler-config/patches.js.map +1 -0
- package/dist/scripts/wrangler-config/resources.d.ts +70 -0
- package/dist/scripts/wrangler-config/resources.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/resources.js +290 -0
- package/dist/scripts/wrangler-config/resources.js.map +1 -0
- package/dist/scripts/wrangler-config/types.d.ts +103 -0
- package/dist/scripts/wrangler-config/types.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/types.js +49 -0
- package/dist/scripts/wrangler-config/types.js.map +1 -0
- package/dist/test/integration-migrations.d.ts +167 -0
- package/dist/test/integration-migrations.d.ts.map +1 -0
- package/dist/test/integration-migrations.js +171 -0
- package/dist/test/integration-migrations.js.map +1 -0
- package/dist/test/mocks/cloudflare-workers.d.ts +106 -0
- package/dist/test/mocks/cloudflare-workers.d.ts.map +1 -0
- package/dist/test/mocks/cloudflare-workers.js +90 -0
- package/dist/test/mocks/cloudflare-workers.js.map +1 -0
- package/dist/vite.d.ts +117 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +125 -0
- package/dist/vite.js.map +1 -0
- package/dist/vitest-web.d.ts +73 -0
- package/dist/vitest-web.d.ts.map +1 -0
- package/dist/vitest-web.js +72 -0
- package/dist/vitest-web.js.map +1 -0
- package/dist/vitest.d.ts +92 -0
- package/dist/vitest.d.ts.map +1 -0
- package/dist/vitest.js +128 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +58 -3
- package/src/eslint.test.ts +175 -0
- package/src/eslint.ts +640 -0
- package/src/functions/pages-proxy.test.ts +72 -0
- package/src/functions/pages-proxy.ts +187 -0
- package/src/github/actions/retry-step/action.yml +39 -0
- package/src/github/actions/setup-env/action.yml +20 -0
- package/src/github/dependabot.yml +30 -0
- package/src/github/workflows/backup-main.yml +46 -0
- package/src/github/workflows/continuous-deployment.yml +188 -0
- package/src/github/workflows/continuous-integration.yml +259 -0
- package/src/github/workflows/scheduled-version-update.yml +38 -0
- package/src/github/workflows/upstream-sync.yml +56 -0
- package/src/index.ts +42 -0
- package/src/scripts/backup/backup-rules.test.ts +105 -0
- package/src/scripts/backup/d1-target.ts +54 -0
- package/src/scripts/backup/destination-config.ts +92 -0
- package/src/scripts/backup/encrypt-backup.ts +107 -0
- package/src/scripts/backup/naming.ts +62 -0
- package/src/scripts/check-god-files.test.ts +131 -0
- package/src/scripts/check-god-files.ts +327 -0
- package/src/scripts/ensure-spa-shell-stub.ts +34 -0
- package/src/scripts/init-secrets.ts +265 -0
- package/src/scripts/lib/cli-args.ts +154 -0
- package/src/scripts/lib/github-actions.ts +41 -0
- package/src/scripts/lib/wrangler-table.ts +105 -0
- package/src/scripts/migrations-lock.ts +52 -0
- package/src/scripts/prepare-wrangler-config.ts +51 -0
- package/src/scripts/spa-shell.test.ts +91 -0
- package/src/scripts/spa-shell.ts +179 -0
- package/src/scripts/validate-locales.test.ts +89 -0
- package/src/scripts/validate-locales.ts +380 -0
- package/src/scripts/verify-migrations.test.ts +71 -0
- package/src/scripts/verify-migrations.ts +364 -0
- package/src/scripts/verify-spa-shell.ts +56 -0
- package/src/scripts/wrangler-config/cli.ts +51 -0
- package/src/scripts/wrangler-config/patches.ts +157 -0
- package/src/scripts/wrangler-config/resources.ts +330 -0
- package/src/scripts/wrangler-config/types.ts +113 -0
- package/src/test/integration-migrations.test.ts +169 -0
- package/src/test/integration-migrations.ts +267 -0
- package/src/test/mocks/cloudflare-workers.ts +115 -0
- package/src/vite.test.ts +83 -0
- package/src/vite.ts +202 -0
- package/src/vitest-web.ts +109 -0
- package/src/vitest.ts +185 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared paths, placeholders and shape definitions for the wrangler config helpers.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: ported from Edge-Sonic's `scripts/lib/wrangler-config/types.ts`.
|
|
5
|
+
* Every script that reads or writes `wrangler.jsonc` resolves it here, so the
|
|
6
|
+
* backup workflow cannot end up inspecting a different file than the deploy
|
|
7
|
+
* materialized.
|
|
8
|
+
*
|
|
9
|
+
* The paths default to `process.cwd()`, which is the repo root in every consumer:
|
|
10
|
+
* the scripts run as `pnpm exec tsx scripts/deploy/…` from there. `WRANGLER_CONFIG_PATH`
|
|
11
|
+
* / `WRANGLER_TEMPLATE_PATH` override them for a caller that is not in the repo
|
|
12
|
+
* root, which is the one case the cwd default gets wrong.
|
|
13
|
+
*/
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
/** The config the deploy targets. */
|
|
16
|
+
export const CONFIG_PATH = process.env['WRANGLER_CONFIG_PATH'] ?? path.join(process.cwd(), 'wrangler.jsonc');
|
|
17
|
+
/** The committed template the config is materialized from. */
|
|
18
|
+
export const TEMPLATE_PATH = process.env['WRANGLER_TEMPLATE_PATH'] ?? path.join(process.cwd(), 'apps/api/wrangler.template.jsonc');
|
|
19
|
+
/**
|
|
20
|
+
* The id a template carries for a D1 database that does not exist yet.
|
|
21
|
+
*
|
|
22
|
+
* `provisionWranglerResources` replaces it with a real id; the backup scripts
|
|
23
|
+
* refuse to export while it is still in place, because a database that was just
|
|
24
|
+
* created holds no user data.
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_UUID = '00000000-0000-0000-0000-000000000000';
|
|
27
|
+
/** The placeholder for a KV namespace id and for a Secrets Store id. */
|
|
28
|
+
export const DEFAULT_HEX_ID = '00000000000000000000000000000000';
|
|
29
|
+
/** The Secrets Store name a deployment uses. */
|
|
30
|
+
export const DEFAULT_SECRET_STORE_NAME = 'default';
|
|
31
|
+
/**
|
|
32
|
+
* Fallback when the config declares no `name`, used to derive resource names.
|
|
33
|
+
*
|
|
34
|
+
* Overridable per consumer with `WRANGLER_WORKER_NAME`, which keeps the default
|
|
35
|
+
* honest rather than baking one repo's name into the kit.
|
|
36
|
+
*/
|
|
37
|
+
export const DEFAULT_WORKER_NAME = process.env['WRANGLER_WORKER_NAME'] ?? 'my-worker';
|
|
38
|
+
/**
|
|
39
|
+
* KV bindings a deployment always needs, and the namespace each maps to.
|
|
40
|
+
*
|
|
41
|
+
* A consumer whose worker has no cache binding passes an empty map; a consumer
|
|
42
|
+
* with one names it here or leaves the derived `<worker>-<BINDING>` convention.
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_KV_NAMESPACE_NAMES = {
|
|
45
|
+
CACHE: `${DEFAULT_WORKER_NAME}-cache`,
|
|
46
|
+
};
|
|
47
|
+
/** Dimensions of a Vectorize index this kit provisions. */
|
|
48
|
+
export const VECTORIZE_DIMENSIONS = 1024;
|
|
49
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/scripts/wrangler-config/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,qCAAqC;AACrC,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC;AAE7G,8DAA8D;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,kCAAkC,CAAC,CAAC;AAEnI;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,sCAAsC,CAAC;AAEnE,wEAAwE;AACxE,MAAM,CAAC,MAAM,cAAc,GAAG,kCAAkC,CAAC;AAEjE,gDAAgD;AAChD,MAAM,CAAC,MAAM,yBAAyB,GAAG,SAAS,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,WAAW,CAAC;AAEtF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAA2B;IAChE,KAAK,EAAE,GAAG,mBAAmB,QAAQ;CACtC,CAAC;AAEF,2DAA2D;AAC3D,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applying the migration directory to a D1 binding — as a factory.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from `test/integration/helpers/migrations.ts` in six repos
|
|
5
|
+
* (AWS-AccessBridge, ChordDHT-Tracker, Durable-DAV, Durable-DAV-Router, Edge-Git,
|
|
6
|
+
* Mail-Meow) and `test/helpers/migrations.ts` in two more (CalDAV-Bridge,
|
|
7
|
+
* Edge-Sonic). All eight are the same file: a SQL splitter plus a range-addressed
|
|
8
|
+
* `applyMigrations`.
|
|
9
|
+
*
|
|
10
|
+
* ### Why it is a factory and not a module
|
|
11
|
+
*
|
|
12
|
+
* Every source file hardcodes where the migrations live, either as a
|
|
13
|
+
* `__INTEGRATION_MIGRATION_FILES__` binding injected by the Vitest config's
|
|
14
|
+
* `define`, or as a path derived from `import.meta.url`. The kit cannot know
|
|
15
|
+
* either, so the directory is an argument and the injection stays available as an
|
|
16
|
+
* option. One call to `createMigrationHelper({ migrationsDir })` gives the consumer
|
|
17
|
+
* the same helper their repo already has.
|
|
18
|
+
*
|
|
19
|
+
* ### What each source contributes
|
|
20
|
+
*
|
|
21
|
+
* - **Durable-DAV** — the trigger-aware splitter. `CREATE TRIGGER … BEGIN … END;`
|
|
22
|
+
* bodies contain semicolons that must not split, and a naive splitter produces
|
|
23
|
+
* statements SQLite rejects mid-migration. The BEGIN/END depth is tracked only
|
|
24
|
+
* outside strings and comments, and the word *before* `TRIGGER` disambiguates
|
|
25
|
+
* `CREATE TRIGGER` from a table called `trigger`. Durable-DAV also applies one
|
|
26
|
+
* file per `batch()` and names the offending statement in the error, which is
|
|
27
|
+
* the difference between "0007 failed" and a line number.
|
|
28
|
+
* - **AWS-AccessBridge** — the once-per-database guard. D1 records which
|
|
29
|
+
* migrations have run; this helper does the same, because `ALTER TABLE … ADD
|
|
30
|
+
* COLUMN` is not idempotent in SQLite and re-running the bundle fails with
|
|
31
|
+
* "duplicate column name". A `WeakMap` keyed on the binding is the right scope
|
|
32
|
+
* because each test file gets its own database. AWS also keeps `next` out of the
|
|
33
|
+
* line-comment branch, which never reads it, and preserves newlines inside block
|
|
34
|
+
* comments so an error's line numbers still point at the file.
|
|
35
|
+
* - **Edge-Sonic** — reading the directory off disk rather than a `define`
|
|
36
|
+
* binding, because a test that names one hardcoded file cannot tell **a new
|
|
37
|
+
* migration** from **an edit to an old one**, so it goes on passing through a
|
|
38
|
+
* schema change that never reached production.
|
|
39
|
+
*
|
|
40
|
+
* ### The splitter is not here
|
|
41
|
+
*
|
|
42
|
+
* The statement splitter — with all of Durable-DAV's trigger-aware rules and its
|
|
43
|
+
* comment-level history — lives once, in `@rexezuge/d1`. `@rexezuge/identity`'s
|
|
44
|
+
* migration applier delegates to the same module, so the splitter cannot drift
|
|
45
|
+
* between the suite helper and the production applier. The re-exports below keep
|
|
46
|
+
* this file's public surface identical to the source repos'.
|
|
47
|
+
*
|
|
48
|
+
* ### The lock is asserted, not reimplemented
|
|
49
|
+
*
|
|
50
|
+
* D1 records applied migrations by *filename*, so an applied migration is immutable
|
|
51
|
+
* in fact while being an ordinary text file in appearance. That fact lives in
|
|
52
|
+
* `migrations/migrations.lock.json`, and the rules that check it live once, in
|
|
53
|
+
* `scripts/verify-migrations.ts` — the same module the CI gate runs. This helper
|
|
54
|
+
* deliberately does not parse the lock itself: a second implementation of a thing
|
|
55
|
+
* already written is free to disagree with the first, and the two disagreeing would
|
|
56
|
+
* leave a suite green against a lock CI rejects.
|
|
57
|
+
*
|
|
58
|
+
* ### Generic over the binding
|
|
59
|
+
*
|
|
60
|
+
* `D1DatabaseLike` is structural — `prepare`, and `batch` when the runtime has it
|
|
61
|
+
* — so a fake D1 in a unit test, a real D1 in the workerd integration suite, and
|
|
62
|
+
* Miniflare's D1 all satisfy it without an import of `@cloudflare/workers-types`.
|
|
63
|
+
* `batch` is used when present (Durable-DAV's shape: one round trip per file, and
|
|
64
|
+
* the failure names the file) and a sequential `prepare().run()` loop otherwise
|
|
65
|
+
* (AWS's shape: works on a binding with no batch support).
|
|
66
|
+
*/
|
|
67
|
+
import { executableStatements, splitSql } from '@rexezuge/d1';
|
|
68
|
+
/**
|
|
69
|
+
* One migration file, as it is read.
|
|
70
|
+
*/
|
|
71
|
+
export interface MigrationFile {
|
|
72
|
+
/** Bare filename, e.g. `0007_replication.sql`. */
|
|
73
|
+
name: string;
|
|
74
|
+
/** The file's full text. */
|
|
75
|
+
sql: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The subset of a D1 prepared statement this helper uses.
|
|
79
|
+
*/
|
|
80
|
+
export interface D1PreparedStatementLike {
|
|
81
|
+
run(): Promise<unknown>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The subset of a D1 binding this helper uses.
|
|
85
|
+
*
|
|
86
|
+
* `batch` is optional because not every binding that can prepare a statement can
|
|
87
|
+
* batch one; where it exists it is the better path, and where it does not the
|
|
88
|
+
* sequential loop is equivalent for a migration whose statements are independent.
|
|
89
|
+
*/
|
|
90
|
+
export interface D1DatabaseLike {
|
|
91
|
+
prepare(sql: string): D1PreparedStatementLike;
|
|
92
|
+
batch?(statements: readonly D1PreparedStatementLike[]): Promise<unknown>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A closed range of migration files, by filename.
|
|
96
|
+
*
|
|
97
|
+
* Both ends inclusive, because "up to and including 0006" is the natural way to
|
|
98
|
+
* seed a pre-migration database and then apply the migration under test.
|
|
99
|
+
*/
|
|
100
|
+
export interface MigrationRange {
|
|
101
|
+
from?: string;
|
|
102
|
+
to?: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* What `createMigrationHelper` accepts.
|
|
106
|
+
*/
|
|
107
|
+
export interface MigrationHelperOptions {
|
|
108
|
+
/**
|
|
109
|
+
* Directory holding the `.sql` files. Read top-level only, matching wrangler's
|
|
110
|
+
* default `${migrationsDir}/*.sql`, so the helper describes exactly the set
|
|
111
|
+
* wrangler will run.
|
|
112
|
+
*/
|
|
113
|
+
readonly migrationsDir: string;
|
|
114
|
+
/**
|
|
115
|
+
* Migration files to use instead of reading `migrationsDir`.
|
|
116
|
+
*
|
|
117
|
+
* The escape hatch for a harness that embeds the SQL at build time. Same shape
|
|
118
|
+
* as the `__INTEGRATION_MIGRATION_FILES__` binding the source repos inject.
|
|
119
|
+
*/
|
|
120
|
+
readonly files?: readonly MigrationFile[];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* A bound migration helper.
|
|
124
|
+
*/
|
|
125
|
+
export interface MigrationHelper {
|
|
126
|
+
/** Every migration, in apply order. */
|
|
127
|
+
migrationFiles(): MigrationFile[];
|
|
128
|
+
/** The filenames, in apply order. */
|
|
129
|
+
migrationFileNames(): string[];
|
|
130
|
+
/** The concatenation of every file, in apply order — what a fresh database becomes. */
|
|
131
|
+
migrationSql(): string;
|
|
132
|
+
/** One file's SQL, by name. "What does *this* file say", not "what does the set say". */
|
|
133
|
+
migrationSqlOf(name: string): string;
|
|
134
|
+
/** Split one file into statements, comments included. */
|
|
135
|
+
splitSql(sql: string): string[];
|
|
136
|
+
/** The statements of one file that SQLite will actually execute. */
|
|
137
|
+
executableStatements(sql: string): string[];
|
|
138
|
+
/**
|
|
139
|
+
* Apply a range of migrations, at most once per database and range.
|
|
140
|
+
*
|
|
141
|
+
* Per file rather than one batch for everything, so a test can stop between
|
|
142
|
+
* files: the identity-upgrade test needs to seed the *pre-0004* shape and then
|
|
143
|
+
* apply 0004 to it.
|
|
144
|
+
*/
|
|
145
|
+
applyMigrations(db: D1DatabaseLike, range?: MigrationRange): Promise<void>;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Reads every `.sql` file in `dir`, in apply order.
|
|
149
|
+
*
|
|
150
|
+
* Sort is the filename sort D1 itself uses, rather than a numeric parse of the
|
|
151
|
+
* prefix: wrangler hands the directory to SQLite in lexicographic order, so a
|
|
152
|
+
* suite that reads it numerically disagrees with production on any prefix wider
|
|
153
|
+
* than four digits.
|
|
154
|
+
*/
|
|
155
|
+
export declare function readMigrationFiles(dir: string): MigrationFile[];
|
|
156
|
+
/** Re-exported for surface parity with the source repos — the rules live in @rexezuge/d1. */
|
|
157
|
+
export { executableStatements, splitSql };
|
|
158
|
+
/**
|
|
159
|
+
* Bind a migration directory into a helper.
|
|
160
|
+
*
|
|
161
|
+
* ```ts
|
|
162
|
+
* const migrations = createMigrationHelper({ migrationsDir: path.join(REPO_ROOT, 'migrations') });
|
|
163
|
+
* await migrations.applyMigrations(env.DB);
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export declare function createMigrationHelper(options: MigrationHelperOptions): MigrationHelper;
|
|
167
|
+
//# sourceMappingURL=integration-migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-migrations.d.ts","sourceRoot":"","sources":["../../src/test/integration-migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAEH,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI9D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,uBAAuB,CAAC;IAC9C,KAAK,CAAC,CAAC,UAAU,EAAE,SAAS,uBAAuB,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1E;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,cAAc,IAAI,aAAa,EAAE,CAAC;IAClC,qCAAqC;IACrC,kBAAkB,IAAI,MAAM,EAAE,CAAC;IAC/B,uFAAuF;IACvF,YAAY,IAAI,MAAM,CAAC;IACvB,yFAAyF;IACzF,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACrC,yDAAyD;IACzD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAChC,oEAAoE;IACpE,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5C;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,EAAE,CAM/D;AAeD,6FAA6F;AAC7F,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,CAAC;AAE1C;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CAsEtF"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applying the migration directory to a D1 binding — as a factory.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from `test/integration/helpers/migrations.ts` in six repos
|
|
5
|
+
* (AWS-AccessBridge, ChordDHT-Tracker, Durable-DAV, Durable-DAV-Router, Edge-Git,
|
|
6
|
+
* Mail-Meow) and `test/helpers/migrations.ts` in two more (CalDAV-Bridge,
|
|
7
|
+
* Edge-Sonic). All eight are the same file: a SQL splitter plus a range-addressed
|
|
8
|
+
* `applyMigrations`.
|
|
9
|
+
*
|
|
10
|
+
* ### Why it is a factory and not a module
|
|
11
|
+
*
|
|
12
|
+
* Every source file hardcodes where the migrations live, either as a
|
|
13
|
+
* `__INTEGRATION_MIGRATION_FILES__` binding injected by the Vitest config's
|
|
14
|
+
* `define`, or as a path derived from `import.meta.url`. The kit cannot know
|
|
15
|
+
* either, so the directory is an argument and the injection stays available as an
|
|
16
|
+
* option. One call to `createMigrationHelper({ migrationsDir })` gives the consumer
|
|
17
|
+
* the same helper their repo already has.
|
|
18
|
+
*
|
|
19
|
+
* ### What each source contributes
|
|
20
|
+
*
|
|
21
|
+
* - **Durable-DAV** — the trigger-aware splitter. `CREATE TRIGGER … BEGIN … END;`
|
|
22
|
+
* bodies contain semicolons that must not split, and a naive splitter produces
|
|
23
|
+
* statements SQLite rejects mid-migration. The BEGIN/END depth is tracked only
|
|
24
|
+
* outside strings and comments, and the word *before* `TRIGGER` disambiguates
|
|
25
|
+
* `CREATE TRIGGER` from a table called `trigger`. Durable-DAV also applies one
|
|
26
|
+
* file per `batch()` and names the offending statement in the error, which is
|
|
27
|
+
* the difference between "0007 failed" and a line number.
|
|
28
|
+
* - **AWS-AccessBridge** — the once-per-database guard. D1 records which
|
|
29
|
+
* migrations have run; this helper does the same, because `ALTER TABLE … ADD
|
|
30
|
+
* COLUMN` is not idempotent in SQLite and re-running the bundle fails with
|
|
31
|
+
* "duplicate column name". A `WeakMap` keyed on the binding is the right scope
|
|
32
|
+
* because each test file gets its own database. AWS also keeps `next` out of the
|
|
33
|
+
* line-comment branch, which never reads it, and preserves newlines inside block
|
|
34
|
+
* comments so an error's line numbers still point at the file.
|
|
35
|
+
* - **Edge-Sonic** — reading the directory off disk rather than a `define`
|
|
36
|
+
* binding, because a test that names one hardcoded file cannot tell **a new
|
|
37
|
+
* migration** from **an edit to an old one**, so it goes on passing through a
|
|
38
|
+
* schema change that never reached production.
|
|
39
|
+
*
|
|
40
|
+
* ### The splitter is not here
|
|
41
|
+
*
|
|
42
|
+
* The statement splitter — with all of Durable-DAV's trigger-aware rules and its
|
|
43
|
+
* comment-level history — lives once, in `@rexezuge/d1`. `@rexezuge/identity`'s
|
|
44
|
+
* migration applier delegates to the same module, so the splitter cannot drift
|
|
45
|
+
* between the suite helper and the production applier. The re-exports below keep
|
|
46
|
+
* this file's public surface identical to the source repos'.
|
|
47
|
+
*
|
|
48
|
+
* ### The lock is asserted, not reimplemented
|
|
49
|
+
*
|
|
50
|
+
* D1 records applied migrations by *filename*, so an applied migration is immutable
|
|
51
|
+
* in fact while being an ordinary text file in appearance. That fact lives in
|
|
52
|
+
* `migrations/migrations.lock.json`, and the rules that check it live once, in
|
|
53
|
+
* `scripts/verify-migrations.ts` — the same module the CI gate runs. This helper
|
|
54
|
+
* deliberately does not parse the lock itself: a second implementation of a thing
|
|
55
|
+
* already written is free to disagree with the first, and the two disagreeing would
|
|
56
|
+
* leave a suite green against a lock CI rejects.
|
|
57
|
+
*
|
|
58
|
+
* ### Generic over the binding
|
|
59
|
+
*
|
|
60
|
+
* `D1DatabaseLike` is structural — `prepare`, and `batch` when the runtime has it
|
|
61
|
+
* — so a fake D1 in a unit test, a real D1 in the workerd integration suite, and
|
|
62
|
+
* Miniflare's D1 all satisfy it without an import of `@cloudflare/workers-types`.
|
|
63
|
+
* `batch` is used when present (Durable-DAV's shape: one round trip per file, and
|
|
64
|
+
* the failure names the file) and a sequential `prepare().run()` loop otherwise
|
|
65
|
+
* (AWS's shape: works on a binding with no batch support).
|
|
66
|
+
*/
|
|
67
|
+
import { executableStatements, splitSql } from '@rexezuge/d1';
|
|
68
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
69
|
+
import { join } from 'node:path';
|
|
70
|
+
/**
|
|
71
|
+
* Reads every `.sql` file in `dir`, in apply order.
|
|
72
|
+
*
|
|
73
|
+
* Sort is the filename sort D1 itself uses, rather than a numeric parse of the
|
|
74
|
+
* prefix: wrangler hands the directory to SQLite in lexicographic order, so a
|
|
75
|
+
* suite that reads it numerically disagrees with production on any prefix wider
|
|
76
|
+
* than four digits.
|
|
77
|
+
*/
|
|
78
|
+
export function readMigrationFiles(dir) {
|
|
79
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
80
|
+
return entries
|
|
81
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.sql'))
|
|
82
|
+
.map((entry) => ({ name: entry.name, sql: readFileSync(join(dir, entry.name), 'utf8') }))
|
|
83
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Strip leading `--` and block comments, which the splitter folds into the
|
|
87
|
+
* statement it then reports on.
|
|
88
|
+
*/
|
|
89
|
+
function stripLeadingComments(statement) {
|
|
90
|
+
let out = statement;
|
|
91
|
+
for (;;) {
|
|
92
|
+
const next = out.replace(/^\s*(?:--[^\n]*\n|\/\*[\s\S]*?\*\/)\s*/, '');
|
|
93
|
+
if (next === out)
|
|
94
|
+
return out.trim();
|
|
95
|
+
out = next;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Re-exported for surface parity with the source repos — the rules live in @rexezuge/d1. */
|
|
99
|
+
export { executableStatements, splitSql };
|
|
100
|
+
/**
|
|
101
|
+
* Bind a migration directory into a helper.
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* const migrations = createMigrationHelper({ migrationsDir: path.join(REPO_ROOT, 'migrations') });
|
|
105
|
+
* await migrations.applyMigrations(env.DB);
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function createMigrationHelper(options) {
|
|
109
|
+
const files = options.files !== undefined ? [...options.files] : readMigrationFiles(options.migrationsDir);
|
|
110
|
+
/**
|
|
111
|
+
* Databases this helper has already migrated, keyed by the range applied.
|
|
112
|
+
*
|
|
113
|
+
* A `WeakMap` rather than a module-level `Set`, because the guard is per
|
|
114
|
+
* binding: two databases in one suite each need the bundle applied once, and a
|
|
115
|
+
* module-level set would silently skip the second.
|
|
116
|
+
*/
|
|
117
|
+
const applied = new WeakMap();
|
|
118
|
+
const indexOf = (name, fallback) => {
|
|
119
|
+
if (name === undefined)
|
|
120
|
+
return fallback;
|
|
121
|
+
const found = files.findIndex((file) => file.name === name);
|
|
122
|
+
if (found === -1) {
|
|
123
|
+
throw new Error(`Unknown migration file: ${name}. Available: ${files.map((file) => file.name).join(', ')}`);
|
|
124
|
+
}
|
|
125
|
+
return found;
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
migrationFiles: () => files.map((file) => ({ ...file })),
|
|
129
|
+
migrationFileNames: () => files.map((file) => file.name),
|
|
130
|
+
migrationSql: () => files.map((file) => file.sql).join('\n'),
|
|
131
|
+
migrationSqlOf: (name) => {
|
|
132
|
+
const file = files.find((candidate) => candidate.name === name);
|
|
133
|
+
if (file === undefined) {
|
|
134
|
+
throw new Error(`Unknown migration file: ${name}. Available: ${files.map((candidate) => candidate.name).join(', ')}`);
|
|
135
|
+
}
|
|
136
|
+
return file.sql;
|
|
137
|
+
},
|
|
138
|
+
splitSql,
|
|
139
|
+
executableStatements,
|
|
140
|
+
applyMigrations: async (db, range) => {
|
|
141
|
+
const start = indexOf(range?.from, 0);
|
|
142
|
+
const end = indexOf(range?.to, files.length - 1);
|
|
143
|
+
const key = `${start}:${end}`;
|
|
144
|
+
if (applied.get(db) === key)
|
|
145
|
+
return;
|
|
146
|
+
const applicable = files.slice(start, end + 1);
|
|
147
|
+
for (const file of applicable) {
|
|
148
|
+
const statements = executableStatements(file.sql);
|
|
149
|
+
if (statements.length === 0)
|
|
150
|
+
continue;
|
|
151
|
+
if (typeof db.batch === 'function') {
|
|
152
|
+
try {
|
|
153
|
+
await db.batch(statements.map((sql) => db.prepare(sql)));
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
// A batch reports only the file's single failure, so name the statement:
|
|
157
|
+
// a migration error that says nothing about which of its nine statements
|
|
158
|
+
// broke is a migration error nobody fixes.
|
|
159
|
+
throw new Error(`${file.name}: batch of ${statements.length} statements failed at ${stripLeadingComments(statements.at(-1) ?? '').slice(0, 200)}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
for (const sql of statements) {
|
|
164
|
+
await db.prepare(sql).run();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
applied.set(db, key);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=integration-migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-migrations.js","sourceRoot":"","sources":["../../src/test/integration-migrations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stand-in for the `cloudflare:workers` module, which has no Node equivalent.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from the eight copies at `test/mocks/cloudflare-workers.ts`
|
|
5
|
+
* (AWS-AccessBridge, CalDAV-Bridge, ChordDHT-Tracker, Durable-DAV, Edge-Git,
|
|
6
|
+
* Edge-Sonic, Mail-Meow, Mail-Otter). All eight are the same twenty lines; the
|
|
7
|
+
* union is three base classes and the union exists because each repo discovered a
|
|
8
|
+
* missing one the hard way.
|
|
9
|
+
*
|
|
10
|
+
* ### What each source contributes
|
|
11
|
+
*
|
|
12
|
+
* - **DurableObject** — every copy. Stores `ctx` and `env` so a real DO class can
|
|
13
|
+
* be constructed and its lifecycle RPCs driven directly, without Miniflare.
|
|
14
|
+
* - **`RpcTarget`** — Durable-DAV's, and the reason is load-bearing rather than
|
|
15
|
+
* tidy: `dofs`' `Fs` class `extends RpcTarget`, so the module fails to evaluate
|
|
16
|
+
* with *"Class extends value undefined"* when the base is missing. That is a
|
|
17
|
+
* module-load error, not a test failure, so it surfaces as an unimportable
|
|
18
|
+
* `dav-store` barrel and no unit test can reach `DavRepository`. Deliberately
|
|
19
|
+
* empty: on the platform it marks which methods are callable across an RPC
|
|
20
|
+
* boundary, and nothing in a Node suite calls those through RPC. An empty base
|
|
21
|
+
* keeps the fake honest instead of pretending to implement dispatch.
|
|
22
|
+
* - **`WorkflowEntrypoint`** — Edge-Git, Edge-Sonic, Mail-Meow, Mail-Otter. A
|
|
23
|
+
* Workflow class extends it, so the mock must provide it or the module fails the
|
|
24
|
+
* same way — a **startup** error, which therefore takes down every suite that
|
|
25
|
+
* reaches `apps/background` for an unrelated reason. Edge-Sonic's is the shape
|
|
26
|
+
* kept: it stores `ctx` as well as `env`, because the failure policy under test
|
|
27
|
+
* in `runWorkflow` lives in a plain function precisely so it can be exercised
|
|
28
|
+
* without workerd, and that policy reads the execution context.
|
|
29
|
+
*
|
|
30
|
+
* ### Why the ambient types are declared here rather than imported
|
|
31
|
+
*
|
|
32
|
+
* The source repos name `DurableObjectState`, `ExecutionContext` and `Env`
|
|
33
|
+
* unqualified, which resolves only because `worker-configuration.d.ts` and
|
|
34
|
+
* `@cloudflare/workers-types` are ambient inside a Wrangler project. This copy is
|
|
35
|
+
* imported by the kit's own typecheck, where neither exists, so the two state
|
|
36
|
+
* shapes are declared locally and `TEnv` defaults to `unknown` instead of `Env`
|
|
37
|
+
* (Edge-Sonic's form). A local declaration shadows the ambient one inside this
|
|
38
|
+
* file only, so a consumer's copy typechecks against their generated types
|
|
39
|
+
* exactly as the original did.
|
|
40
|
+
*
|
|
41
|
+
* ### Usage in a consumer
|
|
42
|
+
*
|
|
43
|
+
* Copy this file to `<repo>/test/mocks/cloudflare-workers.ts` and alias the
|
|
44
|
+
* specifier in the Vitest config — `defineUnitVitestConfig()` already does:
|
|
45
|
+
*
|
|
46
|
+
* ```jsonc
|
|
47
|
+
* // wrangler.jsonc / vitest alias
|
|
48
|
+
* "cloudflare:workers": "test/mocks/cloudflare-workers.ts"
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* Only what a test actually imports is exported. A symbol here that nothing
|
|
52
|
+
* imports is not a safety net; it is a claim that something depends on it.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* The subset of `DurableObjectState` a Node test reaches.
|
|
56
|
+
*
|
|
57
|
+
* Declared structurally rather than imported: `id`, `storage` and `waitUntil` are
|
|
58
|
+
* the three a hand-driven lifecycle touches, and nothing here needs the rest.
|
|
59
|
+
*/
|
|
60
|
+
export interface DurableObjectState {
|
|
61
|
+
readonly id: {
|
|
62
|
+
toString(): string;
|
|
63
|
+
readonly name?: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The subset of `ExecutionContext` a Node test reaches.
|
|
68
|
+
*
|
|
69
|
+
* `waitUntil` is present because a Workflow's `run()` is driven through it, and
|
|
70
|
+
* `passThroughOnException` is absent on purpose — nothing in a Node suite wires a
|
|
71
|
+
* request through it.
|
|
72
|
+
*/
|
|
73
|
+
export interface ExecutionContext {
|
|
74
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The base class every Durable Object extends.
|
|
78
|
+
*
|
|
79
|
+
* `protected`, exactly as on the platform: a DO subclass reads `this.env` and
|
|
80
|
+
* `this.ctx`, and nothing outside it may.
|
|
81
|
+
*/
|
|
82
|
+
declare class DurableObject<TEnv = unknown> {
|
|
83
|
+
protected ctx: DurableObjectState;
|
|
84
|
+
protected env: TEnv;
|
|
85
|
+
constructor(ctx: DurableObjectState, env: TEnv);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The base class `dofs`' `Fs` extends. See the header: an empty body is the
|
|
89
|
+
* whole point, and a missing class is a module-load failure.
|
|
90
|
+
*/
|
|
91
|
+
declare class RpcTarget {
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The base class every Workflow extends.
|
|
95
|
+
*
|
|
96
|
+
* `ctx` is `ExecutionContext` rather than `unknown` (Mail-Meow/Mail-Otter's
|
|
97
|
+
* form) because a Workflow's step code reads it; `void ctx` to silence the unused
|
|
98
|
+
* warning would be a lie about which of the two a subclass uses.
|
|
99
|
+
*/
|
|
100
|
+
declare class WorkflowEntrypoint<TEnv = unknown> {
|
|
101
|
+
protected ctx: ExecutionContext;
|
|
102
|
+
protected env: TEnv;
|
|
103
|
+
constructor(ctx: ExecutionContext, env: TEnv);
|
|
104
|
+
}
|
|
105
|
+
export { DurableObject, RpcTarget, WorkflowEntrypoint };
|
|
106
|
+
//# sourceMappingURL=cloudflare-workers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-workers.d.ts","sourceRoot":"","sources":["../../../src/test/mocks/cloudflare-workers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE;QAAE,QAAQ,IAAI,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5C;AAED;;;;;GAKG;AACH,cAAM,aAAa,CAAC,IAAI,GAAG,OAAO;IAChC,SAAS,CAAC,GAAG,EAAE,kBAAkB,CAAC;IAClC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC;gBAER,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,IAAI;CAI/C;AAED;;;GAGG;AACH,cAAM,SAAS;CAAG;AAElB;;;;;;GAMG;AACH,cAAM,kBAAkB,CAAC,IAAI,GAAG,OAAO;IACrC,SAAS,CAAC,GAAG,EAAE,gBAAgB,CAAC;IAChC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC;gBAER,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,IAAI;CAI7C;AAED,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stand-in for the `cloudflare:workers` module, which has no Node equivalent.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from the eight copies at `test/mocks/cloudflare-workers.ts`
|
|
5
|
+
* (AWS-AccessBridge, CalDAV-Bridge, ChordDHT-Tracker, Durable-DAV, Edge-Git,
|
|
6
|
+
* Edge-Sonic, Mail-Meow, Mail-Otter). All eight are the same twenty lines; the
|
|
7
|
+
* union is three base classes and the union exists because each repo discovered a
|
|
8
|
+
* missing one the hard way.
|
|
9
|
+
*
|
|
10
|
+
* ### What each source contributes
|
|
11
|
+
*
|
|
12
|
+
* - **DurableObject** — every copy. Stores `ctx` and `env` so a real DO class can
|
|
13
|
+
* be constructed and its lifecycle RPCs driven directly, without Miniflare.
|
|
14
|
+
* - **`RpcTarget`** — Durable-DAV's, and the reason is load-bearing rather than
|
|
15
|
+
* tidy: `dofs`' `Fs` class `extends RpcTarget`, so the module fails to evaluate
|
|
16
|
+
* with *"Class extends value undefined"* when the base is missing. That is a
|
|
17
|
+
* module-load error, not a test failure, so it surfaces as an unimportable
|
|
18
|
+
* `dav-store` barrel and no unit test can reach `DavRepository`. Deliberately
|
|
19
|
+
* empty: on the platform it marks which methods are callable across an RPC
|
|
20
|
+
* boundary, and nothing in a Node suite calls those through RPC. An empty base
|
|
21
|
+
* keeps the fake honest instead of pretending to implement dispatch.
|
|
22
|
+
* - **`WorkflowEntrypoint`** — Edge-Git, Edge-Sonic, Mail-Meow, Mail-Otter. A
|
|
23
|
+
* Workflow class extends it, so the mock must provide it or the module fails the
|
|
24
|
+
* same way — a **startup** error, which therefore takes down every suite that
|
|
25
|
+
* reaches `apps/background` for an unrelated reason. Edge-Sonic's is the shape
|
|
26
|
+
* kept: it stores `ctx` as well as `env`, because the failure policy under test
|
|
27
|
+
* in `runWorkflow` lives in a plain function precisely so it can be exercised
|
|
28
|
+
* without workerd, and that policy reads the execution context.
|
|
29
|
+
*
|
|
30
|
+
* ### Why the ambient types are declared here rather than imported
|
|
31
|
+
*
|
|
32
|
+
* The source repos name `DurableObjectState`, `ExecutionContext` and `Env`
|
|
33
|
+
* unqualified, which resolves only because `worker-configuration.d.ts` and
|
|
34
|
+
* `@cloudflare/workers-types` are ambient inside a Wrangler project. This copy is
|
|
35
|
+
* imported by the kit's own typecheck, where neither exists, so the two state
|
|
36
|
+
* shapes are declared locally and `TEnv` defaults to `unknown` instead of `Env`
|
|
37
|
+
* (Edge-Sonic's form). A local declaration shadows the ambient one inside this
|
|
38
|
+
* file only, so a consumer's copy typechecks against their generated types
|
|
39
|
+
* exactly as the original did.
|
|
40
|
+
*
|
|
41
|
+
* ### Usage in a consumer
|
|
42
|
+
*
|
|
43
|
+
* Copy this file to `<repo>/test/mocks/cloudflare-workers.ts` and alias the
|
|
44
|
+
* specifier in the Vitest config — `defineUnitVitestConfig()` already does:
|
|
45
|
+
*
|
|
46
|
+
* ```jsonc
|
|
47
|
+
* // wrangler.jsonc / vitest alias
|
|
48
|
+
* "cloudflare:workers": "test/mocks/cloudflare-workers.ts"
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* Only what a test actually imports is exported. A symbol here that nothing
|
|
52
|
+
* imports is not a safety net; it is a claim that something depends on it.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* The base class every Durable Object extends.
|
|
56
|
+
*
|
|
57
|
+
* `protected`, exactly as on the platform: a DO subclass reads `this.env` and
|
|
58
|
+
* `this.ctx`, and nothing outside it may.
|
|
59
|
+
*/
|
|
60
|
+
class DurableObject {
|
|
61
|
+
ctx;
|
|
62
|
+
env;
|
|
63
|
+
constructor(ctx, env) {
|
|
64
|
+
this.ctx = ctx;
|
|
65
|
+
this.env = env;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The base class `dofs`' `Fs` extends. See the header: an empty body is the
|
|
70
|
+
* whole point, and a missing class is a module-load failure.
|
|
71
|
+
*/
|
|
72
|
+
class RpcTarget {
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The base class every Workflow extends.
|
|
76
|
+
*
|
|
77
|
+
* `ctx` is `ExecutionContext` rather than `unknown` (Mail-Meow/Mail-Otter's
|
|
78
|
+
* form) because a Workflow's step code reads it; `void ctx` to silence the unused
|
|
79
|
+
* warning would be a lie about which of the two a subclass uses.
|
|
80
|
+
*/
|
|
81
|
+
class WorkflowEntrypoint {
|
|
82
|
+
ctx;
|
|
83
|
+
env;
|
|
84
|
+
constructor(ctx, env) {
|
|
85
|
+
this.ctx = ctx;
|
|
86
|
+
this.env = env;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export { DurableObject, RpcTarget, WorkflowEntrypoint };
|
|
90
|
+
//# sourceMappingURL=cloudflare-workers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-workers.js","sourceRoot":"","sources":["../../../src/test/mocks/cloudflare-workers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAuBH;;;;;GAKG;AACH,MAAM,aAAa;IACP,GAAG,CAAqB;IACxB,GAAG,CAAO;IAEpB,YAAY,GAAuB,EAAE,GAAS;QAC5C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,SAAS;CAAG;AAElB;;;;;;GAMG;AACH,MAAM,kBAAkB;IACZ,GAAG,CAAmB;IACtB,GAAG,CAAO;IAEpB,YAAY,GAAqB,EAAE,GAAS;QAC1C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAED,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC"}
|