@byline/db-postgres 2.1.2 → 2.2.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/dist/database/schema/auth.d.ts +98 -98
- package/dist/database/schema/auth.js +6 -9
- package/dist/database/schema/common.d.ts +34 -0
- package/dist/database/schema/common.js +45 -0
- package/dist/database/schema/index.d.ts +407 -349
- package/dist/database/schema/index.js +28 -13
- package/dist/index.js +6 -1
- package/dist/modules/counters/counters-commands.d.ts +22 -0
- package/dist/modules/counters/counters-commands.js +96 -0
- package/dist/modules/storage/storage-commands.d.ts +6 -6
- package/dist/modules/storage/storage-flatten.js +1 -0
- package/dist/modules/storage/storage-queries.d.ts +10 -10
- package/dist/modules/storage/storage-queries.js +13 -2
- package/package.json +4 -4
- package/dist/lib/test-bootstrap.d.ts +0 -8
- package/dist/lib/test-bootstrap.js +0 -27
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { eq, relations, sql } from 'drizzle-orm';
|
|
9
9
|
import { bigint, boolean, customType, date, decimal, index, integer, jsonb, pgTable, pgView, real, text, time, timestamp, unique, uuid, varchar, } from 'drizzle-orm/pg-core';
|
|
10
|
+
import { createdAt, timestamps } from './common.js';
|
|
10
11
|
/**
|
|
11
12
|
* `varchar(...)` with explicit byte-wise (C) collation.
|
|
12
13
|
*
|
|
@@ -44,8 +45,7 @@ export const collections = pgTable('byline_collections', {
|
|
|
44
45
|
// `ensureCollections()` run post-migration, tightens to NOT NULL when
|
|
45
46
|
// the `collection_versions` history table lands.
|
|
46
47
|
schema_hash: varchar('schema_hash', { length: 64 }),
|
|
47
|
-
|
|
48
|
-
updated_at: timestamp('updated_at').defaultNow(),
|
|
48
|
+
...timestamps,
|
|
49
49
|
});
|
|
50
50
|
// Documents table
|
|
51
51
|
export const documents = pgTable('byline_documents', {
|
|
@@ -63,8 +63,7 @@ export const documents = pgTable('byline_documents', {
|
|
|
63
63
|
// comparison — the fractional-index algorithm requires this. See
|
|
64
64
|
// `varcharByteSorted` above and docs/COLLECTIONS.md (Orderable collections).
|
|
65
65
|
order_key: varcharByteSorted('order_key', { length: 128 }),
|
|
66
|
-
|
|
67
|
-
updated_at: timestamp('updated_at').defaultNow(),
|
|
66
|
+
...timestamps,
|
|
68
67
|
}, (table) => [
|
|
69
68
|
index('idx_documents_collection').on(table.collection_id),
|
|
70
69
|
index('idx_documents_collection_order').on(table.collection_id, table.order_key),
|
|
@@ -87,8 +86,7 @@ export const documentVersions = pgTable('byline_document_versions', {
|
|
|
87
86
|
event_type: varchar('event_type', { length: 20 }).notNull().default('create'), // 'create', 'update', 'delete'
|
|
88
87
|
status: varchar('status', { length: 50 }).default('draft'),
|
|
89
88
|
is_deleted: boolean('is_deleted').default(false), // Tombstone for soft deletes
|
|
90
|
-
|
|
91
|
-
updated_at: timestamp('updated_at').defaultNow(),
|
|
89
|
+
...timestamps,
|
|
92
90
|
created_by: uuid('created_by'),
|
|
93
91
|
change_summary: text('change_summary'),
|
|
94
92
|
}, (table) => [
|
|
@@ -121,8 +119,7 @@ export const documentPaths = pgTable('byline_document_paths', {
|
|
|
121
119
|
.notNull()
|
|
122
120
|
.references(() => collections.id, { onDelete: 'cascade' }),
|
|
123
121
|
path: varchar('path', { length: 255 }).notNull(),
|
|
124
|
-
|
|
125
|
-
updated_at: timestamp('updated_at').defaultNow(),
|
|
122
|
+
...timestamps,
|
|
126
123
|
}, (table) => [
|
|
127
124
|
// One path per (logical document, locale).
|
|
128
125
|
unique('unique_document_paths_document_locale').on(table.document_id, table.locale),
|
|
@@ -142,7 +139,7 @@ export const documentRelationships = pgTable('byline_document_relationships', {
|
|
|
142
139
|
child_document_id: uuid('child_document_id')
|
|
143
140
|
.notNull()
|
|
144
141
|
.references(() => documents.id, { onDelete: 'cascade' }),
|
|
145
|
-
|
|
142
|
+
...createdAt,
|
|
146
143
|
}, (table) => [
|
|
147
144
|
// Composite primary key to ensure a child is only parented once by the same parent.
|
|
148
145
|
unique().on(table.parent_document_id, table.child_document_id),
|
|
@@ -258,8 +255,7 @@ const baseStoreColumns = {
|
|
|
258
255
|
field_name: varchar('field_name', { length: 255 }).notNull(),
|
|
259
256
|
locale: varchar('locale', { length: 10 }).notNull().default('default'),
|
|
260
257
|
parent_path: varchar('parent_path', { length: 500 }),
|
|
261
|
-
|
|
262
|
-
updated_at: timestamp('updated_at').defaultNow(),
|
|
258
|
+
...timestamps,
|
|
263
259
|
};
|
|
264
260
|
// 1. TEXT FIELDS TABLE
|
|
265
261
|
export const textStore = pgTable('byline_store_text', {
|
|
@@ -364,8 +360,7 @@ export const metaStore = pgTable('byline_store_meta', {
|
|
|
364
360
|
// Optional opaque metadata payload for this node. Common attributes like
|
|
365
361
|
// label, icon, collapsed state, etc. can be stored here.
|
|
366
362
|
meta: jsonb('meta'),
|
|
367
|
-
|
|
368
|
-
updated_at: timestamp('updated_at').defaultNow(),
|
|
363
|
+
...timestamps,
|
|
369
364
|
}, (table) => [
|
|
370
365
|
// Fast lookup by document and node type/path when enriching reconstructed
|
|
371
366
|
// trees with meta information.
|
|
@@ -431,6 +426,26 @@ export const jsonStore = pgTable('byline_store_json', {
|
|
|
431
426
|
index('idx_json_keys').using('gin', table.object_keys),
|
|
432
427
|
unique('unique_json_field').on(table.document_version_id, table.field_path, table.locale),
|
|
433
428
|
]);
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
// Counter groups registry
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
//
|
|
433
|
+
// One row per counter `group` discovered in collection field definitions.
|
|
434
|
+
// The actual ID allocator is a Postgres SEQUENCE (named in `sequence_name`),
|
|
435
|
+
// reconciled at boot by `IDbAdapter.ensureCounterGroup`. The registry table
|
|
436
|
+
// itself only records that the group exists and which sequence backs it —
|
|
437
|
+
// it is not used in the hot allocation path (`nextval()` operates on the
|
|
438
|
+
// sequence object directly).
|
|
439
|
+
//
|
|
440
|
+
// Why a separate table rather than reading sequences from
|
|
441
|
+
// `information_schema`: the mapping from `group_name` → `sequence_name`
|
|
442
|
+
// belongs in the application's schema, not in PG metadata, so backups and
|
|
443
|
+
// adapter logic have a stable name to anchor against.
|
|
444
|
+
export const counterGroups = pgTable('byline_counter_groups', {
|
|
445
|
+
group_name: text('group_name').primaryKey(),
|
|
446
|
+
sequence_name: text('sequence_name').notNull(),
|
|
447
|
+
...createdAt,
|
|
448
|
+
});
|
|
434
449
|
// RELATIONS
|
|
435
450
|
// =========
|
|
436
451
|
export const collectionsRelations = relations(collections, ({ many }) => ({
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
9
9
|
import pg from 'pg';
|
|
10
10
|
import * as schema from './database/schema/index.js';
|
|
11
|
+
import { createCounterCommands } from './modules/counters/counters-commands.js';
|
|
11
12
|
import { createCommandBuilders } from './modules/storage/storage-commands.js';
|
|
12
13
|
import { createQueryBuilders } from './modules/storage/storage-queries.js';
|
|
13
14
|
export const pgAdapter = ({ connectionString, collections, defaultContentLocale, }) => {
|
|
@@ -20,8 +21,12 @@ export const pgAdapter = ({ connectionString, collections, defaultContentLocale,
|
|
|
20
21
|
const db = drizzle(pool, { schema });
|
|
21
22
|
const commandBuilders = createCommandBuilders(db, defaultContentLocale);
|
|
22
23
|
const queryBuilders = createQueryBuilders(db, collections, defaultContentLocale);
|
|
24
|
+
const counterCommands = createCounterCommands(db);
|
|
23
25
|
return {
|
|
24
|
-
commands:
|
|
26
|
+
commands: {
|
|
27
|
+
...commandBuilders,
|
|
28
|
+
counters: counterCommands,
|
|
29
|
+
},
|
|
25
30
|
queries: queryBuilders,
|
|
26
31
|
drizzle: db,
|
|
27
32
|
pool,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { ICounterCommands } from '@byline/core';
|
|
9
|
+
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
10
|
+
import type * as schema from '../../database/schema/index.js';
|
|
11
|
+
type DatabaseConnection = NodePgDatabase<typeof schema>;
|
|
12
|
+
export declare class CounterCommands implements ICounterCommands {
|
|
13
|
+
private db;
|
|
14
|
+
constructor(db: DatabaseConnection);
|
|
15
|
+
ensureCounterGroup(groupName: string): Promise<{
|
|
16
|
+
groupName: string;
|
|
17
|
+
sequenceName: string;
|
|
18
|
+
}>;
|
|
19
|
+
nextCounterValue(groupName: string): Promise<number>;
|
|
20
|
+
}
|
|
21
|
+
export declare function createCounterCommands(db: DatabaseConnection): ICounterCommands;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { eq, sql } from 'drizzle-orm';
|
|
10
|
+
import { counterGroups } from '../../database/schema/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Derive a stable, safe Postgres identifier for the sequence backing a
|
|
13
|
+
* counter group.
|
|
14
|
+
*
|
|
15
|
+
* Two competing goals: humans browsing the database should be able to
|
|
16
|
+
* recognise which group a sequence belongs to, and the name must never
|
|
17
|
+
* collide for two different group strings. We satisfy both by combining
|
|
18
|
+
* a sanitised slug of the group name with an 8-character SHA-256 prefix
|
|
19
|
+
* — so `'library-facets'` becomes something like
|
|
20
|
+
* `byline_cseq_library_facets_8c2f4d6a`.
|
|
21
|
+
*
|
|
22
|
+
* Postgres identifier limit is 63 bytes (NAMEDATALEN-1). The prefix is
|
|
23
|
+
* 12 bytes, the hash + underscore is 9, leaving 42 bytes for the slug.
|
|
24
|
+
*/
|
|
25
|
+
function deriveSequenceName(groupName) {
|
|
26
|
+
const slug = groupName
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
29
|
+
.replace(/^_+|_+$/g, '')
|
|
30
|
+
.slice(0, 42);
|
|
31
|
+
const hash = createHash('sha256').update(groupName).digest('hex').slice(0, 8);
|
|
32
|
+
return slug ? `byline_cseq_${slug}_${hash}` : `byline_cseq_${hash}`;
|
|
33
|
+
}
|
|
34
|
+
export class CounterCommands {
|
|
35
|
+
db;
|
|
36
|
+
constructor(db) {
|
|
37
|
+
this.db = db;
|
|
38
|
+
}
|
|
39
|
+
async ensureCounterGroup(groupName) {
|
|
40
|
+
if (!groupName || typeof groupName !== 'string') {
|
|
41
|
+
throw new Error(`ensureCounterGroup: groupName must be a non-empty string`);
|
|
42
|
+
}
|
|
43
|
+
const sequenceName = deriveSequenceName(groupName);
|
|
44
|
+
// Two statements, both idempotent. We deliberately do not wrap them
|
|
45
|
+
// in a single transaction: CREATE SEQUENCE acquires its own catalog
|
|
46
|
+
// locks, and DDL inside a long-running INSERT can deadlock under
|
|
47
|
+
// concurrent boots. Splitting them keeps each step short.
|
|
48
|
+
//
|
|
49
|
+
// The sequence is created first so that even if the INSERT loses a
|
|
50
|
+
// race, the next caller's nextval() on the existing row's
|
|
51
|
+
// sequence_name still works.
|
|
52
|
+
await this.db.execute(sql.raw(`CREATE SEQUENCE IF NOT EXISTS "${sequenceName}" AS BIGINT`));
|
|
53
|
+
await this.db
|
|
54
|
+
.insert(counterGroups)
|
|
55
|
+
.values({ group_name: groupName, sequence_name: sequenceName })
|
|
56
|
+
.onConflictDoNothing({ target: counterGroups.group_name });
|
|
57
|
+
return { groupName, sequenceName };
|
|
58
|
+
}
|
|
59
|
+
async nextCounterValue(groupName) {
|
|
60
|
+
if (!groupName || typeof groupName !== 'string') {
|
|
61
|
+
throw new Error(`nextCounterValue: groupName must be a non-empty string`);
|
|
62
|
+
}
|
|
63
|
+
// Resolve the sequence name from the registry rather than re-deriving
|
|
64
|
+
// it. This keeps callers honest — if the group was never registered
|
|
65
|
+
// via ensureCounterGroup, we surface that immediately rather than
|
|
66
|
+
// silently creating a new sequence the first time a value is asked
|
|
67
|
+
// for (which would mask configuration bugs during boot).
|
|
68
|
+
const rows = await this.db
|
|
69
|
+
.select({ sequence_name: counterGroups.sequence_name })
|
|
70
|
+
.from(counterGroups)
|
|
71
|
+
.where(eq(counterGroups.group_name, groupName))
|
|
72
|
+
.limit(1);
|
|
73
|
+
if (rows.length === 0) {
|
|
74
|
+
throw new Error(`nextCounterValue: counter group "${groupName}" is not registered. ` +
|
|
75
|
+
`Call ensureCounterGroup at boot before any document create that uses it.`);
|
|
76
|
+
}
|
|
77
|
+
const sequenceName = rows[0].sequence_name;
|
|
78
|
+
// sql.raw is safe here: sequenceName came from our own registry row,
|
|
79
|
+
// which was written by ensureCounterGroup from deriveSequenceName
|
|
80
|
+
// — only [a-z0-9_] characters, no user-controlled SQL.
|
|
81
|
+
const result = await this.db.execute(sql.raw(`SELECT nextval('"${sequenceName}"') AS value`));
|
|
82
|
+
// Drizzle/node-postgres returns rows on result.rows; the value comes
|
|
83
|
+
// back as a string because pg's BIGINT default is string. Parse it —
|
|
84
|
+
// counter values that overflow Number.MAX_SAFE_INTEGER are not a
|
|
85
|
+
// realistic concern for the facet-URL use case (we'd run out of
|
|
86
|
+
// useful URL space long before).
|
|
87
|
+
const row = result.rows[0];
|
|
88
|
+
if (row === undefined) {
|
|
89
|
+
throw new Error(`nextCounterValue: nextval returned no row for group "${groupName}"`);
|
|
90
|
+
}
|
|
91
|
+
return typeof row.value === 'number' ? row.value : Number(row.value);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function createCounterCommands(db) {
|
|
95
|
+
return new CounterCommands(db);
|
|
96
|
+
}
|
|
@@ -19,9 +19,9 @@ export declare class CollectionCommands implements ICollectionCommands {
|
|
|
19
19
|
version?: number;
|
|
20
20
|
schemaHash?: string;
|
|
21
21
|
}): Promise<{
|
|
22
|
+
created_at: Date;
|
|
23
|
+
updated_at: Date;
|
|
22
24
|
id: string;
|
|
23
|
-
created_at: Date | null;
|
|
24
|
-
updated_at: Date | null;
|
|
25
25
|
config: unknown;
|
|
26
26
|
path: string;
|
|
27
27
|
singular: string;
|
|
@@ -34,6 +34,8 @@ export declare class CollectionCommands implements ICollectionCommands {
|
|
|
34
34
|
version?: number;
|
|
35
35
|
schemaHash?: string;
|
|
36
36
|
}): Promise<{
|
|
37
|
+
created_at: Date;
|
|
38
|
+
updated_at: Date;
|
|
37
39
|
id: string;
|
|
38
40
|
path: string;
|
|
39
41
|
singular: string;
|
|
@@ -41,8 +43,6 @@ export declare class CollectionCommands implements ICollectionCommands {
|
|
|
41
43
|
config: unknown;
|
|
42
44
|
version: number;
|
|
43
45
|
schema_hash: string | null;
|
|
44
|
-
created_at: Date | null;
|
|
45
|
-
updated_at: Date | null;
|
|
46
46
|
}[]>;
|
|
47
47
|
delete(id: string): Promise<import("pg").QueryResult<never>>;
|
|
48
48
|
}
|
|
@@ -82,9 +82,9 @@ export declare class DocumentCommands implements IDocumentCommands {
|
|
|
82
82
|
orderKey?: string;
|
|
83
83
|
}): Promise<{
|
|
84
84
|
document: {
|
|
85
|
+
created_at: Date;
|
|
86
|
+
updated_at: Date;
|
|
85
87
|
id: string;
|
|
86
|
-
created_at: Date | null;
|
|
87
|
-
updated_at: Date | null;
|
|
88
88
|
collection_id: string;
|
|
89
89
|
document_id: string;
|
|
90
90
|
collection_version: number;
|
|
@@ -16,6 +16,8 @@ export declare class CollectionQueries implements ICollectionQueries {
|
|
|
16
16
|
private db;
|
|
17
17
|
constructor(db: DatabaseConnection);
|
|
18
18
|
getAllCollections(): Promise<{
|
|
19
|
+
created_at: Date;
|
|
20
|
+
updated_at: Date;
|
|
19
21
|
id: string;
|
|
20
22
|
path: string;
|
|
21
23
|
singular: string;
|
|
@@ -23,13 +25,11 @@ export declare class CollectionQueries implements ICollectionQueries {
|
|
|
23
25
|
config: unknown;
|
|
24
26
|
version: number;
|
|
25
27
|
schema_hash: string | null;
|
|
26
|
-
created_at: Date | null;
|
|
27
|
-
updated_at: Date | null;
|
|
28
28
|
}[]>;
|
|
29
29
|
getCollectionByPath(path: string): Promise<{
|
|
30
|
+
created_at: Date;
|
|
31
|
+
updated_at: Date;
|
|
30
32
|
id: string;
|
|
31
|
-
created_at: Date | null;
|
|
32
|
-
updated_at: Date | null;
|
|
33
33
|
config: unknown;
|
|
34
34
|
path: string;
|
|
35
35
|
singular: string;
|
|
@@ -38,9 +38,9 @@ export declare class CollectionQueries implements ICollectionQueries {
|
|
|
38
38
|
schema_hash: string | null;
|
|
39
39
|
} | undefined>;
|
|
40
40
|
getCollectionById(id: string): Promise<{
|
|
41
|
+
created_at: Date;
|
|
42
|
+
updated_at: Date;
|
|
41
43
|
id: string;
|
|
42
|
-
created_at: Date | null;
|
|
43
|
-
updated_at: Date | null;
|
|
44
44
|
config: unknown;
|
|
45
45
|
path: string;
|
|
46
46
|
singular: string;
|
|
@@ -192,8 +192,8 @@ export declare class DocumentQueries implements IDocumentQueries {
|
|
|
192
192
|
document_id: string;
|
|
193
193
|
path: string;
|
|
194
194
|
status: string | null;
|
|
195
|
-
created_at: Date
|
|
196
|
-
updated_at: Date
|
|
195
|
+
created_at: Date;
|
|
196
|
+
updated_at: Date;
|
|
197
197
|
fields: any;
|
|
198
198
|
} | null>;
|
|
199
199
|
getDocumentByPath({ collection_id, path, locale, reconstruct, readMode, filters, }: {
|
|
@@ -208,8 +208,8 @@ export declare class DocumentQueries implements IDocumentQueries {
|
|
|
208
208
|
document_id: string;
|
|
209
209
|
path: string;
|
|
210
210
|
status: string | null;
|
|
211
|
-
created_at: Date
|
|
212
|
-
updated_at: Date
|
|
211
|
+
created_at: Date;
|
|
212
|
+
updated_at: Date;
|
|
213
213
|
fields: any;
|
|
214
214
|
} | null>;
|
|
215
215
|
/**
|
|
@@ -1074,11 +1074,22 @@ export class DocumentQueries {
|
|
|
1074
1074
|
return sql `${column} ILIKE ${`%${String(value)}%`}`;
|
|
1075
1075
|
case '$in': {
|
|
1076
1076
|
const arr = value;
|
|
1077
|
-
|
|
1077
|
+
// Empty `$in` matches nothing — explicit FALSE avoids generating
|
|
1078
|
+
// an invalid empty `IN ()` clause.
|
|
1079
|
+
if (arr.length === 0)
|
|
1080
|
+
return sql `FALSE`;
|
|
1081
|
+
// Bind each element as its own parameter. Drizzle's `${arr}` would
|
|
1082
|
+
// serialise as a single row-constructor (`($1, $2)`), which Postgres
|
|
1083
|
+
// rejects when compared to a scalar column with `= ANY(...)`.
|
|
1084
|
+
const items = sql.join(arr.map((v) => sql `${v}`), sql `, `);
|
|
1085
|
+
return sql `${column} IN (${items})`;
|
|
1078
1086
|
}
|
|
1079
1087
|
case '$nin': {
|
|
1080
1088
|
const arr = value;
|
|
1081
|
-
|
|
1089
|
+
if (arr.length === 0)
|
|
1090
|
+
return sql `TRUE`;
|
|
1091
|
+
const items = sql.join(arr.map((v) => sql `${v}`), sql `, `);
|
|
1092
|
+
return sql `${column} NOT IN (${items})`;
|
|
1082
1093
|
}
|
|
1083
1094
|
default:
|
|
1084
1095
|
throw ERR_DATABASE({
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/db-postgres",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.2.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"pg": "^8.20.0",
|
|
58
58
|
"uuid": "^14.0.0",
|
|
59
59
|
"zod": "^4.4.3",
|
|
60
|
-
"@byline/admin": "2.
|
|
61
|
-
"@byline/auth": "2.
|
|
62
|
-
"@byline/core": "2.
|
|
60
|
+
"@byline/admin": "2.2.0",
|
|
61
|
+
"@byline/auth": "2.2.0",
|
|
62
|
+
"@byline/core": "2.2.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@biomejs/biome": "2.4.15",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) Infonomic Company Limited
|
|
7
|
-
*/
|
|
8
|
-
export {};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) Infonomic Company Limited
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Per-test-file bootstrap for node:test integration tests.
|
|
10
|
-
*
|
|
11
|
-
* The `tsx --env-file=.env.test --import ./src/lib/test-bootstrap.ts --test`
|
|
12
|
-
* invocation runs this module once at the start of each test-file process,
|
|
13
|
-
* before any test imports resolve. It:
|
|
14
|
-
*
|
|
15
|
-
* 1. Asserts `POSTGRES_CONNECTION_STRING` targets a `_test` database
|
|
16
|
-
* (belt; the script-level guard in `common.sh` is the braces).
|
|
17
|
-
* 2. Applies Drizzle migrations (idempotent — cheap on re-run).
|
|
18
|
-
* 3. Truncates all `public` tables so the file starts from a known state.
|
|
19
|
-
*
|
|
20
|
-
* Top-level await ensures the file's own `before()` hooks don't run until
|
|
21
|
-
* the database is ready.
|
|
22
|
-
*/
|
|
23
|
-
import { assertTestDatabase, migrateTestDatabase, resetTestDatabase } from './test-db.js';
|
|
24
|
-
const connectionString = process.env.POSTGRES_CONNECTION_STRING;
|
|
25
|
-
assertTestDatabase(connectionString);
|
|
26
|
-
await migrateTestDatabase(connectionString);
|
|
27
|
-
await resetTestDatabase(connectionString);
|