@krantho/persistence-postgres 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/dist/auth-adapter.d.ts +23 -0
- package/dist/auth-adapter.d.ts.map +1 -0
- package/dist/auth-adapter.js +159 -0
- package/dist/auth-adapter.js.map +1 -0
- package/dist/content-migration.d.ts +14 -0
- package/dist/content-migration.d.ts.map +1 -0
- package/dist/content-migration.js +64 -0
- package/dist/content-migration.js.map +1 -0
- package/dist/content-query.d.ts +23 -0
- package/dist/content-query.d.ts.map +1 -0
- package/dist/content-query.js +170 -0
- package/dist/content-query.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/migration-runner.d.ts +29 -0
- package/dist/migration-runner.d.ts.map +1 -0
- package/dist/migration-runner.js +123 -0
- package/dist/migration-runner.js.map +1 -0
- package/dist/migrations/index.d.ts +4 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +32 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/v001-initial-schema.d.ts +10 -0
- package/dist/migrations/v001-initial-schema.d.ts.map +1 -0
- package/dist/migrations/v001-initial-schema.js +94 -0
- package/dist/migrations/v001-initial-schema.js.map +1 -0
- package/dist/migrations/v002-auth-tables.d.ts +8 -0
- package/dist/migrations/v002-auth-tables.d.ts.map +1 -0
- package/dist/migrations/v002-auth-tables.js +37 -0
- package/dist/migrations/v002-auth-tables.js.map +1 -0
- package/dist/migrations/v003-user-permissions.d.ts +6 -0
- package/dist/migrations/v003-user-permissions.d.ts.map +1 -0
- package/dist/migrations/v003-user-permissions.js +18 -0
- package/dist/migrations/v003-user-permissions.js.map +1 -0
- package/dist/migrations/v004-media-hotspot.d.ts +14 -0
- package/dist/migrations/v004-media-hotspot.d.ts.map +1 -0
- package/dist/migrations/v004-media-hotspot.js +25 -0
- package/dist/migrations/v004-media-hotspot.js.map +1 -0
- package/dist/migrations/v005-content-locks.d.ts +6 -0
- package/dist/migrations/v005-content-locks.d.ts.map +1 -0
- package/dist/migrations/v005-content-locks.js +21 -0
- package/dist/migrations/v005-content-locks.js.map +1 -0
- package/dist/migrations/v006-webhook-deliveries.d.ts +6 -0
- package/dist/migrations/v006-webhook-deliveries.d.ts.map +1 -0
- package/dist/migrations/v006-webhook-deliveries.js +24 -0
- package/dist/migrations/v006-webhook-deliveries.js.map +1 -0
- package/dist/migrations/v007-media-folders.d.ts +6 -0
- package/dist/migrations/v007-media-folders.d.ts.map +1 -0
- package/dist/migrations/v007-media-folders.js +19 -0
- package/dist/migrations/v007-media-folders.js.map +1 -0
- package/dist/migrations/v008-site-settings.d.ts +6 -0
- package/dist/migrations/v008-site-settings.d.ts.map +1 -0
- package/dist/migrations/v008-site-settings.js +17 -0
- package/dist/migrations/v008-site-settings.js.map +1 -0
- package/dist/migrations/v009-seo-config.d.ts +12 -0
- package/dist/migrations/v009-seo-config.d.ts.map +1 -0
- package/dist/migrations/v009-seo-config.js +27 -0
- package/dist/migrations/v009-seo-config.js.map +1 -0
- package/dist/migrations/v010-content-live-version.d.ts +15 -0
- package/dist/migrations/v010-content-live-version.d.ts.map +1 -0
- package/dist/migrations/v010-content-live-version.js +40 -0
- package/dist/migrations/v010-content-live-version.js.map +1 -0
- package/dist/migrations/v011-media.d.ts +12 -0
- package/dist/migrations/v011-media.d.ts.map +1 -0
- package/dist/migrations/v011-media.js +43 -0
- package/dist/migrations/v011-media.js.map +1 -0
- package/dist/migrations/v012-page-cache.d.ts +12 -0
- package/dist/migrations/v012-page-cache.d.ts.map +1 -0
- package/dist/migrations/v012-page-cache.js +31 -0
- package/dist/migrations/v012-page-cache.js.map +1 -0
- package/dist/migrations/v013-content-contracts.d.ts +17 -0
- package/dist/migrations/v013-content-contracts.d.ts.map +1 -0
- package/dist/migrations/v013-content-contracts.js +41 -0
- package/dist/migrations/v013-content-contracts.js.map +1 -0
- package/dist/migrations/v014-content-manifest-commit.d.ts +7 -0
- package/dist/migrations/v014-content-manifest-commit.d.ts.map +1 -0
- package/dist/migrations/v014-content-manifest-commit.js +12 -0
- package/dist/migrations/v014-content-manifest-commit.js.map +1 -0
- package/dist/postgres-adapter.d.ts +71 -0
- package/dist/postgres-adapter.d.ts.map +1 -0
- package/dist/postgres-adapter.js +327 -0
- package/dist/postgres-adapter.js.map +1 -0
- package/dist/postgres-checkpoints.d.ts +20 -0
- package/dist/postgres-checkpoints.d.ts.map +1 -0
- package/dist/postgres-checkpoints.js +109 -0
- package/dist/postgres-checkpoints.js.map +1 -0
- package/dist/postgres-lock-adapter.d.ts +12 -0
- package/dist/postgres-lock-adapter.d.ts.map +1 -0
- package/dist/postgres-lock-adapter.js +62 -0
- package/dist/postgres-lock-adapter.js.map +1 -0
- package/dist/postgres-media-adapter.d.ts +28 -0
- package/dist/postgres-media-adapter.d.ts.map +1 -0
- package/dist/postgres-media-adapter.js +167 -0
- package/dist/postgres-media-adapter.js.map +1 -0
- package/dist/postgres-media-folder-adapter.d.ts +12 -0
- package/dist/postgres-media-folder-adapter.d.ts.map +1 -0
- package/dist/postgres-media-folder-adapter.js +60 -0
- package/dist/postgres-media-folder-adapter.js.map +1 -0
- package/dist/postgres-page-cache-store.d.ts +23 -0
- package/dist/postgres-page-cache-store.d.ts.map +1 -0
- package/dist/postgres-page-cache-store.js +80 -0
- package/dist/postgres-page-cache-store.js.map +1 -0
- package/dist/postgres-retention-adapter.d.ts +14 -0
- package/dist/postgres-retention-adapter.d.ts.map +1 -0
- package/dist/postgres-retention-adapter.js +61 -0
- package/dist/postgres-retention-adapter.js.map +1 -0
- package/dist/postgres-seo-config-adapter.d.ts +14 -0
- package/dist/postgres-seo-config-adapter.d.ts.map +1 -0
- package/dist/postgres-seo-config-adapter.js +59 -0
- package/dist/postgres-seo-config-adapter.js.map +1 -0
- package/dist/postgres-site-settings-adapter.d.ts +11 -0
- package/dist/postgres-site-settings-adapter.d.ts.map +1 -0
- package/dist/postgres-site-settings-adapter.js +30 -0
- package/dist/postgres-site-settings-adapter.js.map +1 -0
- package/dist/postgres-webhook-delivery-adapter.d.ts +27 -0
- package/dist/postgres-webhook-delivery-adapter.d.ts.map +1 -0
- package/dist/postgres-webhook-delivery-adapter.js +99 -0
- package/dist/postgres-webhook-delivery-adapter.js.map +1 -0
- package/dist/row-mapping.d.ts +40 -0
- package/dist/row-mapping.d.ts.map +1 -0
- package/dist/row-mapping.js +49 -0
- package/dist/row-mapping.js.map +1 -0
- package/package.json +38 -3
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Descoped
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CreateUserData, IAuthAdapter, RefreshTokenRecord, Role, UpdateUserData, UserPermission, UserRecord } from '@krantho/persistence';
|
|
2
|
+
import type { Sql } from 'postgres';
|
|
3
|
+
/** Postgres implementation of the auth adapter */
|
|
4
|
+
export declare class PostgresAuthAdapter implements IAuthAdapter {
|
|
5
|
+
private readonly sql;
|
|
6
|
+
constructor(sql: Sql);
|
|
7
|
+
createUser(data: CreateUserData): Promise<UserRecord>;
|
|
8
|
+
getUser(id: string): Promise<UserRecord | null>;
|
|
9
|
+
getUserByEmail(email: string): Promise<UserRecord | null>;
|
|
10
|
+
getUserByOidc(issuer: string, subject: string): Promise<UserRecord | null>;
|
|
11
|
+
listUsers(): Promise<UserRecord[]>;
|
|
12
|
+
updateUser(id: string, data: UpdateUserData): Promise<UserRecord | null>;
|
|
13
|
+
deleteUser(id: string): Promise<boolean>;
|
|
14
|
+
createRefreshToken(userId: string, tokenHash: string, expiresAt: string): Promise<string>;
|
|
15
|
+
getRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenRecord | null>;
|
|
16
|
+
deleteRefreshToken(id: string): Promise<void>;
|
|
17
|
+
deleteUserRefreshTokens(userId: string): Promise<void>;
|
|
18
|
+
deleteExpiredRefreshTokens(): Promise<number>;
|
|
19
|
+
getUserPermissions(userId: string): Promise<UserPermission[]>;
|
|
20
|
+
setUserPermission(userId: string, collection: string, role: Role): Promise<void>;
|
|
21
|
+
deleteUserPermission(userId: string, collection: string): Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=auth-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-adapter.d.ts","sourceRoot":"","sources":["../src/auth-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,IAAI,EACJ,cAAc,EACd,cAAc,EACd,UAAU,EACX,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAEnC,kDAAkD;AAClD,qBAAa,mBAAoB,YAAW,YAAY;IACtD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAK;gBAEb,GAAG,EAAE,GAAG;IAMd,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IAarD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAK/C,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAKzD,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAM1E,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAKlC,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAmDxE,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOxC,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUzF,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAiB5E,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD,0BAA0B,IAAI,OAAO,CAAC,MAAM,CAAC;IAO7C,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAW7D,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhF,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAKjF"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/** Postgres implementation of the auth adapter */
|
|
2
|
+
export class PostgresAuthAdapter {
|
|
3
|
+
sql;
|
|
4
|
+
constructor(sql) {
|
|
5
|
+
this.sql = sql;
|
|
6
|
+
}
|
|
7
|
+
// --- Users ---
|
|
8
|
+
async createUser(data) {
|
|
9
|
+
// postgres.js handles JS arrays natively in tagged templates — no manual
|
|
10
|
+
// string-join needed. The driver serialises string[] to a Postgres text[].
|
|
11
|
+
const rows = await this.sql `
|
|
12
|
+
INSERT INTO users (email, name, roles, oidc_subject, oidc_issuer)
|
|
13
|
+
VALUES (${data.email}, ${data.name}, ${data.roles}::text[], ${data.oidcSubject ?? null}, ${data.oidcIssuer ?? null})
|
|
14
|
+
RETURNING *`;
|
|
15
|
+
const row = rows[0];
|
|
16
|
+
if (!row)
|
|
17
|
+
throw new Error('INSERT RETURNING produced no row');
|
|
18
|
+
return rowToUser(row);
|
|
19
|
+
}
|
|
20
|
+
async getUser(id) {
|
|
21
|
+
const rows = await this.sql `SELECT * FROM users WHERE id = ${id}::uuid`;
|
|
22
|
+
return rows[0] ? rowToUser(rows[0]) : null;
|
|
23
|
+
}
|
|
24
|
+
async getUserByEmail(email) {
|
|
25
|
+
const rows = await this.sql `SELECT * FROM users WHERE email = ${email}`;
|
|
26
|
+
return rows[0] ? rowToUser(rows[0]) : null;
|
|
27
|
+
}
|
|
28
|
+
async getUserByOidc(issuer, subject) {
|
|
29
|
+
const rows = await this.sql `
|
|
30
|
+
SELECT * FROM users WHERE oidc_issuer = ${issuer} AND oidc_subject = ${subject}`;
|
|
31
|
+
return rows[0] ? rowToUser(rows[0]) : null;
|
|
32
|
+
}
|
|
33
|
+
async listUsers() {
|
|
34
|
+
const rows = await this.sql `SELECT * FROM users ORDER BY created_at DESC`;
|
|
35
|
+
return rows.map(rowToUser);
|
|
36
|
+
}
|
|
37
|
+
async updateUser(id, data) {
|
|
38
|
+
const sets = ['updated_at = NOW()'];
|
|
39
|
+
const params = [];
|
|
40
|
+
let idx = 1;
|
|
41
|
+
if (data.email !== undefined) {
|
|
42
|
+
sets.push(`email = $${idx}`);
|
|
43
|
+
params.push(data.email);
|
|
44
|
+
idx++;
|
|
45
|
+
}
|
|
46
|
+
if (data.name !== undefined) {
|
|
47
|
+
sets.push(`name = $${idx}`);
|
|
48
|
+
params.push(data.name);
|
|
49
|
+
idx++;
|
|
50
|
+
}
|
|
51
|
+
if (data.roles !== undefined) {
|
|
52
|
+
sets.push(`roles = $${idx}::text[]`);
|
|
53
|
+
// sql.unsafe receives raw params — use Postgres array literal syntax
|
|
54
|
+
// because there's no tagged-template array serialisation in unsafe mode.
|
|
55
|
+
// Roles are code-controlled enum values, not user input.
|
|
56
|
+
params.push(`{${data.roles.join(',')}}`);
|
|
57
|
+
idx++;
|
|
58
|
+
}
|
|
59
|
+
if (data.isActive !== undefined) {
|
|
60
|
+
sets.push(`is_active = $${idx}`);
|
|
61
|
+
params.push(data.isActive);
|
|
62
|
+
idx++;
|
|
63
|
+
}
|
|
64
|
+
if (data.oidcSubject !== undefined) {
|
|
65
|
+
sets.push(`oidc_subject = $${idx}`);
|
|
66
|
+
params.push(data.oidcSubject);
|
|
67
|
+
idx++;
|
|
68
|
+
}
|
|
69
|
+
if (data.oidcIssuer !== undefined) {
|
|
70
|
+
sets.push(`oidc_issuer = $${idx}`);
|
|
71
|
+
params.push(data.oidcIssuer);
|
|
72
|
+
idx++;
|
|
73
|
+
}
|
|
74
|
+
params.push(id);
|
|
75
|
+
const query = `UPDATE users SET ${sets.join(', ')} WHERE id = $${idx}::uuid RETURNING *`;
|
|
76
|
+
// sql.unsafe is intentional: the dynamic column set requires it, but column
|
|
77
|
+
// names are code-controlled (never from user input), so this is safe.
|
|
78
|
+
const rows = (await this.sql.unsafe(query, params));
|
|
79
|
+
return rows[0] ? rowToUser(rows[0]) : null;
|
|
80
|
+
}
|
|
81
|
+
async deleteUser(id) {
|
|
82
|
+
const result = await this.sql `DELETE FROM users WHERE id = ${id}::uuid`;
|
|
83
|
+
return affectedCount(result) > 0;
|
|
84
|
+
}
|
|
85
|
+
// --- Refresh Tokens ---
|
|
86
|
+
async createRefreshToken(userId, tokenHash, expiresAt) {
|
|
87
|
+
const rows = await this.sql `
|
|
88
|
+
INSERT INTO refresh_tokens (user_id, token_hash, expires_at)
|
|
89
|
+
VALUES (${userId}::uuid, ${tokenHash}, ${expiresAt}::timestamptz)
|
|
90
|
+
RETURNING id`;
|
|
91
|
+
const row = rows[0];
|
|
92
|
+
if (!row)
|
|
93
|
+
throw new Error('INSERT RETURNING produced no row');
|
|
94
|
+
return row.id;
|
|
95
|
+
}
|
|
96
|
+
async getRefreshTokenByHash(tokenHash) {
|
|
97
|
+
const rows = await this.sql `
|
|
98
|
+
SELECT * FROM refresh_tokens WHERE token_hash = ${tokenHash}`;
|
|
99
|
+
const row = rows[0];
|
|
100
|
+
if (!row)
|
|
101
|
+
return null;
|
|
102
|
+
return {
|
|
103
|
+
id: row.id,
|
|
104
|
+
userId: row.user_id,
|
|
105
|
+
tokenHash: row.token_hash,
|
|
106
|
+
expiresAt: row.expires_at instanceof Date ? row.expires_at.toISOString() : String(row.expires_at),
|
|
107
|
+
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async deleteRefreshToken(id) {
|
|
111
|
+
await this.sql `DELETE FROM refresh_tokens WHERE id = ${id}::uuid`;
|
|
112
|
+
}
|
|
113
|
+
async deleteUserRefreshTokens(userId) {
|
|
114
|
+
await this.sql `DELETE FROM refresh_tokens WHERE user_id = ${userId}::uuid`;
|
|
115
|
+
}
|
|
116
|
+
async deleteExpiredRefreshTokens() {
|
|
117
|
+
const result = await this.sql `DELETE FROM refresh_tokens WHERE expires_at < NOW()`;
|
|
118
|
+
return affectedCount(result);
|
|
119
|
+
}
|
|
120
|
+
// --- Per-Collection Permissions ---
|
|
121
|
+
async getUserPermissions(userId) {
|
|
122
|
+
const rows = await this.sql `
|
|
123
|
+
SELECT user_id, collection, role FROM user_permissions WHERE user_id = ${userId}::uuid`;
|
|
124
|
+
return rows.map((r) => ({
|
|
125
|
+
userId: r.user_id,
|
|
126
|
+
collection: r.collection,
|
|
127
|
+
role: r.role,
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
async setUserPermission(userId, collection, role) {
|
|
131
|
+
await this.sql `
|
|
132
|
+
INSERT INTO user_permissions (user_id, collection, role)
|
|
133
|
+
VALUES (${userId}::uuid, ${collection}, ${role})
|
|
134
|
+
ON CONFLICT (user_id, collection) DO UPDATE SET role = ${role}`;
|
|
135
|
+
}
|
|
136
|
+
async deleteUserPermission(userId, collection) {
|
|
137
|
+
const result = await this.sql `
|
|
138
|
+
DELETE FROM user_permissions WHERE user_id = ${userId}::uuid AND collection = ${collection}`;
|
|
139
|
+
return affectedCount(result) > 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Extract affected row count from a postgres.js query result */
|
|
143
|
+
function affectedCount(result) {
|
|
144
|
+
return result.count;
|
|
145
|
+
}
|
|
146
|
+
function rowToUser(row) {
|
|
147
|
+
return {
|
|
148
|
+
id: row.id,
|
|
149
|
+
email: row.email,
|
|
150
|
+
name: row.name,
|
|
151
|
+
roles: row.roles,
|
|
152
|
+
oidcSubject: row.oidc_subject,
|
|
153
|
+
oidcIssuer: row.oidc_issuer,
|
|
154
|
+
isActive: row.is_active,
|
|
155
|
+
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at),
|
|
156
|
+
updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : String(row.updated_at),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=auth-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-adapter.js","sourceRoot":"","sources":["../src/auth-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ConflictResolution, ContentConflict, ContentManifestRecord, IContentMigrationAdapter } from '@krantho/persistence';
|
|
2
|
+
import type { Sql } from 'postgres';
|
|
3
|
+
/** Postgres content migration adapter */
|
|
4
|
+
export declare class PostgresContentMigrationAdapter implements IContentMigrationAdapter {
|
|
5
|
+
private readonly sql;
|
|
6
|
+
constructor(sql: Sql);
|
|
7
|
+
getContentManifestVersion(): Promise<number | null>;
|
|
8
|
+
getContentManifest(): Promise<ContentManifestRecord | null>;
|
|
9
|
+
saveContentManifest(manifest: ContentManifestRecord): Promise<void>;
|
|
10
|
+
listConflicts(): Promise<ContentConflict[]>;
|
|
11
|
+
resolveConflict(conflictId: number, resolution: ConflictResolution): Promise<boolean>;
|
|
12
|
+
recordConflict(conflict: Omit<ContentConflict, 'id' | 'resolved' | 'resolvedAt' | 'resolution'>): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=content-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-migration.d.ts","sourceRoot":"","sources":["../src/content-migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAEnC,yCAAyC;AACzC,qBAAa,+BAAgC,YAAW,wBAAwB;IAC9E,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAK;gBAEb,GAAG,EAAE,GAAG;IAId,yBAAyB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAQnD,kBAAkB,IAAI,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IAwB3D,mBAAmB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAanE,aAAa,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IA8B3C,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQrF,cAAc,CAClB,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,CAAC,GAC/E,OAAO,CAAC,IAAI,CAAC;CAajB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Postgres content migration adapter */
|
|
2
|
+
export class PostgresContentMigrationAdapter {
|
|
3
|
+
sql;
|
|
4
|
+
constructor(sql) {
|
|
5
|
+
this.sql = sql;
|
|
6
|
+
}
|
|
7
|
+
async getContentManifestVersion() {
|
|
8
|
+
const rows = (await this.sql.unsafe('SELECT MAX(repo_version) as max_version FROM content_manifest'));
|
|
9
|
+
return rows[0]?.max_version ?? null;
|
|
10
|
+
}
|
|
11
|
+
async getContentManifest() {
|
|
12
|
+
const rows = (await this.sql.unsafe('SELECT id, repo_version, repo_commit, imported_at, file_checksums FROM content_manifest ORDER BY id DESC LIMIT 1'));
|
|
13
|
+
const row = rows[0];
|
|
14
|
+
if (!row)
|
|
15
|
+
return null;
|
|
16
|
+
return {
|
|
17
|
+
id: row.id,
|
|
18
|
+
repoVersion: row.repo_version,
|
|
19
|
+
repoCommit: row.repo_commit,
|
|
20
|
+
importedAt: row.imported_at instanceof Date ? row.imported_at.toISOString() : String(row.imported_at),
|
|
21
|
+
fileChecksums: row.file_checksums,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async saveContentManifest(manifest) {
|
|
25
|
+
await this.sql.unsafe('INSERT INTO content_manifest (repo_version, repo_commit, imported_at, file_checksums) VALUES ($1, $2, $3, $4)',
|
|
26
|
+
// sql.json binds the object as JSONB; a JSON string would be stored as a string
|
|
27
|
+
[
|
|
28
|
+
manifest.repoVersion,
|
|
29
|
+
manifest.repoCommit,
|
|
30
|
+
manifest.importedAt,
|
|
31
|
+
this.sql.json(manifest.fileChecksums),
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
async listConflicts() {
|
|
35
|
+
const rows = (await this.sql.unsafe('SELECT id, migration_id, collection, slug, locale, repo_checksum, db_checksum, resolved, resolved_at, resolution FROM content_migration_conflicts WHERE resolved = FALSE ORDER BY id DESC'));
|
|
36
|
+
return rows.map((r) => ({
|
|
37
|
+
id: r.id,
|
|
38
|
+
migrationId: r.migration_id,
|
|
39
|
+
collection: r.collection,
|
|
40
|
+
slug: r.slug,
|
|
41
|
+
locale: r.locale,
|
|
42
|
+
repoChecksum: r.repo_checksum,
|
|
43
|
+
dbChecksum: r.db_checksum,
|
|
44
|
+
resolved: r.resolved,
|
|
45
|
+
resolvedAt: r.resolved_at instanceof Date ? r.resolved_at.toISOString() : r.resolved_at,
|
|
46
|
+
resolution: r.resolution,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
async resolveConflict(conflictId, resolution) {
|
|
50
|
+
const result = await this.sql.unsafe('UPDATE content_migration_conflicts SET resolved = TRUE, resolved_at = NOW(), resolution = $1 WHERE id = $2 AND resolved = FALSE', [resolution, conflictId]);
|
|
51
|
+
return result.count > 0;
|
|
52
|
+
}
|
|
53
|
+
async recordConflict(conflict) {
|
|
54
|
+
await this.sql.unsafe('INSERT INTO content_migration_conflicts (migration_id, collection, slug, locale, repo_checksum, db_checksum) VALUES ($1, $2, $3, $4, $5, $6)', [
|
|
55
|
+
conflict.migrationId,
|
|
56
|
+
conflict.collection,
|
|
57
|
+
conflict.slug,
|
|
58
|
+
conflict.locale,
|
|
59
|
+
conflict.repoChecksum,
|
|
60
|
+
conflict.dbChecksum,
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=content-migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-migration.js","sourceRoot":"","sources":["../src/content-migration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ContentQuery, QueryPage, QueryProjection } from '@krantho/persistence';
|
|
2
|
+
import type { Sql } from 'postgres';
|
|
3
|
+
import type { SqlParam } from './row-mapping.js';
|
|
4
|
+
/**
|
|
5
|
+
* The ORDER BY of a query: the requested sort, then `collection, slug, locale` so paging is stable.
|
|
6
|
+
*
|
|
7
|
+
* Exported for unit testing — consumers of this package should not call it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function orderByClause(sort: string | undefined): string;
|
|
10
|
+
/**
|
|
11
|
+
* Build the one statement a content query runs: the projection's columns, the rows of
|
|
12
|
+
* its state, the text match, the order and a page of `limit + 1` rows.
|
|
13
|
+
*
|
|
14
|
+
* Every name in the SQL is a whitelisted column; collections, locale, match text and
|
|
15
|
+
* paging are parameters.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildContentQuery(spec: ContentQuery): {
|
|
18
|
+
sql: string;
|
|
19
|
+
params: SqlParam[];
|
|
20
|
+
};
|
|
21
|
+
/** Run a content query: one statement, one page */
|
|
22
|
+
export declare function runContentQuery<P extends QueryProjection>(sql: Sql, spec: ContentQuery<P>): Promise<QueryPage<P>>;
|
|
23
|
+
//# sourceMappingURL=content-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-query.d.ts","sourceRoot":"","sources":["../src/content-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAGZ,SAAS,EACT,eAAe,EAChB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AACnC,OAAO,KAAK,EAAuB,QAAQ,EAAc,MAAM,kBAAkB,CAAA;AA2DjF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAQ9D;AAyDD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,EAAE,CAAA;CAAE,CAwBzF;AAaD,mDAAmD;AACnD,wBAAsB,eAAe,CAAC,CAAC,SAAS,eAAe,EAC7D,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAWvB"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { isDirectSortColumn, isSafeSortField, statusCondition } from '@krantho/persistence';
|
|
2
|
+
import { META_COLUMNS, rowToContentDocument, rowToDocumentMeta, rowToSummary, SUMMARY_COLUMNS, } from './row-mapping.js';
|
|
3
|
+
/** Characters of a snippet before the match, and from the match on */
|
|
4
|
+
const SNIPPET_BEFORE = 80;
|
|
5
|
+
const SNIPPET_LENGTH = 200;
|
|
6
|
+
/** The column set a projection reads; `full` reads the whole row */
|
|
7
|
+
const PROJECTION_COLUMNS = {
|
|
8
|
+
meta: META_COLUMNS,
|
|
9
|
+
summary: SUMMARY_COLUMNS,
|
|
10
|
+
full: '*',
|
|
11
|
+
};
|
|
12
|
+
/** The SQL expression of the text each match target searches */
|
|
13
|
+
const MATCH_COLUMNS = {
|
|
14
|
+
slug: 'slug',
|
|
15
|
+
fields: 'fields::text',
|
|
16
|
+
body: 'body',
|
|
17
|
+
};
|
|
18
|
+
/** Escape LIKE wildcards (and the escape character) so the text matches literally */
|
|
19
|
+
function escapeLike(text) {
|
|
20
|
+
return text.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
21
|
+
}
|
|
22
|
+
/** Collects the parameters of one statement and hands out their `$n` placeholders */
|
|
23
|
+
class Parameters {
|
|
24
|
+
values = [];
|
|
25
|
+
bind(value) {
|
|
26
|
+
this.values.push(value);
|
|
27
|
+
return `$${this.values.length}`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The sort keys of a field, ordered as SQLite orders `json_extract`: a missing value first,
|
|
32
|
+
* then numbers numerically, then the rest as text. `fields->>` alone is text, which would
|
|
33
|
+
* put `10` before `9`.
|
|
34
|
+
*/
|
|
35
|
+
function fieldSortKeys(field, dir) {
|
|
36
|
+
const value = `fields->'${field}'`;
|
|
37
|
+
const text = `fields->>'${field}'`;
|
|
38
|
+
const isNumber = `jsonb_typeof(${value}) = 'number'`;
|
|
39
|
+
return [
|
|
40
|
+
`${text} IS NOT NULL ${dir}`,
|
|
41
|
+
`NOT ${isNumber} ${dir}`,
|
|
42
|
+
`CASE WHEN ${isNumber} THEN (${text})::numeric END ${dir}`,
|
|
43
|
+
`${text} ${dir}`,
|
|
44
|
+
].join(', ');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The ORDER BY of a query: the requested sort, then `collection, slug, locale` so paging is stable.
|
|
48
|
+
*
|
|
49
|
+
* Exported for unit testing — consumers of this package should not call it.
|
|
50
|
+
*/
|
|
51
|
+
export function orderByClause(sort) {
|
|
52
|
+
const [field, direction] = sort ? sort.split(':') : [];
|
|
53
|
+
const tieBreak = 'collection, slug, locale';
|
|
54
|
+
if (!field)
|
|
55
|
+
return ` ORDER BY ${tieBreak}`;
|
|
56
|
+
const dir = direction === 'desc' ? 'DESC' : 'ASC';
|
|
57
|
+
if (isDirectSortColumn(field))
|
|
58
|
+
return ` ORDER BY ${field} ${dir}, ${tieBreak}`;
|
|
59
|
+
if (isSafeSortField(field))
|
|
60
|
+
return ` ORDER BY ${fieldSortKeys(field, dir)}, ${tieBreak}`;
|
|
61
|
+
return ` ORDER BY ${tieBreak}`;
|
|
62
|
+
}
|
|
63
|
+
/** The condition that a document matches the text in any of the targets */
|
|
64
|
+
function matchCondition(targets, pattern) {
|
|
65
|
+
const likes = targets.map((target) => `lower(${MATCH_COLUMNS[target]}) LIKE ${pattern} ESCAPE '\\'`);
|
|
66
|
+
return `(${likes.join(' OR ')})`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The substring of the first match: from the body when it matches there, else from
|
|
70
|
+
* the fields' JSON. `strpos` and `substr` count characters, so the position found in
|
|
71
|
+
* the folded text applies to the original.
|
|
72
|
+
*/
|
|
73
|
+
function snippetColumn(targets, needle) {
|
|
74
|
+
const cases = ['body', 'fields']
|
|
75
|
+
.filter((target) => targets.includes(target))
|
|
76
|
+
.map((target) => {
|
|
77
|
+
const column = MATCH_COLUMNS[target];
|
|
78
|
+
const position = `strpos(lower(${column}), ${needle})`;
|
|
79
|
+
return `WHEN ${position} > 0 THEN substr(${column}, greatest(1, ${position} - ${SNIPPET_BEFORE}), ${SNIPPET_LENGTH})`;
|
|
80
|
+
});
|
|
81
|
+
return `, CASE ${cases.join(' ')} END AS snippet`;
|
|
82
|
+
}
|
|
83
|
+
/** The conditions that narrow rows to the query's collections and locale; `prefix` qualifies the columns */
|
|
84
|
+
function scopeConditions(spec, params, prefix) {
|
|
85
|
+
const conditions = [];
|
|
86
|
+
if (spec.collections) {
|
|
87
|
+
const list = spec.collections.map((collection) => params.bind(collection)).join(', ');
|
|
88
|
+
conditions.push(spec.collections.length > 0 ? `${prefix}collection IN (${list})` : 'FALSE');
|
|
89
|
+
}
|
|
90
|
+
if (spec.locale)
|
|
91
|
+
conditions.push(`${prefix}locale = ${params.bind(spec.locale)}`);
|
|
92
|
+
return conditions;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The WHERE conditions of the rows the query reads. `current` is the current, not
|
|
96
|
+
* deleted rows; `published` the live row of each current, not deleted document, found
|
|
97
|
+
* in a subquery over the same table so the outer columns stay those of the live row.
|
|
98
|
+
*/
|
|
99
|
+
function sourceConditions(spec, params) {
|
|
100
|
+
if (spec.state === 'current') {
|
|
101
|
+
const where = ['is_current = TRUE', 'deleted_at IS NULL', ...scopeConditions(spec, params, '')];
|
|
102
|
+
if (spec.status)
|
|
103
|
+
where.push(statusCondition(spec.status));
|
|
104
|
+
return where;
|
|
105
|
+
}
|
|
106
|
+
const inner = ['cur.is_current = TRUE', 'cur.deleted_at IS NULL'];
|
|
107
|
+
inner.push(...scopeConditions(spec, params, 'cur.'));
|
|
108
|
+
const liveIds = `SELECT live.id FROM content cur
|
|
109
|
+
JOIN content live ON live.collection = cur.collection AND live.slug = cur.slug
|
|
110
|
+
AND live.locale = cur.locale AND live.version = cur.live_version
|
|
111
|
+
WHERE ${inner.join(' AND ')}`;
|
|
112
|
+
return [`id IN (${liveIds})`];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build the one statement a content query runs: the projection's columns, the rows of
|
|
116
|
+
* its state, the text match, the order and a page of `limit + 1` rows.
|
|
117
|
+
*
|
|
118
|
+
* Every name in the SQL is a whitelisted column; collections, locale, match text and
|
|
119
|
+
* paging are parameters.
|
|
120
|
+
*/
|
|
121
|
+
export function buildContentQuery(spec) {
|
|
122
|
+
const params = new Parameters();
|
|
123
|
+
const where = sourceConditions(spec, params);
|
|
124
|
+
let snippet = '';
|
|
125
|
+
if (spec.match) {
|
|
126
|
+
// A parameter no expression references fails in Postgres ("could not determine
|
|
127
|
+
// data type"), so each is bound only when its expression is written.
|
|
128
|
+
const needle = spec.match.text.toLowerCase();
|
|
129
|
+
const targets = spec.match.in;
|
|
130
|
+
where.push(targets.length > 0
|
|
131
|
+
? matchCondition(targets, params.bind(`%${escapeLike(needle)}%`))
|
|
132
|
+
: 'FALSE');
|
|
133
|
+
if (targets.includes('body') || targets.includes('fields')) {
|
|
134
|
+
snippet = snippetColumn(targets, params.bind(needle));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
let sql = `SELECT ${PROJECTION_COLUMNS[spec.projection]}${snippet} FROM content WHERE ${where.join(' AND ')}`;
|
|
138
|
+
sql += orderByClause(spec.sort);
|
|
139
|
+
if (spec.limit !== undefined)
|
|
140
|
+
sql += ` LIMIT ${params.bind(spec.limit + 1)}`;
|
|
141
|
+
if (spec.offset !== undefined)
|
|
142
|
+
sql += ` OFFSET ${params.bind(spec.offset)}`;
|
|
143
|
+
return { sql, params: params.values };
|
|
144
|
+
}
|
|
145
|
+
/** A snippet is the matched text with its whitespace runs collapsed to one space */
|
|
146
|
+
function collapseWhitespace(text) {
|
|
147
|
+
return text.replace(/\s+/g, ' ');
|
|
148
|
+
}
|
|
149
|
+
function rowItem(projection, row) {
|
|
150
|
+
if (projection === 'meta')
|
|
151
|
+
return rowToDocumentMeta(row);
|
|
152
|
+
if (projection === 'summary')
|
|
153
|
+
return rowToSummary(row);
|
|
154
|
+
return rowToContentDocument(row);
|
|
155
|
+
}
|
|
156
|
+
/** Run a content query: one statement, one page */
|
|
157
|
+
export async function runContentQuery(sql, spec) {
|
|
158
|
+
const built = buildContentQuery(spec);
|
|
159
|
+
const rows = (await sql.unsafe(built.sql, built.params));
|
|
160
|
+
const hasMore = spec.limit !== undefined && rows.length > spec.limit;
|
|
161
|
+
const page = hasMore ? rows.slice(0, spec.limit) : rows;
|
|
162
|
+
const items = page.map((row) => {
|
|
163
|
+
const item = rowItem(spec.projection, row);
|
|
164
|
+
if (typeof row['snippet'] === 'string')
|
|
165
|
+
item.snippet = collapseWhitespace(row['snippet']);
|
|
166
|
+
return item;
|
|
167
|
+
});
|
|
168
|
+
return { items, hasMore };
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=content-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-query.js","sourceRoot":"","sources":["../src/content-query.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { PostgresAuthAdapter } from './auth-adapter.js';
|
|
2
|
+
export { PostgresContentMigrationAdapter } from './content-migration.js';
|
|
3
|
+
export type { PostgresMigration } from './migration-runner.js';
|
|
4
|
+
export { getAppliedMigrations, getSchemaVersion, runMigrations } from './migration-runner.js';
|
|
5
|
+
export { postgresMigrations } from './migrations/index.js';
|
|
6
|
+
export type { PostgresAdapterOptions, PostgresDefaultStatus } from './postgres-adapter.js';
|
|
7
|
+
export { DEFAULT_POSTGRES_STATUS, PostgresAdapter } from './postgres-adapter.js';
|
|
8
|
+
export { PostgresCheckpointAdapter } from './postgres-checkpoints.js';
|
|
9
|
+
export { PostgresLockAdapter } from './postgres-lock-adapter.js';
|
|
10
|
+
export { PostgresMediaAdapter } from './postgres-media-adapter.js';
|
|
11
|
+
export { PostgresMediaFolderAdapter } from './postgres-media-folder-adapter.js';
|
|
12
|
+
export { PostgresPageCacheStore } from './postgres-page-cache-store.js';
|
|
13
|
+
export { PostgresRetentionAdapter } from './postgres-retention-adapter.js';
|
|
14
|
+
export { PostgresSeoConfigAdapter } from './postgres-seo-config-adapter.js';
|
|
15
|
+
export { PostgresSiteSettingsAdapter } from './postgres-site-settings-adapter.js';
|
|
16
|
+
export { PostgresWebhookDeliveryAdapter } from './postgres-webhook-delivery-adapter.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAA;AACxE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC1D,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC1F,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAA;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAA;AAC3E,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAA;AACjF,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { PostgresAuthAdapter } from './auth-adapter.js';
|
|
2
|
+
export { PostgresContentMigrationAdapter } from './content-migration.js';
|
|
3
|
+
export { getAppliedMigrations, getSchemaVersion, runMigrations } from './migration-runner.js';
|
|
4
|
+
export { postgresMigrations } from './migrations/index.js';
|
|
5
|
+
export { DEFAULT_POSTGRES_STATUS, PostgresAdapter } from './postgres-adapter.js';
|
|
6
|
+
export { PostgresCheckpointAdapter } from './postgres-checkpoints.js';
|
|
7
|
+
export { PostgresLockAdapter } from './postgres-lock-adapter.js';
|
|
8
|
+
export { PostgresMediaAdapter } from './postgres-media-adapter.js';
|
|
9
|
+
export { PostgresMediaFolderAdapter } from './postgres-media-folder-adapter.js';
|
|
10
|
+
export { PostgresPageCacheStore } from './postgres-page-cache-store.js';
|
|
11
|
+
export { PostgresRetentionAdapter } from './postgres-retention-adapter.js';
|
|
12
|
+
export { PostgresSeoConfigAdapter } from './postgres-seo-config-adapter.js';
|
|
13
|
+
export { PostgresSiteSettingsAdapter } from './postgres-site-settings-adapter.js';
|
|
14
|
+
export { PostgresWebhookDeliveryAdapter } from './postgres-webhook-delivery-adapter.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAA;AAExE,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAE1D,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAA;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAA;AAC3E,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAA;AACjF,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AppliedMigration, MigrationResult } from '@krantho/persistence';
|
|
2
|
+
import type { Sql, TransactionSql } from 'postgres';
|
|
3
|
+
/** A Postgres migration with async apply function */
|
|
4
|
+
export interface PostgresMigration {
|
|
5
|
+
version: number;
|
|
6
|
+
name: string;
|
|
7
|
+
apply: (sql: TransactionSql) => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run pending migrations on a Postgres database.
|
|
11
|
+
*
|
|
12
|
+
* Self-bootstrapping: creates the schema_migrations table if it doesn't exist.
|
|
13
|
+
* All pending migrations run in one transaction. Halts on first failure, which
|
|
14
|
+
* rolls back the whole run.
|
|
15
|
+
*
|
|
16
|
+
* Deciding what is pending and applying it is a check-then-act, so it runs while
|
|
17
|
+
* holding a transaction-scoped advisory lock: the lock is taken before the ledger
|
|
18
|
+
* is read and released when the last migration commits. Serverless platforms
|
|
19
|
+
* cold-start several instances against the same database at once, and without
|
|
20
|
+
* the lock they all read an empty ledger and race to create the same tables. A
|
|
21
|
+
* losing instance waits on the lock and then re-reads the ledger inside it,
|
|
22
|
+
* finding the work already done.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runMigrations(sql: Sql, migrations: readonly PostgresMigration[]): Promise<MigrationResult>;
|
|
25
|
+
/** Get the current schema version from the database */
|
|
26
|
+
export declare function getSchemaVersion(sql: Sql): Promise<number>;
|
|
27
|
+
/** Get all applied migrations */
|
|
28
|
+
export declare function getAppliedMigrations(sql: Sql): Promise<AppliedMigration[]>;
|
|
29
|
+
//# sourceMappingURL=migration-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-runner.d.ts","sourceRoot":"","sources":["../src/migration-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAqB,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAMhG,OAAO,KAAK,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAEnD,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C;AAuED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,SAAS,iBAAiB,EAAE,GACvC,OAAO,CAAC,eAAe,CAAC,CAgB1B;AAED,uDAAuD;AACvD,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAUhE;AAED,iCAAiC;AACjC,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAUhF"}
|