@zeroxsolutions/db 9.3.5 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +84 -60
- package/dist/base/d1/outbox-table.d.ts.map +1 -1
- package/dist/base/d1/outbox-table.js +9 -1
- package/dist/base/postgres/outbox-table.d.ts.map +1 -1
- package/dist/base/postgres/outbox-table.js +9 -1
- package/dist/containers/index.d.ts +2 -0
- package/dist/containers/index.d.ts.map +1 -0
- package/dist/containers/index.js +1 -0
- package/dist/containers/remove-container.d.ts +13 -0
- package/dist/containers/remove-container.d.ts.map +1 -0
- package/dist/containers/remove-container.js +24 -0
- package/dist/testing/disposable-postgres.d.ts.map +1 -1
- package/dist/testing/disposable-postgres.js +2 -16
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## 9.5.0 (2026-10-16)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **db:** index the outbox's unpublished rows alone, by type and by time ([6f9dc0b](https://github.com/zeroxsolutions/zeroxsolutions/commit/6f9dc0b))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- Claude Opus 5.5
|
|
10
|
+
- Lương Văn Tú
|
|
11
|
+
|
|
12
|
+
## 9.4.0 (2026-10-14)
|
|
13
|
+
|
|
14
|
+
### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- **db:** export the forced container remove from its own subpath ([fade0ea](https://github.com/zeroxsolutions/zeroxsolutions/commit/fade0ea))
|
|
17
|
+
|
|
18
|
+
### ❤️ Thank You
|
|
19
|
+
|
|
20
|
+
- Claude Opus 5.5
|
|
21
|
+
- Lương Văn Tú
|
|
22
|
+
|
|
1
23
|
## 9.3.5 (2026-10-12)
|
|
2
24
|
|
|
3
25
|
### 🩹 Fixes
|
package/README.md
CHANGED
|
@@ -33,12 +33,13 @@ Import from the subpath that matches your driver. **There is no root entry**: `p
|
|
|
33
33
|
declares `"."`, but no `src/index.ts` exists for the build to emit it from, so
|
|
34
34
|
`from '@zeroxsolutions/db'` does not resolve.
|
|
35
35
|
|
|
36
|
-
| Export path
|
|
37
|
-
|
|
|
38
|
-
| `@zeroxsolutions/db/postgres`
|
|
39
|
-
| `@zeroxsolutions/db/d1`
|
|
40
|
-
| `@zeroxsolutions/db/utils`
|
|
41
|
-
| `@zeroxsolutions/db/testing`
|
|
36
|
+
| Export path | Description |
|
|
37
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| `@zeroxsolutions/db/postgres` | PostgreSQL entity, repository, view repository, unit of work, outbox, and types |
|
|
39
|
+
| `@zeroxsolutions/db/d1` | Cloudflare D1 (SQLite) entity, repository, unit of work, outbox, and types |
|
|
40
|
+
| `@zeroxsolutions/db/utils` | Filter and order builders, `TableQuery` and the relational-read builder, the field-whitelist guard, the filter-value schema + operator enum, omit masks, pagination constants |
|
|
41
|
+
| `@zeroxsolutions/db/testing` | A throwaway Postgres container carrying your schema, on the engine you name, plus the container timeout and a fake uuid v7 |
|
|
42
|
+
| `@zeroxsolutions/db/containers` | The forced remove that tears down a container you started yourself |
|
|
42
43
|
|
|
43
44
|
---
|
|
44
45
|
|
|
@@ -98,23 +99,23 @@ export class UserRepository extends BaseRepository<typeof users, typeof schema,
|
|
|
98
99
|
|
|
99
100
|
#### Available Methods
|
|
100
101
|
|
|
101
|
-
| Method
|
|
102
|
-
|
|
|
103
|
-
| `findAll(where, options?)`
|
|
104
|
-
| `findAllAndCount(where, options?)`
|
|
105
|
-
| `findOne(where, options?)`
|
|
106
|
-
| `findOneById(id, options?)`
|
|
107
|
-
| `create(data, options?)`
|
|
108
|
-
| `createMany(data[], options?)`
|
|
109
|
-
| `update(where, data, options?)`
|
|
110
|
-
| `updateById(id, data, options?)`
|
|
111
|
-
| `delete(where, options?)`
|
|
112
|
-
| `deleteById(id, options?)`
|
|
113
|
-
| `count(where, options?)`
|
|
114
|
-
| `exists(where, options?)`
|
|
115
|
-
| `softDelete(where, options?)`
|
|
116
|
-
| `softDeleteById(id, options?)`
|
|
117
|
-
| `nullifyBy(field, value, options?)` | Set field to `null` where field equals value
|
|
102
|
+
| Method | Description |
|
|
103
|
+
| ----------------------------------- | -------------------------------------------------- |
|
|
104
|
+
| `findAll(where, options?)` | Paginated list query |
|
|
105
|
+
| `findAllAndCount(where, options?)` | Paginated list + total count in parallel |
|
|
106
|
+
| `findOne(where, options?)` | First matching record or `null` |
|
|
107
|
+
| `findOneById(id, options?)` | Find by primary key or `null` |
|
|
108
|
+
| `create(data, options?)` | Insert one record, returns inserted row |
|
|
109
|
+
| `createMany(data[], options?)` | Bulk insert, returns inserted rows |
|
|
110
|
+
| `update(where, data, options?)` | Update matching records, returns first updated row |
|
|
111
|
+
| `updateById(id, data, options?)` | Update by primary key |
|
|
112
|
+
| `delete(where, options?)` | Hard delete matching records |
|
|
113
|
+
| `deleteById(id, options?)` | Hard delete by primary key |
|
|
114
|
+
| `count(where, options?)` | Count matching records |
|
|
115
|
+
| `exists(where, options?)` | Returns `true` if any record matches |
|
|
116
|
+
| `softDelete(where, options?)` | Sets `deletedAt` to now |
|
|
117
|
+
| `softDeleteById(id, options?)` | Soft delete by primary key |
|
|
118
|
+
| `nullifyBy(field, value, options?)` | Set field to `null` where field equals value |
|
|
118
119
|
|
|
119
120
|
#### FindAll Options
|
|
120
121
|
|
|
@@ -140,13 +141,13 @@ skipping another. With no `sort`, a page is ordered by the key alone.
|
|
|
140
141
|
`fields` and `include` are `TableQuery<typeof schema, '<export name>'>`, the shape `@zeroxsolutions/query` guards, so
|
|
141
142
|
a guarded query spreads straight into the options:
|
|
142
143
|
|
|
143
|
-
| The query says
|
|
144
|
-
|
|
|
145
|
-
| no fieldset for a table
|
|
146
|
-
| a fieldset
|
|
144
|
+
| The query says | The finder loads |
|
|
145
|
+
| ----------------------------- | ------------------------------------------------------------------- |
|
|
146
|
+
| no fieldset for a table | every column of that table |
|
|
147
|
+
| a fieldset | those columns and the table's primary key |
|
|
147
148
|
| a relation name in a fieldset | that relation's primary keys - the linkage a relationship publishes |
|
|
148
|
-
| an empty fieldset
|
|
149
|
-
| an include path
|
|
149
|
+
| an empty fieldset | the primary key alone |
|
|
150
|
+
| an include path | each relation on it, under its own table's fieldset |
|
|
150
151
|
|
|
151
152
|
`with` and `include` reach the same relational query; where both name one relation, `with` wins. A read loading no
|
|
152
153
|
relation takes a plain `select` of the columns it narrowed to, so a repository given no `queryKey` still narrows its
|
|
@@ -209,7 +210,7 @@ import { DrizzleUnitOfWork, DrizzleOutbox } from '@zeroxsolutions/db/postgres';
|
|
|
209
210
|
|
|
210
211
|
// built in the transport per invocation (the db client comes from the connection string)
|
|
211
212
|
const uow = new DrizzleUnitOfWork(db, (tx) => {
|
|
212
|
-
const scope = container.createChildContainer();
|
|
213
|
+
const scope = container.createChildContainer(); // bind the transaction to a scoped container
|
|
213
214
|
scope.register(TOKENS.TX, { useValue: tx });
|
|
214
215
|
scope.register(TOKENS.OUTBOX, { useValue: new DrizzleOutbox(tx) });
|
|
215
216
|
return scope;
|
|
@@ -223,9 +224,14 @@ await uow.run(async (tx) => {
|
|
|
223
224
|
```
|
|
224
225
|
|
|
225
226
|
`outboxTable` columns: `id` (uuid v7), `type` (event discriminator), `payload` (`jsonb`), `occurredAt`,
|
|
226
|
-
and `publishedAt` (NULL until the relay sends it
|
|
227
|
+
and `publishedAt` (NULL until the relay sends it). The relay reads unsent rows with
|
|
227
228
|
`outbox.pullUnpublished(limit)` and acknowledges them with `outbox.markPublished(ids, publishedAt)`.
|
|
228
229
|
|
|
230
|
+
Two indexes hold the unsent rows alone, so the published ones never slow a relay down:
|
|
231
|
+
`idx_outbox_unpublished_occurred_at` on `occurredAt`, for a relay that sends every type, and
|
|
232
|
+
`idx_outbox_unpublished_type_occurred_at` on `(type, occurredAt)`, for one that sends only some. Both
|
|
233
|
+
answer in `occurredAt` order without a sort.
|
|
234
|
+
|
|
229
235
|
---
|
|
230
236
|
|
|
231
237
|
## Cloudflare D1 (SQLite)
|
|
@@ -288,7 +294,7 @@ import { DrizzleD1UnitOfWork, DrizzleOutbox } from '@zeroxsolutions/db/d1';
|
|
|
288
294
|
|
|
289
295
|
// built in the transport per invocation (the db client comes from the Hyperdrive/D1 binding)
|
|
290
296
|
const uow = new DrizzleD1UnitOfWork(db, ({ db, collector }) => {
|
|
291
|
-
const scope = container.createChildContainer();
|
|
297
|
+
const scope = container.createChildContainer(); // reads use db; writes push to collector
|
|
292
298
|
scope.register(TOKENS.TX, { useValue: { db, collector } });
|
|
293
299
|
scope.register(TOKENS.OUTBOX, { useValue: new DrizzleOutbox(db, collector) });
|
|
294
300
|
return scope;
|
|
@@ -298,7 +304,7 @@ const uow = new DrizzleD1UnitOfWork(db, ({ db, collector }) => {
|
|
|
298
304
|
await uow.run(async (tx) => {
|
|
299
305
|
await tx.resolve<ILinkAggregateRepository>(TOKENS.LINK_AGGREGATE_REPOSITORY).save(link);
|
|
300
306
|
await tx.resolve<IOutboxWriter>(TOKENS.OUTBOX).stage(link.pullEvents());
|
|
301
|
-
});
|
|
307
|
+
}); // -> flush [save, ...stage] as ONE batch()
|
|
302
308
|
```
|
|
303
309
|
|
|
304
310
|
A consuming context's D1 aggregate repository takes both the `db` (for immediate `load` reads) and
|
|
@@ -314,7 +320,7 @@ The D1 `DrizzleOutbox` mirrors the Postgres surface (`stage` / `pullUnpublished(
|
|
|
314
320
|
(so it commits with the aggregate write), or executes immediately when constructed standalone (relay
|
|
315
321
|
use). `outboxTable` columns: `id` (uuid v7), `type` (event discriminator), `payload` (text - SQLite
|
|
316
322
|
has no `jsonb`, so the relay `JSON.parse`s), `occurredAt`, and `publishedAt` (NULL until the relay
|
|
317
|
-
sends it
|
|
323
|
+
sends it), with the same two indexes over the unsent rows as the Postgres table.
|
|
318
324
|
|
|
319
325
|
> **Limitation.** D1's `batch()` is atomic but non-interactive, so the D1 UoW gives all-or-nothing
|
|
320
326
|
> write atomicity without a transaction-scoped read snapshot. A handler that loads, decides, then
|
|
@@ -350,15 +356,15 @@ const condition = buildFilterConditions({
|
|
|
350
356
|
|
|
351
357
|
All 15, as `filterOperatorSchema` declares them:
|
|
352
358
|
|
|
353
|
-
| Operator
|
|
354
|
-
|
|
|
355
|
-
| `equals` / `notEquals`
|
|
356
|
-
| `in` / `notIn`
|
|
359
|
+
| Operator | Description |
|
|
360
|
+
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
361
|
+
| `equals` / `notEquals` | Exact match / not equal |
|
|
362
|
+
| `in` / `notIn` | Array membership; an empty array contributes no condition |
|
|
357
363
|
| `contains` / `startsWith` / `endsWith` | Case-insensitive text match: `ILIKE` on Postgres, `LIKE` on SQLite, which is already case-insensitive for ASCII |
|
|
358
|
-
| `gt` / `gte` / `lt` / `lte`
|
|
359
|
-
| `between`
|
|
360
|
-
| `notBetween`
|
|
361
|
-
| `isNull` / `isNotNull`
|
|
364
|
+
| `gt` / `gte` / `lt` / `lte` | Numeric/date comparisons |
|
|
365
|
+
| `between` | Range (`from` / `to`, both optional) |
|
|
366
|
+
| `notBetween` | Range exclusion (`from` and `to`, both required) |
|
|
367
|
+
| `isNull` / `isNotNull` | Null checks; carry no `value` key at all |
|
|
362
368
|
|
|
363
369
|
A `%` or `_` in a text value is escaped, so it matches literally rather than as a wildcard.
|
|
364
370
|
|
|
@@ -373,10 +379,10 @@ column under that same name in `resolvers`:
|
|
|
373
379
|
import { permitsFor } from '@zeroxsolutions/query';
|
|
374
380
|
import { buildFilterConditions, columnResolver } from '@zeroxsolutions/db/utils';
|
|
375
381
|
|
|
376
|
-
const placesQueryPermits = permitsFor(placeAttributes, { relationships: places.relationships }).relationship(
|
|
377
|
-
'
|
|
378
|
-
|
|
379
|
-
);
|
|
382
|
+
const placesQueryPermits = permitsFor(placeAttributes, { relationships: places.relationships }).relationship('region', [
|
|
383
|
+
'equals',
|
|
384
|
+
'in',
|
|
385
|
+
]);
|
|
380
386
|
|
|
381
387
|
const condition = buildFilterConditions({
|
|
382
388
|
table: places,
|
|
@@ -416,11 +422,7 @@ only the offending field name; the calling service maps each error **type** to i
|
|
|
416
422
|
own transport.
|
|
417
423
|
|
|
418
424
|
```ts
|
|
419
|
-
import {
|
|
420
|
-
assertQueryableFields,
|
|
421
|
-
InvalidQueryFilter,
|
|
422
|
-
type QueryableFields,
|
|
423
|
-
} from '@zeroxsolutions/db/utils';
|
|
425
|
+
import { assertQueryableFields, InvalidQueryFilter, type QueryableFields } from '@zeroxsolutions/db/utils';
|
|
424
426
|
|
|
425
427
|
const USERS_QUERYABLE = {
|
|
426
428
|
filterable: ['name', 'createdAt'],
|
|
@@ -442,7 +444,10 @@ import { collectionQuerySchema } from '@zeroxsolutions/query';
|
|
|
442
444
|
|
|
443
445
|
createRoute({ request: { query: collectionQuerySchema(usersQueryPermits) } });
|
|
444
446
|
const query = c.req.valid('query');
|
|
445
|
-
const [rows, total] = await usersRepository.findAllAndCount(
|
|
447
|
+
const [rows, total] = await usersRepository.findAllAndCount(
|
|
448
|
+
buildFilterConditions({ table: users, filter: query.filter }),
|
|
449
|
+
query,
|
|
450
|
+
);
|
|
446
451
|
```
|
|
447
452
|
|
|
448
453
|
Pass `filterItem: filterValueSchema` in the spec so the parsed clauses are the ones this package's
|
|
@@ -453,11 +458,11 @@ builders accept. A test in `utils/filter.spec.ts` pins the absence of a second q
|
|
|
453
458
|
Three column masks, each a plain `{ column: true }` object to hand to a schema builder's `omit` or to
|
|
454
459
|
`Omit<...>`. This package exports the masks only - it ships no `omit` helper of its own.
|
|
455
460
|
|
|
456
|
-
| Mask
|
|
457
|
-
|
|
|
458
|
-
| `defaultSelectOmit`
|
|
461
|
+
| Mask | Drops |
|
|
462
|
+
| --------------------- | --------------------------------------- |
|
|
463
|
+
| `defaultSelectOmit` | `deletedAt` |
|
|
459
464
|
| `defaultResponseOmit` | the above plus `createdAt`, `updatedAt` |
|
|
460
|
-
| `defaultInsertOmit`
|
|
465
|
+
| `defaultInsertOmit` | the above plus `id` |
|
|
461
466
|
|
|
462
467
|
```ts
|
|
463
468
|
import { defaultInsertOmit } from '@zeroxsolutions/db/utils';
|
|
@@ -519,11 +524,30 @@ This package names no engine for you, and exports no constant standing in as tha
|
|
|
519
524
|
major you run is your deployment's own fact, so `image` takes no default: a forgotten argument is a
|
|
520
525
|
typecheck failure rather than a suite that quietly passed on a major you do not deploy.
|
|
521
526
|
|
|
522
|
-
| Export
|
|
523
|
-
|
|
|
527
|
+
| Export | What it is |
|
|
528
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
524
529
|
| `startDisposablePostgres(schema, image)` | Starts the container, creates `schema` in it, and returns a Drizzle client typed against it, the `connectionString` it dialled, and the `stop()` that tears it down. A server `WARNING` prints as one line on `console.warn`; lower severities are dropped |
|
|
525
|
-
| `CONTAINER_TIMEOUT_MS`
|
|
526
|
-
| `fakeUuidV7`
|
|
530
|
+
| `CONTAINER_TIMEOUT_MS` | 180s, for the hook that starts a container: an image pull on a cold machine outruns vitest's 5s default |
|
|
531
|
+
| `fakeUuidV7` | Random bits in a v7 layout: it passes a v7 check, and is not time-ordered like a minted id |
|
|
532
|
+
|
|
533
|
+
### A container you started yourself
|
|
534
|
+
|
|
535
|
+
A global setup that starts its own container, to migrate a template or several databases, tears it down
|
|
536
|
+
with `removeContainer` from `@zeroxsolutions/db/containers`, not with the container's `stop()`:
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
import { removeContainer } from '@zeroxsolutions/db/containers';
|
|
540
|
+
|
|
541
|
+
const container = await new PostgreSqlContainer(image).start();
|
|
542
|
+
// ...
|
|
543
|
+
return () => removeContainer(container);
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
`stop()` sends a stop and then a separate remove, and on a loaded host the daemon answers that remove
|
|
547
|
+
with 409 "container is running", failing a suite whose every case passed. `removeContainer` is one forced
|
|
548
|
+
remove, volumes included, and it is what `startDisposablePostgres`'s `stop()` calls. It rejects for a
|
|
549
|
+
container the daemon no longer knows, so a teardown that runs twice shows. This subpath imports nothing
|
|
550
|
+
but `@testcontainers/postgresql`, so an e2e project that installs no `drizzle-kit` can use it.
|
|
527
551
|
|
|
528
552
|
---
|
|
529
553
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbox-table.d.ts","sourceRoot":"","sources":["../../../src/base/d1/outbox-table.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outbox-table.d.ts","sourceRoot":"","sources":["../../../src/base/d1/outbox-table.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoBvB,CAAC;AAEF,sGAAsG;AACtG,MAAM,MAAM,SAAS,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNull } from 'drizzle-orm';
|
|
1
2
|
import { index, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
2
3
|
import { v7 as uuidv7 } from 'uuid';
|
|
3
4
|
/**
|
|
@@ -16,4 +17,11 @@ export const outboxTable = sqliteTable('outbox', {
|
|
|
16
17
|
.notNull()
|
|
17
18
|
.$defaultFn(() => new Date().toISOString()),
|
|
18
19
|
publishedAt: text('published_at'),
|
|
19
|
-
},
|
|
20
|
+
},
|
|
21
|
+
// Both hold the unpublished rows alone, so a backlog of published rows never slows a relay. The
|
|
22
|
+
// first answers a relay that publishes every type, the second one that publishes only some, each in
|
|
23
|
+
// `occurredAt` order without a sort.
|
|
24
|
+
(table) => [
|
|
25
|
+
index('idx_outbox_unpublished_occurred_at').on(table.occurredAt).where(isNull(table.publishedAt)),
|
|
26
|
+
index('idx_outbox_unpublished_type_occurred_at').on(table.type, table.occurredAt).where(isNull(table.publishedAt)),
|
|
27
|
+
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbox-table.d.ts","sourceRoot":"","sources":["../../../src/base/postgres/outbox-table.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outbox-table.d.ts","sourceRoot":"","sources":["../../../src/base/postgres/outbox-table.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkBvB,CAAC;AAEF,sDAAsD;AACtD,MAAM,MAAM,SAAS,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNull } from 'drizzle-orm';
|
|
1
2
|
import { index, jsonb, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
2
3
|
import { v7 as uuidv7 } from 'uuid';
|
|
3
4
|
/**
|
|
@@ -12,4 +13,11 @@ export const outboxTable = pgTable('outbox', {
|
|
|
12
13
|
payload: jsonb('payload').notNull(),
|
|
13
14
|
occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull().defaultNow(),
|
|
14
15
|
publishedAt: timestamp('published_at', { withTimezone: true }),
|
|
15
|
-
},
|
|
16
|
+
},
|
|
17
|
+
// Both hold the unpublished rows alone, so a backlog of published rows never slows a relay. The
|
|
18
|
+
// first answers a relay that publishes every type, the second one that publishes only some, each in
|
|
19
|
+
// `occurredAt` order without a sort.
|
|
20
|
+
(table) => [
|
|
21
|
+
index('idx_outbox_unpublished_occurred_at').on(table.occurredAt).where(isNull(table.publishedAt)),
|
|
22
|
+
index('idx_outbox_unpublished_type_occurred_at').on(table.type, table.occurredAt).where(isNull(table.publishedAt)),
|
|
23
|
+
]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/containers/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './remove-container.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes `container` and its volumes in one forced call, which the daemon carries out as a kill and a
|
|
3
|
+
* removal together. The container's own stop sends a stop and then a remove, and on a loaded host that
|
|
4
|
+
* remove answered 409 "container is running", failing a suite whose every case had passed
|
|
5
|
+
* (testcontainers@12.1.0, Docker Desktop 29).
|
|
6
|
+
* @param container - a container started through `@testcontainers/postgresql`.
|
|
7
|
+
* @throws whatever the daemon answers, including that it knows no such container, so a second teardown
|
|
8
|
+
* of the same container shows.
|
|
9
|
+
*/
|
|
10
|
+
export declare function removeContainer(container: {
|
|
11
|
+
getId(): string;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=remove-container.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-container.d.ts","sourceRoot":"","sources":["../../src/containers/remove-container.ts"],"names":[],"mappings":"AAOA;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE;IAAE,KAAK,IAAI,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
/**
|
|
3
|
+
* Removes `container` and its volumes in one forced call, which the daemon carries out as a kill and a
|
|
4
|
+
* removal together. The container's own stop sends a stop and then a remove, and on a loaded host that
|
|
5
|
+
* remove answered 409 "container is running", failing a suite whose every case had passed
|
|
6
|
+
* (testcontainers@12.1.0, Docker Desktop 29).
|
|
7
|
+
* @param container - a container started through `@testcontainers/postgresql`.
|
|
8
|
+
* @throws whatever the daemon answers, including that it knows no such container, so a second teardown
|
|
9
|
+
* of the same container shows.
|
|
10
|
+
*/
|
|
11
|
+
export async function removeContainer(container) {
|
|
12
|
+
const runtime = await containerRuntime();
|
|
13
|
+
await runtime.container.getById(container.getId()).remove({ force: true, v: true });
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The runtime client of the testcontainers that `@testcontainers/postgresql` itself runs on. It is
|
|
17
|
+
* resolved from that package rather than declared here, so a consumer installs nothing beyond the peer it
|
|
18
|
+
* already has, and the client is the one the container was started through.
|
|
19
|
+
*/
|
|
20
|
+
function containerRuntime() {
|
|
21
|
+
const fromPostgres = createRequire(createRequire(import.meta.url).resolve('@testcontainers/postgresql'));
|
|
22
|
+
const { getContainerRuntimeClient } = fromPostgres('testcontainers');
|
|
23
|
+
return getContainerRuntimeClient();
|
|
24
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disposable-postgres.d.ts","sourceRoot":"","sources":["../../src/testing/disposable-postgres.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"disposable-postgres.d.ts","sourceRoot":"","sources":["../../src/testing/disposable-postgres.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAI3E,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,SAAU,CAAC;AAE5C,8FAA8F;AAC9F,MAAM,WAAW,kBAAkB,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzE,EAAE,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAChC;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnF,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAiBtC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
1
|
import { PostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
2
|
import { generateDrizzleJson, generateMigration } from 'drizzle-kit/api';
|
|
4
3
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
5
4
|
import postgres from 'postgres';
|
|
5
|
+
import { removeContainer } from '../containers/index.js';
|
|
6
6
|
/** Pulling the image on a cold machine costs far more than vitest's 5s default timeout. */
|
|
7
7
|
export const CONTAINER_TIMEOUT_MS = 180_000;
|
|
8
8
|
/**
|
|
@@ -27,24 +27,10 @@ export async function startDisposablePostgres(schema, image) {
|
|
|
27
27
|
connectionString,
|
|
28
28
|
async stop() {
|
|
29
29
|
await client.end();
|
|
30
|
-
|
|
31
|
-
// own stop sends a stop and then a remove, and on a loaded host that remove answered 409 "container is
|
|
32
|
-
// running", failing a suite whose every case had passed (testcontainers@12.1.0, Docker Desktop 29).
|
|
33
|
-
const runtime = await containerRuntime();
|
|
34
|
-
await runtime.container.getById(container.getId()).remove({ force: true, v: true });
|
|
30
|
+
await removeContainer(container);
|
|
35
31
|
},
|
|
36
32
|
};
|
|
37
33
|
}
|
|
38
|
-
/**
|
|
39
|
-
* The runtime client of the testcontainers that `@testcontainers/postgresql` itself runs on. It is
|
|
40
|
-
* resolved from that package rather than declared here, so a consumer installs nothing beyond the peer it
|
|
41
|
-
* already has, and the client is the one the container was started through.
|
|
42
|
-
*/
|
|
43
|
-
function containerRuntime() {
|
|
44
|
-
const fromPostgres = createRequire(createRequire(import.meta.url).resolve('@testcontainers/postgresql'));
|
|
45
|
-
const { getContainerRuntimeClient } = fromPostgres('testcontainers');
|
|
46
|
-
return getContainerRuntimeClient();
|
|
47
|
-
}
|
|
48
34
|
/**
|
|
49
35
|
* Prints a server WARNING as one line and drops every lower severity. Left to its default, postgres.js
|
|
50
36
|
* prints each notice as a raw object, and a full-text search for a stop word alone raises two per
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroxsolutions/db",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.5.0",
|
|
4
4
|
"description": "Generic Drizzle toolkit - the id column and audit timestamps a table takes, a base repository, the unit-of-work and transactional outbox impls, and the filter/sort/pagination query helpers, for PostgreSQL (postgres-js) and Cloudflare D1; ships no domain schema. Shared across ZeroXSolutions services.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -32,13 +32,18 @@
|
|
|
32
32
|
"types": "./dist/testing/index.d.ts",
|
|
33
33
|
"import": "./dist/testing/index.js",
|
|
34
34
|
"default": "./dist/testing/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./containers": {
|
|
37
|
+
"types": "./dist/containers/index.d.ts",
|
|
38
|
+
"import": "./dist/containers/index.js",
|
|
39
|
+
"default": "./dist/containers/index.js"
|
|
35
40
|
}
|
|
36
41
|
},
|
|
37
42
|
"dependencies": {
|
|
38
43
|
"tslib": "^2.3.0",
|
|
39
44
|
"uuid": "^14.0.1",
|
|
40
|
-
"@zeroxsolutions/
|
|
41
|
-
"@zeroxsolutions/
|
|
45
|
+
"@zeroxsolutions/query": "0.7.3",
|
|
46
|
+
"@zeroxsolutions/cosmic": "0.3.4"
|
|
42
47
|
},
|
|
43
48
|
"peerDependencies": {
|
|
44
49
|
"@testcontainers/postgresql": "^12.1.0",
|