@endora-commerce/test-kit 0.100.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 +108 -0
- package/dist/database/index.d.ts +17 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +17 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/lease.d.ts +115 -0
- package/dist/database/lease.d.ts.map +1 -0
- package/dist/database/lease.js +204 -0
- package/dist/database/lease.js.map +1 -0
- package/dist/database/provision.d.ts +135 -0
- package/dist/database/provision.d.ts.map +1 -0
- package/dist/database/provision.js +457 -0
- package/dist/database/provision.js.map +1 -0
- package/dist/database/run-isolation.d.ts +356 -0
- package/dist/database/run-isolation.d.ts.map +1 -0
- package/dist/database/run-isolation.js +490 -0
- package/dist/database/run-isolation.js.map +1 -0
- package/dist/server/compose-test-server.d.ts +251 -0
- package/dist/server/compose-test-server.d.ts.map +1 -0
- package/dist/server/compose-test-server.js +359 -0
- package/dist/server/compose-test-server.js.map +1 -0
- package/dist/server/composition.d.ts +112 -0
- package/dist/server/composition.d.ts.map +1 -0
- package/dist/server/composition.js +32 -0
- package/dist/server/composition.js.map +1 -0
- package/dist/server/index.d.ts +13 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +12 -0
- package/dist/server/index.js.map +1 -0
- package/dist/support/entity-index.d.ts +91 -0
- package/dist/support/entity-index.d.ts.map +1 -0
- package/dist/support/entity-index.js +56 -0
- package/dist/support/entity-index.js.map +1 -0
- package/dist/support/index.d.ts +134 -0
- package/dist/support/index.d.ts.map +1 -0
- package/dist/support/index.js +153 -0
- package/dist/support/index.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provision.d.ts","sourceRoot":"","sources":["../../src/database/provision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAoBjC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAoF5C;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAiB9E;AAED,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC;;;;;WAKG;QACH,QAAQ,CAAC,OAAO,EAAE,SAAS;YACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;YAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;SACjC,EAAE,CAAC;KACL,CAAC;IACF,0FAA0F;IAC1F,QAAQ,CAAC,eAAe,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;CACpB;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CA2FtF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,GAAgB,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CAwCnB;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,GAAgB,GACpB,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,GAAgB,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CAqCnB;AAcD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AA2BD,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,GAAgB,GACpB,OAAO,CAAC,UAAU,CAAC,CA8DrB"}
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The half of per-invocation isolation (issue #189) that talks to a server.
|
|
3
|
+
*
|
|
4
|
+
* Split from `run-isolation.ts` so the naming and selection rules — the two
|
|
5
|
+
* things whose correctness protects the dev database and other people's
|
|
6
|
+
* databases — are unit-testable with no service at all, and run in the fast
|
|
7
|
+
* unit job. Nothing here is imported by a test file: `leaseRunDatabase` in
|
|
8
|
+
* `./lease.ts` is the only caller, and a vitest `globalSetup` calls that once
|
|
9
|
+
* per invocation in the parent process.
|
|
10
|
+
*
|
|
11
|
+
* `pg` and `ioredis` are imported dynamically for the same reason: a run that
|
|
12
|
+
* declared `BACKEND_TEST_SERVICES=none` never reaches this module, and should
|
|
13
|
+
* not pay to load a driver it will not dial.
|
|
14
|
+
*/
|
|
15
|
+
import { SWEEP_LIMIT, STALE_RUN_DATABASE_MS, STALE_TEMPLATE_MS, advisoryLockKey, databaseNameOf, formatTemplateProvenance, parseTemplateProvenance, redisUrlWithDatabase, runDatabaseName, staleTemplates, strandedRunDatabases, templateDatabaseName, templateDrift, templateFamilyName, withDatabase, } from './run-isolation.js';
|
|
16
|
+
const defaultLog = (message) => process.stdout.write(`${message}\n`);
|
|
17
|
+
/** The maintenance database `create database` / `drop database` are issued from. */
|
|
18
|
+
function adminUrl(url) {
|
|
19
|
+
return withDatabase(url, 'postgres');
|
|
20
|
+
}
|
|
21
|
+
async function connectAdmin(url) {
|
|
22
|
+
const { Client: PgClient } = await import('pg');
|
|
23
|
+
const client = new PgClient({ connectionString: adminUrl(url) });
|
|
24
|
+
await client.connect();
|
|
25
|
+
return client;
|
|
26
|
+
}
|
|
27
|
+
async function databaseExists(admin, name) {
|
|
28
|
+
const { rowCount } = await admin.query('select 1 from pg_database where datname = $1', [name]);
|
|
29
|
+
return (rowCount ?? 0) > 0;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* What a database says about itself.
|
|
33
|
+
*
|
|
34
|
+
* A database comment is the one piece of state a template can carry that a
|
|
35
|
+
* clone does not inherit — `create database … template …` copies the contents
|
|
36
|
+
* and leaves `pg_shdescription` behind, which is exactly right: the provenance
|
|
37
|
+
* is the *template's*, and a run database has none. It also needs no table in
|
|
38
|
+
* the template and no connection to it.
|
|
39
|
+
*/
|
|
40
|
+
async function databaseComment(admin, name) {
|
|
41
|
+
const { rows } = await admin.query("select shobj_description(oid, 'pg_database') as note from pg_database where datname = $1", [name]);
|
|
42
|
+
return rows[0]?.note ?? undefined;
|
|
43
|
+
}
|
|
44
|
+
async function setDatabaseComment(admin, name, comment) {
|
|
45
|
+
// Neither identifier nor comment can be parameterised here. The name is one
|
|
46
|
+
// this module generated and the comment is assembled from a digest and a
|
|
47
|
+
// timestamp, but the escape stays because the alternative is a rule nobody
|
|
48
|
+
// can see being followed.
|
|
49
|
+
await admin.query(`comment on database "${name}" is '${comment.replace(/'/g, "''")}'`);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `create database … template …` requires the source to be **idle** — a single
|
|
53
|
+
* other session and PostgreSQL refuses with 55006. Our own invocations are
|
|
54
|
+
* serialized by the advisory lock, so what this retries around is the tail of
|
|
55
|
+
* another run's template connection closing, and a human's `psql` into the
|
|
56
|
+
* template.
|
|
57
|
+
*/
|
|
58
|
+
const CLONE_ATTEMPTS = 60;
|
|
59
|
+
const CLONE_RETRY_MS = 500;
|
|
60
|
+
function isSourceInUse(error) {
|
|
61
|
+
const code = error.code;
|
|
62
|
+
const message = error.message ?? '';
|
|
63
|
+
return code === '55006' || /is being accessed by other users/.test(message);
|
|
64
|
+
}
|
|
65
|
+
async function cloneFromTemplate(admin, name, template) {
|
|
66
|
+
for (let attempt = 1; attempt <= CLONE_ATTEMPTS; attempt += 1) {
|
|
67
|
+
try {
|
|
68
|
+
await admin.query(`create database "${name}" template "${template}"`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (!isSourceInUse(error) || attempt === CLONE_ATTEMPTS) {
|
|
73
|
+
if (isSourceInUse(error)) {
|
|
74
|
+
throw new Error(`could not clone the test template "${template}" after ` +
|
|
75
|
+
`${(CLONE_ATTEMPTS * CLONE_RETRY_MS) / 1000}s: something is connected to it. ` +
|
|
76
|
+
`Nothing runs tests against the template, so this is usually an open psql ` +
|
|
77
|
+
`session. Close it, or run with BACKEND_TEST_ISOLATION=shared.`);
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, CLONE_RETRY_MS));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The migration names applied to a database, in the order they were applied.
|
|
87
|
+
*
|
|
88
|
+
* `mikro_orm_migrations` is missing on a template that has just been created
|
|
89
|
+
* and on one that has never been migrated; both mean "nothing applied", which
|
|
90
|
+
* is a prefix of every order and so needs no rebuild.
|
|
91
|
+
*/
|
|
92
|
+
export async function appliedMigrations(databaseUrl) {
|
|
93
|
+
const { Client: PgClient } = await import('pg');
|
|
94
|
+
const client = new PgClient({ connectionString: databaseUrl });
|
|
95
|
+
await client.connect();
|
|
96
|
+
try {
|
|
97
|
+
const { rows } = await client.query('select name from mikro_orm_migrations order by id asc');
|
|
98
|
+
return rows.map((row) => row.name);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error.code === '42P01')
|
|
102
|
+
return [];
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
// Before the clone, not after: `create database … template …` refuses a
|
|
107
|
+
// source anything is connected to.
|
|
108
|
+
await client.end();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Whether a template holds this run's migration set and nothing else. */
|
|
112
|
+
function isExactly(applied, expected) {
|
|
113
|
+
return applied.length === expected.length && templateDrift(applied, expected) === undefined;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Give this invocation its own database.
|
|
117
|
+
*
|
|
118
|
+
* Everything between taking the advisory lock and releasing it is serialized
|
|
119
|
+
* across invocations: identify the template, build it if it is not there or not
|
|
120
|
+
* what it claims, migrate it, disconnect from it, clone, collect the templates
|
|
121
|
+
* nobody uses any more. Holding the lock across the clone as well is deliberate
|
|
122
|
+
* — the clone is a fraction of a second, and it is what guarantees the template
|
|
123
|
+
* is idle at the moment the next invocation's migration opens a connection to
|
|
124
|
+
* it.
|
|
125
|
+
*
|
|
126
|
+
* The lock is taken on the `postgres` maintenance database: PostgreSQL advisory
|
|
127
|
+
* locks are scoped to a database, so every waiter has to agree on which, and it
|
|
128
|
+
* must not be the template — a session holding the lock there is exactly the
|
|
129
|
+
* session that would make the clone illegal. It is one key for the whole
|
|
130
|
+
* `<base>_tpl*` family rather than one per digest, because the sweep walks the
|
|
131
|
+
* templates of other digests (see `templateFamilyName`).
|
|
132
|
+
*
|
|
133
|
+
* **Which template, is the question this function exists to answer** (issue
|
|
134
|
+
* #289). It is `<base>_tpl_<digest>`, the digest being over this run's whole
|
|
135
|
+
* migration input, so a template is shared only by runs whose platform it
|
|
136
|
+
* actually is. Two things then have to agree before it is cloned: the
|
|
137
|
+
* provenance comment the build wrote on it, and the migration names it actually
|
|
138
|
+
* holds. Either disagreeing rebuilds it from empty — including "it says
|
|
139
|
+
* nothing", because a template that cannot say what it holds is the defect.
|
|
140
|
+
*/
|
|
141
|
+
export async function provisionRunDatabase(input) {
|
|
142
|
+
const log = input.log ?? defaultLog;
|
|
143
|
+
const base = databaseNameOf(input.baseUrl);
|
|
144
|
+
const { digest, migrations: expected } = input.identity;
|
|
145
|
+
const template = templateDatabaseName(base, digest);
|
|
146
|
+
const templateUrl = withDatabase(input.baseUrl, template);
|
|
147
|
+
const name = runDatabaseName(base);
|
|
148
|
+
const lockKey = advisoryLockKey(templateFamilyName(base));
|
|
149
|
+
const byOrigin = input.identity.origins
|
|
150
|
+
.map((population) => `${population.origin}:${population.migrationFiles}/${population.registered}`)
|
|
151
|
+
.join(' ');
|
|
152
|
+
log(`[test-setup] migration set ${digest}: ${expected.length} migration(s), read from ` +
|
|
153
|
+
`${input.identity.migrationFiles} source file(s) — ${byOrigin} — template ${template}`);
|
|
154
|
+
const admin = await connectAdmin(input.baseUrl);
|
|
155
|
+
try {
|
|
156
|
+
await admin.query('select pg_advisory_lock($1)', [lockKey]);
|
|
157
|
+
try {
|
|
158
|
+
let rebuild;
|
|
159
|
+
if (!(await databaseExists(admin, template))) {
|
|
160
|
+
rebuild = `no template for this migration set exists yet`;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const provenance = parseTemplateProvenance(await databaseComment(admin, template));
|
|
164
|
+
const applied = await appliedMigrations(templateUrl);
|
|
165
|
+
if (provenance === undefined) {
|
|
166
|
+
// Unknown provenance is the whole defect: a database whose contents
|
|
167
|
+
// nothing can account for. It is never cloned, whatever its name.
|
|
168
|
+
rebuild = `${template} carries no provenance this harness wrote`;
|
|
169
|
+
}
|
|
170
|
+
else if (provenance.digest !== digest) {
|
|
171
|
+
rebuild = `${template} says it holds migration set ${provenance.digest}, not ${digest}`;
|
|
172
|
+
}
|
|
173
|
+
else if (!isExactly(applied, expected)) {
|
|
174
|
+
// The provenance says one thing and the database says another —
|
|
175
|
+
// a build that died half-way, or somebody's psql session.
|
|
176
|
+
rebuild =
|
|
177
|
+
`${template} holds ${applied.length} of this run's ${expected.length} migration(s), ` +
|
|
178
|
+
`and ${templateDrift(applied, expected, template)?.message ?? 'not in order'}`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (rebuild !== undefined) {
|
|
182
|
+
log(`[test-setup] ${rebuild} — building it from empty.`);
|
|
183
|
+
await admin.query(`drop database if exists "${template}" with (force)`);
|
|
184
|
+
await admin.query(`create database "${template}"`);
|
|
185
|
+
await input.migrateTemplate(templateUrl);
|
|
186
|
+
// A clone is only worth anything if the template is now exactly this
|
|
187
|
+
// run's platform. After a build-and-migrate it is, by construction — so
|
|
188
|
+
// a finding here is a real defect and gets said out loud rather than
|
|
189
|
+
// handed to every test file in the run as a database that is quietly
|
|
190
|
+
// not what the code says it is. It is also what catches a
|
|
191
|
+
// `migrateTemplate` that migrated *something else*, which is a live
|
|
192
|
+
// hazard while the ORM config resolves its DSN from an ambient
|
|
193
|
+
// variable.
|
|
194
|
+
const applied = await appliedMigrations(templateUrl);
|
|
195
|
+
if (!isExactly(applied, expected)) {
|
|
196
|
+
throw new Error(`the migration template is not this run's migration set after migrating it: it holds ` +
|
|
197
|
+
`${applied.length} migration(s) against ${expected.length} configured` +
|
|
198
|
+
`${templateDrift(applied, expected, template) ? `, and ${templateDrift(applied, expected, template)?.message}` : ''}. ` +
|
|
199
|
+
`Drop "${template}" by hand and report this — a run cloned from it would not be ` +
|
|
200
|
+
`the platform this branch's code describes.`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Written after the template is known good, and rewritten on every use:
|
|
204
|
+
// the digest is what a later run checks, and `lastUsedAt` is what the
|
|
205
|
+
// sweep reads. A build that dies before this line leaves a template with
|
|
206
|
+
// no provenance, which the next run rebuilds rather than trusts.
|
|
207
|
+
await setDatabaseComment(admin, template, formatTemplateProvenance({ digest, migrations: expected.length, lastUsedAt: new Date() }));
|
|
208
|
+
await cloneFromTemplate(admin, name, template);
|
|
209
|
+
await sweepStaleTemplates(admin, base, template, log);
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
await admin.query('select pg_advisory_unlock($1)', [lockKey]);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
await admin.end();
|
|
217
|
+
}
|
|
218
|
+
log(`[test-setup] this invocation owns database ${name} (cloned from ${template})`);
|
|
219
|
+
return { url: withDatabase(input.baseUrl, name), name, template };
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* A database of this run's template, for a caller that is not the invocation.
|
|
223
|
+
*
|
|
224
|
+
* The template is named by the caller, not re-derived here, and that is the
|
|
225
|
+
* point (issue #289): the lease resolved which template this run's
|
|
226
|
+
* platform is and exported the name, so a file cloning one mid-run gets the
|
|
227
|
+
* database the run itself was cloned from — not whatever `<base>_tpl` has
|
|
228
|
+
* become while the run has been going, which with several branches on one
|
|
229
|
+
* cluster is routinely another branch's schema.
|
|
230
|
+
*
|
|
231
|
+
* The advisory lock is the same one provisioning takes, and for the same
|
|
232
|
+
* reason: it is what keeps the template idle at the moment a clone starts.
|
|
233
|
+
* `setupMigratorTestDb` is the only caller — a test file that drives the real
|
|
234
|
+
* migrator and must not do it to the database its neighbours share.
|
|
235
|
+
*/
|
|
236
|
+
export async function cloneTemplateForCaller(baseUrl, template) {
|
|
237
|
+
const base = databaseNameOf(baseUrl);
|
|
238
|
+
const name = runDatabaseName(base);
|
|
239
|
+
const lockKey = advisoryLockKey(templateFamilyName(base));
|
|
240
|
+
const admin = await connectAdmin(baseUrl);
|
|
241
|
+
try {
|
|
242
|
+
await admin.query('select pg_advisory_lock($1)', [lockKey]);
|
|
243
|
+
try {
|
|
244
|
+
if (!(await databaseExists(admin, template))) {
|
|
245
|
+
throw new Error(`this run's migration template "${template}" is gone. Nothing drops a template in ` +
|
|
246
|
+
`use, so either something dropped it by hand or this run outlived it — either way ` +
|
|
247
|
+
`a clone of what is there now would not be the platform this run started on.`);
|
|
248
|
+
}
|
|
249
|
+
await cloneFromTemplate(admin, name, template);
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
await admin.query('select pg_advisory_unlock($1)', [lockKey]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
await admin.end();
|
|
257
|
+
}
|
|
258
|
+
return { url: withDatabase(baseUrl, name), name, template };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Drop the templates of migration sets nobody runs any more.
|
|
262
|
+
*
|
|
263
|
+
* Keying a template by its migration set is what stops two branches sharing
|
|
264
|
+
* one; this is the other half of that bargain, because a branch leaves its
|
|
265
|
+
* set's template behind the moment it gains a migration or is rebased. Called
|
|
266
|
+
* under the provisioning lock, so no template can be half-built while it runs.
|
|
267
|
+
*
|
|
268
|
+
* Best-effort and never fatal: a sweep that fails is a disk-space problem, not
|
|
269
|
+
* a reason to fail somebody's test run.
|
|
270
|
+
*/
|
|
271
|
+
export async function sweepStaleTemplates(admin, base, keep, log = defaultLog) {
|
|
272
|
+
const dropped = [];
|
|
273
|
+
try {
|
|
274
|
+
const { rows } = await admin.query("select datname, shobj_description(oid, 'pg_database') as note from pg_database");
|
|
275
|
+
const busy = await admin.query('select distinct datname from pg_stat_activity where datname is not null');
|
|
276
|
+
const lastUsed = new Map();
|
|
277
|
+
for (const row of rows) {
|
|
278
|
+
const provenance = parseTemplateProvenance(row.note);
|
|
279
|
+
if (provenance)
|
|
280
|
+
lastUsed.set(row.datname, provenance.lastUsedAt);
|
|
281
|
+
}
|
|
282
|
+
const stale = staleTemplates({
|
|
283
|
+
base,
|
|
284
|
+
names: rows.map((row) => row.datname),
|
|
285
|
+
busy: new Set(busy.rows.map((row) => row.datname)),
|
|
286
|
+
keep,
|
|
287
|
+
lastUsed,
|
|
288
|
+
now: new Date(),
|
|
289
|
+
maxAgeMs: STALE_TEMPLATE_MS,
|
|
290
|
+
limit: SWEEP_LIMIT,
|
|
291
|
+
});
|
|
292
|
+
for (const name of stale) {
|
|
293
|
+
try {
|
|
294
|
+
await admin.query(`drop database if exists "${name}" with (force)`);
|
|
295
|
+
dropped.push(name);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// Somebody connected between the read and the drop. The next
|
|
299
|
+
// invocation sweeps again.
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (dropped.length > 0) {
|
|
303
|
+
log(`[test-setup] swept ${dropped.length} migration template(s) no branch has used lately`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
log(`[test-setup] could not sweep stale migration templates: ${error.message}`);
|
|
308
|
+
}
|
|
309
|
+
return dropped;
|
|
310
|
+
}
|
|
311
|
+
export async function dropRunDatabase(baseUrl, name, log = defaultLog) {
|
|
312
|
+
const admin = await connectAdmin(baseUrl);
|
|
313
|
+
try {
|
|
314
|
+
// `with (force)` terminates whatever is still connected — a worker whose
|
|
315
|
+
// pool has not finished closing is the ordinary case, and the alternative
|
|
316
|
+
// is leaving the database behind for the sweep.
|
|
317
|
+
await admin.query(`drop database if exists "${name}" with (force)`);
|
|
318
|
+
log(`[test-setup] dropped ${name}`);
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
await admin.end();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Drop the run databases of invocations that crashed before their teardown.
|
|
326
|
+
*
|
|
327
|
+
* Best-effort and never fatal: a sweep that fails is a disk-space problem, not
|
|
328
|
+
* a reason to fail somebody's test run.
|
|
329
|
+
*/
|
|
330
|
+
export async function sweepStrandedRunDatabases(baseUrl, keep, log = defaultLog) {
|
|
331
|
+
const base = databaseNameOf(baseUrl);
|
|
332
|
+
const dropped = [];
|
|
333
|
+
let admin;
|
|
334
|
+
try {
|
|
335
|
+
admin = await connectAdmin(baseUrl);
|
|
336
|
+
const { rows } = await admin.query('select datname from pg_database');
|
|
337
|
+
const busy = await admin.query('select distinct datname from pg_stat_activity where datname is not null');
|
|
338
|
+
const stranded = strandedRunDatabases({
|
|
339
|
+
base,
|
|
340
|
+
names: rows.map((r) => r.datname),
|
|
341
|
+
busy: new Set(busy.rows.map((r) => r.datname)),
|
|
342
|
+
keep,
|
|
343
|
+
now: new Date(),
|
|
344
|
+
maxAgeMs: STALE_RUN_DATABASE_MS,
|
|
345
|
+
limit: SWEEP_LIMIT,
|
|
346
|
+
});
|
|
347
|
+
for (const name of stranded) {
|
|
348
|
+
try {
|
|
349
|
+
await admin.query(`drop database if exists "${name}" with (force)`);
|
|
350
|
+
dropped.push(name);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
// Somebody connected between the read and the drop, or it is already
|
|
354
|
+
// gone. The next invocation sweeps again.
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (dropped.length > 0) {
|
|
358
|
+
log(`[test-setup] swept ${dropped.length} stranded run database(s) from crashed runs`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
log(`[test-setup] could not sweep stranded run databases: ${error.message}`);
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
await admin?.end();
|
|
366
|
+
}
|
|
367
|
+
return dropped;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* The Redis half.
|
|
371
|
+
*
|
|
372
|
+
* A logical database index, leased in index 0 so the lease itself is visible to
|
|
373
|
+
* every invocation. The TTL is what makes a crashed run give its index back
|
|
374
|
+
* without anybody sweeping: five minutes, refreshed once a minute for as long
|
|
375
|
+
* as the vitest parent process lives.
|
|
376
|
+
*/
|
|
377
|
+
const LEASE_PREFIX = 'b2b:test-run-lease:';
|
|
378
|
+
const LEASE_TTL_MS = 5 * 60 * 1000;
|
|
379
|
+
const LEASE_REFRESH_MS = 60 * 1000;
|
|
380
|
+
async function connectRedis(url) {
|
|
381
|
+
// Named, never default: `ioredis`' `built/index.js` reassigns
|
|
382
|
+
// `module.exports` to the class while its `.d.ts` writes `export { default }`,
|
|
383
|
+
// so NodeNext models the default binding as the module namespace — TS2709 as a
|
|
384
|
+
// type, TS2351 as a constructor. `default === Redis` at runtime, and
|
|
385
|
+
// `test/unit/packages/package-dist-build.test.ts` compiles this package's
|
|
386
|
+
// emitted `dist` in a NodeNext consumer, which is the only place that error
|
|
387
|
+
// can be seen. See AGENTS.md's stack note.
|
|
388
|
+
const { Redis: IORedis } = await import('ioredis');
|
|
389
|
+
return new IORedis(url, { maxRetriesPerRequest: null, lazyConnect: false });
|
|
390
|
+
}
|
|
391
|
+
/** How many logical databases this server has. `databases` is 16 unless someone raised it. */
|
|
392
|
+
async function redisDatabaseCount(client) {
|
|
393
|
+
try {
|
|
394
|
+
const config = await client.config('GET', 'databases');
|
|
395
|
+
const value = Array.isArray(config) ? Number.parseInt(String(config[1]), 10) : NaN;
|
|
396
|
+
return Number.isFinite(value) && value > 1 ? value : 16;
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
// A managed Redis may refuse CONFIG GET. 16 is the server default and the
|
|
400
|
+
// only number this repository's compose file and CI service ever have.
|
|
401
|
+
return 16;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
export async function leaseRedisDatabase(redisUrl, runId, log = defaultLog) {
|
|
405
|
+
const registry = await connectRedis(redisUrlWithDatabase(redisUrl, 0));
|
|
406
|
+
const count = await redisDatabaseCount(registry);
|
|
407
|
+
let index;
|
|
408
|
+
// Index 0 stays the shared one: it holds the leases, and it is what a
|
|
409
|
+
// developer's `pnpm run dev` uses.
|
|
410
|
+
for (let candidate = 1; candidate < count; candidate += 1) {
|
|
411
|
+
const taken = await registry.set(`${LEASE_PREFIX}${candidate}`, runId, 'PX', LEASE_TTL_MS, 'NX');
|
|
412
|
+
if (taken === 'OK') {
|
|
413
|
+
index = candidate;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (index === undefined) {
|
|
418
|
+
registry.disconnect();
|
|
419
|
+
throw new Error(`every one of this Redis server's ${count - 1} lease-able logical databases is held by ` +
|
|
420
|
+
`another test invocation. A lease expires ${LEASE_TTL_MS / 60000} minutes after the run ` +
|
|
421
|
+
`holding it dies, so this is either that many live runs or a very recent pile of ` +
|
|
422
|
+
`crashes. Wait, raise the server's \`databases\`, or run with ` +
|
|
423
|
+
`BACKEND_TEST_ISOLATION=shared.`);
|
|
424
|
+
}
|
|
425
|
+
const url = redisUrlWithDatabase(redisUrl, index);
|
|
426
|
+
// Whatever a crashed run left in this index is not this run's state. The
|
|
427
|
+
// lease makes the index exclusively ours, so this cannot reach anybody else.
|
|
428
|
+
const owned = await connectRedis(url);
|
|
429
|
+
await owned.flushdb();
|
|
430
|
+
const refresh = setInterval(() => {
|
|
431
|
+
void registry.set(`${LEASE_PREFIX}${index}`, runId, 'PX', LEASE_TTL_MS, 'XX');
|
|
432
|
+
}, LEASE_REFRESH_MS);
|
|
433
|
+
// The refresher must not be what keeps the vitest process alive.
|
|
434
|
+
refresh.unref();
|
|
435
|
+
log(`[test-setup] this invocation owns Redis database ${index}`);
|
|
436
|
+
return {
|
|
437
|
+
url,
|
|
438
|
+
index,
|
|
439
|
+
runId,
|
|
440
|
+
release: async () => {
|
|
441
|
+
clearInterval(refresh);
|
|
442
|
+
try {
|
|
443
|
+
await owned.flushdb();
|
|
444
|
+
// Compare-and-delete: never release a lease that has already expired
|
|
445
|
+
// and been taken by somebody else.
|
|
446
|
+
const holder = await registry.get(`${LEASE_PREFIX}${index}`);
|
|
447
|
+
if (holder === runId)
|
|
448
|
+
await registry.del(`${LEASE_PREFIX}${index}`);
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
owned.disconnect();
|
|
452
|
+
registry.disconnect();
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
//# sourceMappingURL=provision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/database/provision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAI5B,MAAM,UAAU,GAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;AAE1E,oFAAoF;AACpF,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,YAAY,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;AACvC,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,GAAW;IACrC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,EAAE,gBAAgB,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjE,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACvB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,KAAa,EAAE,IAAY;IACvD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,8CAA8C,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/F,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,eAAe,CAAC,KAAa,EAAE,IAAY;IACxD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,KAAK,CAChC,0FAA0F,EAC1F,CAAC,IAAI,CAAC,CACP,CAAC;IACF,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,SAAS,CAAC;AACpC,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,KAAa,EAAE,IAAY,EAAE,OAAe;IAC5E,4EAA4E;IAC5E,yEAAyE;IACzE,2EAA2E;IAC3E,0BAA0B;IAC1B,MAAM,KAAK,CAAC,KAAK,CAAC,wBAAwB,IAAI,SAAS,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;AACzF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,SAAS,aAAa,CAAC,KAAc;IACnC,MAAM,IAAI,GAAI,KAA2B,CAAC,IAAI,CAAC;IAC/C,MAAM,OAAO,GAAI,KAA8B,CAAC,OAAO,IAAI,EAAE,CAAC;IAC9D,OAAO,IAAI,KAAK,OAAO,IAAI,kCAAkC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC9E,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,KAAa,EAAE,IAAY,EAAE,QAAgB;IAC5E,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,cAAc,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC9D,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,KAAK,CAAC,oBAAoB,IAAI,eAAe,QAAQ,GAAG,CAAC,CAAC;YACtE,OAAO;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,cAAc,EAAE,CAAC;gBACxD,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,MAAM,IAAI,KAAK,CACb,sCAAsC,QAAQ,UAAU;wBACtD,GAAG,CAAC,cAAc,GAAG,cAAc,CAAC,GAAG,IAAI,mCAAmC;wBAC9E,2EAA2E;wBAC3E,+DAA+D,CAClE,CAAC;gBACJ,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,WAAmB;IACzD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC,CAAC;IAC/D,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CACjC,uDAAuD,CACxD,CAAC;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA2B,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,CAAC;QAC7D,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,wEAAwE;QACxE,mCAAmC;QACnC,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;IACrB,CAAC;AACH,CAAC;AA4CD,0EAA0E;AAC1E,SAAS,SAAS,CAAC,OAA0B,EAAE,QAA2B;IACxE,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,SAAS,CAAC;AAC9F,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAAqB;IAC9D,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,UAAU,CAAC;IACpC,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC;IACxD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,eAAe,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;IAE1D,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO;SACpC,GAAG,CACF,CAAC,UAAU,EAAE,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,IAAI,UAAU,CAAC,cAAc,IAAI,UAAU,CAAC,UAAU,EAAE,CAC7F;SACA,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,GAAG,CACD,8BAA8B,MAAM,KAAK,QAAQ,CAAC,MAAM,2BAA2B;QACjF,GAAG,KAAK,CAAC,QAAQ,CAAC,cAAc,qBAAqB,QAAQ,eAAe,QAAQ,EAAE,CACzF,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,KAAK,CAAC,6BAA6B,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC;YACH,IAAI,OAA2B,CAAC;YAChC,IAAI,CAAC,CAAC,MAAM,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;gBAC7C,OAAO,GAAG,+CAA+C,CAAC;YAC5D,CAAC;iBAAM,CAAC;gBACN,MAAM,UAAU,GAAG,uBAAuB,CAAC,MAAM,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;gBACnF,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,WAAW,CAAC,CAAC;gBACrD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;oBAC7B,oEAAoE;oBACpE,kEAAkE;oBAClE,OAAO,GAAG,GAAG,QAAQ,2CAA2C,CAAC;gBACnE,CAAC;qBAAM,IAAI,UAAU,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;oBACxC,OAAO,GAAG,GAAG,QAAQ,gCAAgC,UAAU,CAAC,MAAM,SAAS,MAAM,EAAE,CAAC;gBAC1F,CAAC;qBAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;oBACzC,gEAAgE;oBAChE,0DAA0D;oBAC1D,OAAO;wBACL,GAAG,QAAQ,UAAU,OAAO,CAAC,MAAM,kBAAkB,QAAQ,CAAC,MAAM,iBAAiB;4BACrF,OAAO,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,IAAI,cAAc,EAAE,CAAC;gBACnF,CAAC;YACH,CAAC;YAED,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,GAAG,CAAC,gBAAgB,OAAO,4BAA4B,CAAC,CAAC;gBACzD,MAAM,KAAK,CAAC,KAAK,CAAC,4BAA4B,QAAQ,gBAAgB,CAAC,CAAC;gBACxE,MAAM,KAAK,CAAC,KAAK,CAAC,oBAAoB,QAAQ,GAAG,CAAC,CAAC;gBACnD,MAAM,KAAK,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;gBAEzC,qEAAqE;gBACrE,wEAAwE;gBACxE,qEAAqE;gBACrE,qEAAqE;gBACrE,0DAA0D;gBAC1D,oEAAoE;gBACpE,+DAA+D;gBAC/D,YAAY;gBACZ,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,WAAW,CAAC,CAAC;gBACrD,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;oBAClC,MAAM,IAAI,KAAK,CACb,sFAAsF;wBACpF,GAAG,OAAO,CAAC,MAAM,yBAAyB,QAAQ,CAAC,MAAM,aAAa;wBACtE,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI;wBACvH,SAAS,QAAQ,gEAAgE;wBACjF,4CAA4C,CAC/C,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,wEAAwE;YACxE,sEAAsE;YACtE,yEAAyE;YACzE,iEAAiE;YACjE,MAAM,kBAAkB,CACtB,KAAK,EACL,QAAQ,EACR,wBAAwB,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAC1F,CAAC;YAEF,MAAM,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC/C,MAAM,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;QACxD,CAAC;gBAAS,CAAC;YACT,MAAM,KAAK,CAAC,KAAK,CAAC,+BAA+B,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IAED,GAAG,CAAC,8CAA8C,IAAI,iBAAiB,QAAQ,GAAG,CAAC,CAAC;IACpF,OAAO,EAAE,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACpE,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,OAAe,EACf,QAAgB;IAEhB,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,eAAe,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,KAAK,CAAC,6BAA6B,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC;YACH,IAAI,CAAC,CAAC,MAAM,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;gBAC7C,MAAM,IAAI,KAAK,CACb,kCAAkC,QAAQ,yCAAyC;oBACjF,mFAAmF;oBACnF,6EAA6E,CAChF,CAAC;YACJ,CAAC;YACD,MAAM,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACjD,CAAC;gBAAS,CAAC;YACT,MAAM,KAAK,CAAC,KAAK,CAAC,+BAA+B,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAa,EACb,IAAY,EACZ,IAAY,EACZ,MAAW,UAAU;IAErB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,KAAK,CAChC,gFAAgF,CACjF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,KAAK,CAC5B,yEAAyE,CAC1E,CAAC;QACF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAgB,CAAC;QACzC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,UAAU,GAAG,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACrD,IAAI,UAAU;gBAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,KAAK,GAAG,cAAc,CAAC;YAC3B,IAAI;YACJ,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;YACrC,IAAI,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAClD,IAAI;YACJ,QAAQ;YACR,GAAG,EAAE,IAAI,IAAI,EAAE;YACf,QAAQ,EAAE,iBAAiB;YAC3B,KAAK,EAAE,WAAW;SACnB,CAAC,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,KAAK,CAAC,4BAA4B,IAAI,gBAAgB,CAAC,CAAC;gBACpE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,6DAA6D;gBAC7D,2BAA2B;YAC7B,CAAC;QACH,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,GAAG,CAAC,sBAAsB,OAAO,CAAC,MAAM,kDAAkD,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,2DAA4D,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAe,EACf,IAAY,EACZ,MAAW,UAAU;IAErB,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,CAAC;QACH,yEAAyE;QACzE,0EAA0E;QAC1E,gDAAgD;QAChD,MAAM,KAAK,CAAC,KAAK,CAAC,4BAA4B,IAAI,gBAAgB,CAAC,CAAC;QACpE,GAAG,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;YAAS,CAAC;QACT,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAe,EACf,IAAY,EACZ,MAAW,UAAU;IAErB,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,KAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,KAAK,CAAsB,iCAAiC,CAAC,CAAC;QAC3F,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,KAAK,CAC5B,yEAAyE,CAC1E,CAAC;QACF,MAAM,QAAQ,GAAG,oBAAoB,CAAC;YACpC,IAAI;YACJ,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YACjC,IAAI,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI;YACJ,GAAG,EAAE,IAAI,IAAI,EAAE;YACf,QAAQ,EAAE,qBAAqB;YAC/B,KAAK,EAAE,WAAW;SACnB,CAAC,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,KAAK,CAAC,4BAA4B,IAAI,gBAAgB,CAAC,CAAC;gBACpE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,qEAAqE;gBACrE,0CAA0C;YAC5C,CAAC;QACH,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,GAAG,CAAC,sBAAsB,OAAO,CAAC,MAAM,6CAA6C,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,wDAAyD,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1F,CAAC;YAAS,CAAC;QACT,MAAM,KAAK,EAAE,GAAG,EAAE,CAAC;IACrB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,YAAY,GAAG,qBAAqB,CAAC;AAC3C,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AACnC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,CAAC;AASnC,KAAK,UAAU,YAAY,CAAC,GAAW;IACrC,8DAA8D;IAC9D,+EAA+E;IAC/E,+EAA+E;IAC/E,qEAAqE;IACrE,0EAA0E;IAC1E,4EAA4E;IAC5E,2CAA2C;IAC3C,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IACnD,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,oBAAoB,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,8FAA8F;AAC9F,KAAK,UAAU,kBAAkB,CAAC,MAAa;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACnF,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,uEAAuE;QACvE,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAgB,EAChB,KAAa,EACb,MAAW,UAAU;IAErB,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,oBAAoB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;IACvE,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,KAAyB,CAAC;IAC9B,sEAAsE;IACtE,mCAAmC;IACnC,KAAK,IAAI,SAAS,GAAG,CAAC,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,IAAI,CAAC,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,GAAG,CAC9B,GAAG,YAAY,GAAG,SAAS,EAAE,EAC7B,KAAK,EACL,IAAI,EACJ,YAAY,EACZ,IAAI,CACL,CAAC;QACF,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,KAAK,GAAG,SAAS,CAAC;YAClB,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,QAAQ,CAAC,UAAU,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,oCAAoC,KAAK,GAAG,CAAC,2CAA2C;YACtF,4CAA4C,YAAY,GAAG,KAAK,yBAAyB;YACzF,kFAAkF;YAClF,+DAA+D;YAC/D,gCAAgC,CACnC,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAClD,yEAAyE;IACzE,6EAA6E;IAC7E,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;IAEtB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,KAAK,QAAQ,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;IAChF,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACrB,iEAAiE;IACjE,OAAO,CAAC,KAAK,EAAE,CAAC;IAEhB,GAAG,CAAC,oDAAoD,KAAK,EAAE,CAAC,CAAC;IAEjE,OAAO;QACL,GAAG;QACH,KAAK;QACL,KAAK;QACL,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,aAAa,CAAC,OAAO,CAAC,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;gBACtB,qEAAqE;gBACrE,mCAAmC;gBACnC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,KAAK,EAAE,CAAC,CAAC;gBAC7D,IAAI,MAAM,KAAK,KAAK;oBAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,KAAK,EAAE,CAAC,CAAC;YACtE,CAAC;oBAAS,CAAC;gBACT,KAAK,CAAC,UAAU,EAAE,CAAC;gBACnB,QAAQ,CAAC,UAAU,EAAE,CAAC;YACxB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|