@owlmeans/postgres-resource 0.1.15
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 +219 -0
- package/agent-meta/instructions/postgres-resource.instructions.md +60 -0
- package/agent-meta/manifest.json +23 -0
- package/agent-meta/skills/postgres-resource/SKILL.md +186 -0
- package/build/consts.d.ts +81 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +86 -0
- package/build/consts.js.map +1 -0
- package/build/declarations.d.ts +12 -0
- package/build/declarations.d.ts.map +1 -0
- package/build/declarations.js +28 -0
- package/build/declarations.js.map +1 -0
- package/build/errors.d.ts +70 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +201 -0
- package/build/errors.js.map +1 -0
- package/build/helper.d.ts +4 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +5 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/build/resource.d.ts +4 -0
- package/build/resource.d.ts.map +1 -0
- package/build/resource.js +395 -0
- package/build/resource.js.map +1 -0
- package/build/types.d.ts +273 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/criteria.d.ts +24 -0
- package/build/utils/criteria.d.ts.map +1 -0
- package/build/utils/criteria.js +209 -0
- package/build/utils/criteria.js.map +1 -0
- package/build/utils/diff.d.ts +24 -0
- package/build/utils/diff.d.ts.map +1 -0
- package/build/utils/diff.js +330 -0
- package/build/utils/diff.js.map +1 -0
- package/build/utils/index.d.ts +12 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +12 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/introspect.d.ts +7 -0
- package/build/utils/introspect.d.ts.map +1 -0
- package/build/utils/introspect.js +70 -0
- package/build/utils/introspect.js.map +1 -0
- package/build/utils/life-cycle.d.ts +39 -0
- package/build/utils/life-cycle.d.ts.map +1 -0
- package/build/utils/life-cycle.js +119 -0
- package/build/utils/life-cycle.js.map +1 -0
- package/build/utils/marshal.d.ts +36 -0
- package/build/utils/marshal.d.ts.map +1 -0
- package/build/utils/marshal.js +152 -0
- package/build/utils/marshal.js.map +1 -0
- package/build/utils/migrations.d.ts +16 -0
- package/build/utils/migrations.d.ts.map +1 -0
- package/build/utils/migrations.js +108 -0
- package/build/utils/migrations.js.map +1 -0
- package/build/utils/name.d.ts +39 -0
- package/build/utils/name.d.ts.map +1 -0
- package/build/utils/name.js +63 -0
- package/build/utils/name.js.map +1 -0
- package/build/utils/schema.d.ts +28 -0
- package/build/utils/schema.d.ts.map +1 -0
- package/build/utils/schema.js +356 -0
- package/build/utils/schema.js.map +1 -0
- package/build/utils/sql.d.ts +22 -0
- package/build/utils/sql.d.ts.map +1 -0
- package/build/utils/sql.js +0 -0
- package/build/utils/sql.js.map +1 -0
- package/build/utils/sync.d.ts +21 -0
- package/build/utils/sync.d.ts.map +1 -0
- package/build/utils/sync.js +93 -0
- package/build/utils/sync.js.map +1 -0
- package/build/utils/table.d.ts +8 -0
- package/build/utils/table.d.ts.map +1 -0
- package/build/utils/table.js +68 -0
- package/build/utils/table.js.map +1 -0
- package/package.json +50 -0
- package/src/consts.ts +95 -0
- package/src/declarations.ts +41 -0
- package/src/errors.ts +242 -0
- package/src/helper.ts +7 -0
- package/src/index.ts +7 -0
- package/src/resource.ts +524 -0
- package/src/types.ts +310 -0
- package/src/utils/criteria.ts +246 -0
- package/src/utils/diff.ts +363 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/introspect.ts +87 -0
- package/src/utils/life-cycle.ts +155 -0
- package/src/utils/marshal.ts +175 -0
- package/src/utils/migrations.ts +135 -0
- package/src/utils/name.ts +79 -0
- package/src/utils/schema.ts +426 -0
- package/src/utils/sql.ts +0 -0
- package/src/utils/sync.ts +106 -0
- package/src/utils/table.ts +76 -0
- package/tests/errors.spec.ts +121 -0
- package/tests/name.spec.ts +75 -0
- package/tests/schema.spec.ts +258 -0
- package/tests/sql.spec.ts +104 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ResourceRecord } from '@owlmeans/resource';
|
|
2
|
+
import type { TableSpec } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turn a driver row into a record: physical column names back to schema property names,
|
|
5
|
+
* plus the coercions node-postgres leaves to the caller.
|
|
6
|
+
*
|
|
7
|
+
* Columns the spec doesn't know about — computed expressions, joined columns from custom
|
|
8
|
+
* SQL — are kept verbatim. A join result is more useful than a lossy projection.
|
|
9
|
+
*/
|
|
10
|
+
export declare const rowToRecord: <T extends ResourceRecord>(row: Record<string, unknown>, spec: TableSpec) => T;
|
|
11
|
+
/**
|
|
12
|
+
* Same coercion as {@link rowToRecord}, for rows Drizzle produced.
|
|
13
|
+
*
|
|
14
|
+
* Drizzle keys its results by the table object's JS property names — which are the schema
|
|
15
|
+
* property names — where raw SQL returns physical column names. Two functions rather than
|
|
16
|
+
* one lookup that tries both, because a schema that renames `a` to column `b` while another
|
|
17
|
+
* property is itself named `b` would make the combined lookup pick the wrong spec.
|
|
18
|
+
*/
|
|
19
|
+
export declare const resultToRecord: <T extends ResourceRecord>(row: Record<string, unknown>, spec: TableSpec) => T;
|
|
20
|
+
/**
|
|
21
|
+
* Turn a record into the values Drizzle inserts, keyed by property name (Drizzle owns the
|
|
22
|
+
* property to column mapping).
|
|
23
|
+
*
|
|
24
|
+
* `undefined` properties are dropped so a partial write touches only what was supplied;
|
|
25
|
+
* an explicit `null` is kept, because nulling a column is a real intent.
|
|
26
|
+
*/
|
|
27
|
+
export declare const recordToValues: (record: Record<string, unknown>, spec: TableSpec) => Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Every managed column, with anything the record omits explicitly nulled.
|
|
30
|
+
*
|
|
31
|
+
* This is what makes `update()` a replace rather than a merge — the semantics mongo's
|
|
32
|
+
* `replaceOne` gives, kept identical so a resource behaves the same on either backend.
|
|
33
|
+
* The primary key and unmanaged columns are never nulled.
|
|
34
|
+
*/
|
|
35
|
+
export declare const recordToFullValues: (record: Record<string, unknown>, spec: TableSpec) => Record<string, unknown>;
|
|
36
|
+
//# sourceMappingURL=marshal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marshal.d.ts","sourceRoot":"","sources":["../../src/utils/marshal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAIxD,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,aAAa,CAAA;AAmFxD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,cAAc,EAAE,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAS,KAAG,CAerG,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,cAAc,EACrD,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAS,KAC5C,CAWF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAS,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAWvG,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAS,KAC/C,MAAM,CAAC,MAAM,EAAE,OAAO,CAWxB,CAAA"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { types as pgTypes } from 'pg';
|
|
2
|
+
import { ID_FIELD, PgTypeOid } from '../consts.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse a temporal value the way the driver would have.
|
|
5
|
+
*
|
|
6
|
+
* Drizzle's node-postgres session installs its own `getTypeParser` that hands date and
|
|
7
|
+
* timestamp columns back as raw text, because its own statically declared column types do
|
|
8
|
+
* the conversion. Ours are compiled at runtime and can't, so the driver's parser is called
|
|
9
|
+
* directly — the same function, just later. Raw SQL never comes through here, since that
|
|
10
|
+
* path keeps the driver's default parsers.
|
|
11
|
+
*/
|
|
12
|
+
const toDate = (value, column) => {
|
|
13
|
+
const oid = column.sqlType.startsWith('timestamp with time zone')
|
|
14
|
+
? PgTypeOid.TimestampTz
|
|
15
|
+
: column.sqlType.startsWith('timestamp')
|
|
16
|
+
? PgTypeOid.Timestamp
|
|
17
|
+
: PgTypeOid.Date;
|
|
18
|
+
return pgTypes.getTypeParser(oid)(value);
|
|
19
|
+
};
|
|
20
|
+
const fromDriver = (value, column) => {
|
|
21
|
+
if (value == null) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
/** Ciphertext is opaque — coercing it would corrupt it. */
|
|
25
|
+
if (column.secure) {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
switch (column.jsonType) {
|
|
29
|
+
case 'number':
|
|
30
|
+
/** Postgres returns `numeric` as a string to protect precision the JS number can't hold. */
|
|
31
|
+
return typeof value === 'string' ? parseFloat(value) : value;
|
|
32
|
+
case 'integer':
|
|
33
|
+
return typeof value === 'string' ? parseInt(value, 10) : value;
|
|
34
|
+
case 'bigint': {
|
|
35
|
+
if (typeof value !== 'string' && typeof value !== 'bigint') {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
const parsed = Number(value);
|
|
39
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
40
|
+
console.warn(`@owlmeans/postgres-resource: "${column.column}" holds ${value}, which exceeds the safe`
|
|
41
|
+
+ ' integer range — the value read back is imprecise.');
|
|
42
|
+
}
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
case 'binary':
|
|
46
|
+
return Buffer.isBuffer(value) ? value.toString('base64') : value;
|
|
47
|
+
case 'date':
|
|
48
|
+
return typeof value === 'string' ? toDate(value, column) : value;
|
|
49
|
+
default:
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const toDriver = (value, column) => {
|
|
54
|
+
if (value == null) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
if (column.secure) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
if (column.jsonb) {
|
|
61
|
+
/**
|
|
62
|
+
* node-postgres stringifies plain objects on its own but turns arrays into Postgres
|
|
63
|
+
* array literals, which a jsonb column rejects. Stringifying here covers both.
|
|
64
|
+
*/
|
|
65
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
66
|
+
}
|
|
67
|
+
if (column.jsonType === 'date' && !(value instanceof Date)) {
|
|
68
|
+
return new Date(value);
|
|
69
|
+
}
|
|
70
|
+
if (column.jsonType === 'binary' && typeof value === 'string') {
|
|
71
|
+
return Buffer.from(value, 'base64');
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Turn a driver row into a record: physical column names back to schema property names,
|
|
77
|
+
* plus the coercions node-postgres leaves to the caller.
|
|
78
|
+
*
|
|
79
|
+
* Columns the spec doesn't know about — computed expressions, joined columns from custom
|
|
80
|
+
* SQL — are kept verbatim. A join result is more useful than a lossy projection.
|
|
81
|
+
*/
|
|
82
|
+
export const rowToRecord = (row, spec) => {
|
|
83
|
+
const record = {};
|
|
84
|
+
for (const [key, value] of Object.entries(row)) {
|
|
85
|
+
const column = spec.byColumn[key];
|
|
86
|
+
if (column == null) {
|
|
87
|
+
record[key] = value;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
record[column.property] = fromDriver(value, column);
|
|
91
|
+
}
|
|
92
|
+
if (record[ID_FIELD] != null) {
|
|
93
|
+
record[ID_FIELD] = `${record[ID_FIELD]}`;
|
|
94
|
+
}
|
|
95
|
+
return record;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Same coercion as {@link rowToRecord}, for rows Drizzle produced.
|
|
99
|
+
*
|
|
100
|
+
* Drizzle keys its results by the table object's JS property names — which are the schema
|
|
101
|
+
* property names — where raw SQL returns physical column names. Two functions rather than
|
|
102
|
+
* one lookup that tries both, because a schema that renames `a` to column `b` while another
|
|
103
|
+
* property is itself named `b` would make the combined lookup pick the wrong spec.
|
|
104
|
+
*/
|
|
105
|
+
export const resultToRecord = (row, spec) => {
|
|
106
|
+
const record = {};
|
|
107
|
+
for (const [key, value] of Object.entries(row)) {
|
|
108
|
+
const column = spec.byProperty[key];
|
|
109
|
+
record[key] = column == null ? value : fromDriver(value, column);
|
|
110
|
+
}
|
|
111
|
+
if (record[ID_FIELD] != null) {
|
|
112
|
+
record[ID_FIELD] = `${record[ID_FIELD]}`;
|
|
113
|
+
}
|
|
114
|
+
return record;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Turn a record into the values Drizzle inserts, keyed by property name (Drizzle owns the
|
|
118
|
+
* property to column mapping).
|
|
119
|
+
*
|
|
120
|
+
* `undefined` properties are dropped so a partial write touches only what was supplied;
|
|
121
|
+
* an explicit `null` is kept, because nulling a column is a real intent.
|
|
122
|
+
*/
|
|
123
|
+
export const recordToValues = (record, spec) => {
|
|
124
|
+
const values = {};
|
|
125
|
+
for (const [key, value] of Object.entries(record)) {
|
|
126
|
+
const column = spec.byProperty[key];
|
|
127
|
+
if (column == null || value === undefined) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
values[column.property] = toDriver(value, column);
|
|
131
|
+
}
|
|
132
|
+
return values;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Every managed column, with anything the record omits explicitly nulled.
|
|
136
|
+
*
|
|
137
|
+
* This is what makes `update()` a replace rather than a merge — the semantics mongo's
|
|
138
|
+
* `replaceOne` gives, kept identical so a resource behaves the same on either backend.
|
|
139
|
+
* The primary key and unmanaged columns are never nulled.
|
|
140
|
+
*/
|
|
141
|
+
export const recordToFullValues = (record, spec) => {
|
|
142
|
+
const values = {};
|
|
143
|
+
for (const column of spec.columns) {
|
|
144
|
+
if (spec.primaryKey.includes(column.column) || spec.unmanaged.includes(column.column)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const value = record[column.property];
|
|
148
|
+
values[column.property] = value === undefined ? null : toDriver(value, column);
|
|
149
|
+
}
|
|
150
|
+
return values;
|
|
151
|
+
};
|
|
152
|
+
//# sourceMappingURL=marshal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marshal.js","sourceRoot":"","sources":["../../src/utils/marshal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MigrationStore } from '@owlmeans/resource';
|
|
2
|
+
import type { PoolClient } from 'pg';
|
|
3
|
+
import type { PostgresTx, TableSpec } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* A transaction façade over a checked out client. `{{alias}}` resolution is injected so
|
|
6
|
+
* migrations can address other resources by alias without importing the context.
|
|
7
|
+
*/
|
|
8
|
+
export declare const makeTx: (client: PoolClient, resolve: (text: string) => string, ref: (alias?: string) => string) => PostgresTx;
|
|
9
|
+
/**
|
|
10
|
+
* Migration ledger, one table per Postgres schema.
|
|
11
|
+
*
|
|
12
|
+
* It lives inside the resource's own schema, so an Entity layer schema carries its own
|
|
13
|
+
* ledger — correct, because it also carries its own tables.
|
|
14
|
+
*/
|
|
15
|
+
export declare const makeMigrationStore: (client: PoolClient, spec: TableSpec, table: string, resolve: (text: string) => string, ref: (alias?: string) => string) => MigrationStore<PostgresTx>;
|
|
16
|
+
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/utils/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnE,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,IAAI,CAAA;AAGpD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAGxD;;;GAGG;AACH,eAAO,MAAM,MAAM,GACjB,QAAQ,UAAU,EAAE,SAAS,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,KACrF,UA+BD,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,UAAU,EAClB,MAAM,SAAS,EACf,OAAO,MAAM,EACb,SAAS,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACjC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,KAC9B,cAAc,CAAC,UAAU,CA4E3B,CAAA"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { pgErrorToResourceError } from '../errors.js';
|
|
2
|
+
import { qualify, quoteIdent } from './name.js';
|
|
3
|
+
/**
|
|
4
|
+
* A transaction façade over a checked out client. `{{alias}}` resolution is injected so
|
|
5
|
+
* migrations can address other resources by alias without importing the context.
|
|
6
|
+
*/
|
|
7
|
+
export const makeTx = (client, resolve, ref) => ({
|
|
8
|
+
client,
|
|
9
|
+
query: async (text, params) => {
|
|
10
|
+
try {
|
|
11
|
+
const result = await client.query(resolve(text), params);
|
|
12
|
+
return result.rows;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
throw pgErrorToResourceError(error);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
queryOne: async (text, params) => {
|
|
19
|
+
try {
|
|
20
|
+
const result = await client.query(resolve(text), params);
|
|
21
|
+
return result.rows[0] ?? null;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw pgErrorToResourceError(error);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
execute: async (text, params) => {
|
|
28
|
+
try {
|
|
29
|
+
const result = await client.query(resolve(text), params);
|
|
30
|
+
return result.rowCount ?? 0;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw pgErrorToResourceError(error);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
ref
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Migration ledger, one table per Postgres schema.
|
|
40
|
+
*
|
|
41
|
+
* It lives inside the resource's own schema, so an Entity layer schema carries its own
|
|
42
|
+
* ledger — correct, because it also carries its own tables.
|
|
43
|
+
*/
|
|
44
|
+
export const makeMigrationStore = (client, spec, table, resolve, ref) => {
|
|
45
|
+
const ledger = qualify(spec.schema, table);
|
|
46
|
+
return {
|
|
47
|
+
ensure: async () => {
|
|
48
|
+
try {
|
|
49
|
+
await client.query(`CREATE TABLE IF NOT EXISTS ${ledger} (`
|
|
50
|
+
+ ` ${quoteIdent('alias')} text NOT NULL,`
|
|
51
|
+
+ ` ${quoteIdent('name')} text NOT NULL,`
|
|
52
|
+
+ ` ${quoteIdent('stage')} text NOT NULL DEFAULT 'pre',`
|
|
53
|
+
+ ` ${quoteIdent('checksum')} text,`
|
|
54
|
+
+ ` ${quoteIdent('baseline')} boolean NOT NULL DEFAULT false,`
|
|
55
|
+
+ ` ${quoteIdent('applied_at')} timestamptz NOT NULL DEFAULT now(),`
|
|
56
|
+
+ ` ${quoteIdent('duration_ms')} integer,`
|
|
57
|
+
+ ` CONSTRAINT ${quoteIdent(`${table}_pkey`)} PRIMARY KEY (${quoteIdent('alias')}, ${quoteIdent('name')})`
|
|
58
|
+
+ ')');
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw pgErrorToResourceError(error);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
applied: async (alias) => {
|
|
65
|
+
const result = await client.query(`SELECT ${quoteIdent('name')}, ${quoteIdent('checksum')} FROM ${ledger} WHERE ${quoteIdent('alias')} = $1`, [alias]);
|
|
66
|
+
return result.rows.reduce((applied, row) => {
|
|
67
|
+
applied[row.name] = row.checksum;
|
|
68
|
+
return applied;
|
|
69
|
+
}, {});
|
|
70
|
+
},
|
|
71
|
+
baseline: async (alias, migrations) => {
|
|
72
|
+
const values = migrations
|
|
73
|
+
.map((_, position) => `($1, $${position * 3 + 2}, $${position * 3 + 3}, $${position * 3 + 4}, true)`)
|
|
74
|
+
.join(', ');
|
|
75
|
+
const params = [alias];
|
|
76
|
+
for (const migration of migrations) {
|
|
77
|
+
params.push(migration.name, migration.stage, migration.checksum);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await client.query(`INSERT INTO ${ledger} (${quoteIdent('alias')}, ${quoteIdent('name')}, ${quoteIdent('stage')},`
|
|
81
|
+
+ ` ${quoteIdent('checksum')}, ${quoteIdent('baseline')}) VALUES ${values}`
|
|
82
|
+
+ ` ON CONFLICT (${quoteIdent('alias')}, ${quoteIdent('name')}) DO NOTHING`, params);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw pgErrorToResourceError(error);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
run: async (alias, migration) => {
|
|
89
|
+
const started = Date.now();
|
|
90
|
+
await client.query('BEGIN');
|
|
91
|
+
try {
|
|
92
|
+
await migration.apply(makeTx(client, resolve, ref));
|
|
93
|
+
/**
|
|
94
|
+
* The ledger row commits with the migration itself, so a migration is never left
|
|
95
|
+
* half applied and recorded — the pair is atomic or neither happened.
|
|
96
|
+
*/
|
|
97
|
+
await client.query(`INSERT INTO ${ledger} (${quoteIdent('alias')}, ${quoteIdent('name')}, ${quoteIdent('stage')},`
|
|
98
|
+
+ ` ${quoteIdent('checksum')}, ${quoteIdent('duration_ms')}) VALUES ($1, $2, $3, $4, $5)`, [alias, migration.name, migration.stage, migration.checksum, Date.now() - started]);
|
|
99
|
+
await client.query('COMMIT');
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
await client.query('ROLLBACK').catch(() => undefined);
|
|
103
|
+
throw pgErrorToResourceError(error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/utils/migrations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DbConfig, ResourceRecord } from '@owlmeans/resource';
|
|
2
|
+
import type { PostgresResource } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Coerce an arbitrary name into a legal Postgres identifier of at most 63 bytes.
|
|
5
|
+
*
|
|
6
|
+
* The shared `dbName()` helper's own overflow fallback emits 96 characters, which the
|
|
7
|
+
* server would silently truncate — losing the hash suffix that made the name unique in
|
|
8
|
+
* the first place. Truncating here keeps the disambiguator inside the limit.
|
|
9
|
+
*/
|
|
10
|
+
export declare const pgIdentifier: (name: string) => string;
|
|
11
|
+
/**
|
|
12
|
+
* Assert a value is safe to interpolate as an identifier. Postgres can't bind identifiers
|
|
13
|
+
* as parameters, so every one of them reaches the server as text — which makes this the
|
|
14
|
+
* only thing standing between a config value and injection.
|
|
15
|
+
*
|
|
16
|
+
* @throws {SyntaxError}
|
|
17
|
+
*/
|
|
18
|
+
export declare const assertSqlIdentifier: (value: string, what?: string) => string;
|
|
19
|
+
/** Quote an identifier for emission, doubling any interior quote. */
|
|
20
|
+
export declare const quoteIdent: (value: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Quote a string literal. Only ever used for values Postgres refuses to bind — notably
|
|
23
|
+
* the password in `CREATE ROLE`.
|
|
24
|
+
*
|
|
25
|
+
* @throws {SyntaxError} on a NUL byte, which no escaping makes safe.
|
|
26
|
+
*/
|
|
27
|
+
export declare const quoteLiteral: (value: string) => string;
|
|
28
|
+
export declare const qualify: (schema: string, table: string) => string;
|
|
29
|
+
/**
|
|
30
|
+
* Physical table name of a resource: the explicit `name`, else the resource alias,
|
|
31
|
+
* prefixed per `DbConfig.resourcePrefix` and sanitized.
|
|
32
|
+
*/
|
|
33
|
+
export declare const pgTableName: (config: DbConfig, resource: PostgresResource<ResourceRecord>) => string;
|
|
34
|
+
/**
|
|
35
|
+
* Derive a pair of int32s for `pg_advisory_lock` from a qualified table name, so every
|
|
36
|
+
* replica booting against the same table serializes on the same key.
|
|
37
|
+
*/
|
|
38
|
+
export declare const advisoryKey: (qualified: string) => [number, number];
|
|
39
|
+
//# sourceMappingURL=name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name.d.ts","sourceRoot":"","sources":["../../src/utils/name.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAGlE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAInD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAS3C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,MAAM,EAAE,OAAM,MAAqB,KAAG,MAMhF,CAAA;AAED,qEAAqE;AACrE,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,KAAG,MAA0C,CAAA;AAErF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,MAM5C,CAAA;AAED,eAAO,MAAM,OAAO,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MACV,CAAA;AAE9C;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,QAAQ,EAAE,UAAU,gBAAgB,CAAC,cAAc,CAAC,KAAG,MACT,CAAA;AAElF;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,WAAW,MAAM,KAAG,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAA"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
2
|
+
import { hex } from '@scure/base';
|
|
3
|
+
import { PG_MAX_IDENTIFIER } from '../consts.js';
|
|
4
|
+
const SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
5
|
+
/**
|
|
6
|
+
* Coerce an arbitrary name into a legal Postgres identifier of at most 63 bytes.
|
|
7
|
+
*
|
|
8
|
+
* The shared `dbName()` helper's own overflow fallback emits 96 characters, which the
|
|
9
|
+
* server would silently truncate — losing the hash suffix that made the name unique in
|
|
10
|
+
* the first place. Truncating here keeps the disambiguator inside the limit.
|
|
11
|
+
*/
|
|
12
|
+
export const pgIdentifier = (name) => {
|
|
13
|
+
const sanitized = name.replace(/[^A-Za-z0-9_$]/g, '_');
|
|
14
|
+
const prefixed = /^[A-Za-z_]/.test(sanitized) ? sanitized : `_${sanitized}`;
|
|
15
|
+
if (Buffer.byteLength(prefixed, 'utf8') <= PG_MAX_IDENTIFIER) {
|
|
16
|
+
return prefixed;
|
|
17
|
+
}
|
|
18
|
+
const digest = hex.encode(sha256(new TextEncoder().encode(name))).substring(0, 32);
|
|
19
|
+
return `${prefixed.substring(0, 30)}_${digest}`;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Assert a value is safe to interpolate as an identifier. Postgres can't bind identifiers
|
|
23
|
+
* as parameters, so every one of them reaches the server as text — which makes this the
|
|
24
|
+
* only thing standing between a config value and injection.
|
|
25
|
+
*
|
|
26
|
+
* @throws {SyntaxError}
|
|
27
|
+
*/
|
|
28
|
+
export const assertSqlIdentifier = (value, what = 'identifier') => {
|
|
29
|
+
if (!SAFE_IDENTIFIER.test(value) || Buffer.byteLength(value, 'utf8') > PG_MAX_IDENTIFIER) {
|
|
30
|
+
throw new SyntaxError(`postgres:unsafe-${what}:${value}`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
};
|
|
34
|
+
/** Quote an identifier for emission, doubling any interior quote. */
|
|
35
|
+
export const quoteIdent = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
36
|
+
/**
|
|
37
|
+
* Quote a string literal. Only ever used for values Postgres refuses to bind — notably
|
|
38
|
+
* the password in `CREATE ROLE`.
|
|
39
|
+
*
|
|
40
|
+
* @throws {SyntaxError} on a NUL byte, which no escaping makes safe.
|
|
41
|
+
*/
|
|
42
|
+
export const quoteLiteral = (value) => {
|
|
43
|
+
if (value.includes('\0')) {
|
|
44
|
+
throw new SyntaxError('postgres:unsafe-literal:nul-byte');
|
|
45
|
+
}
|
|
46
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
47
|
+
};
|
|
48
|
+
export const qualify = (schema, table) => `${quoteIdent(schema)}.${quoteIdent(table)}`;
|
|
49
|
+
/**
|
|
50
|
+
* Physical table name of a resource: the explicit `name`, else the resource alias,
|
|
51
|
+
* prefixed per `DbConfig.resourcePrefix` and sanitized.
|
|
52
|
+
*/
|
|
53
|
+
export const pgTableName = (config, resource) => pgIdentifier(`${config.resourcePrefix ?? ''}${resource.name ?? resource.alias}`);
|
|
54
|
+
/**
|
|
55
|
+
* Derive a pair of int32s for `pg_advisory_lock` from a qualified table name, so every
|
|
56
|
+
* replica booting against the same table serializes on the same key.
|
|
57
|
+
*/
|
|
58
|
+
export const advisoryKey = (qualified) => {
|
|
59
|
+
const digest = sha256(new TextEncoder().encode(`owlmeans:${qualified}`));
|
|
60
|
+
const view = new DataView(digest.buffer, digest.byteOffset, digest.byteLength);
|
|
61
|
+
return [view.getInt32(0, false), view.getInt32(4, false)];
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name.js","sourceRoot":"","sources":["../../src/utils/name.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AnySchema } from 'ajv';
|
|
2
|
+
import type { PgIndexSpec, TableSpec } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a Postgres type name to the spelling `format_type()` reports, so the drift
|
|
5
|
+
* comparison against a live table is an exact string match instead of a fuzzy one.
|
|
6
|
+
*/
|
|
7
|
+
export declare const toFormatType: (type: string) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Compile a resource's AJV schema into the table specification that drives both DDL
|
|
10
|
+
* emission and drift detection.
|
|
11
|
+
*
|
|
12
|
+
* `extraIndexes` carries what `resource.index()` declared. It's compiled here rather than
|
|
13
|
+
* merged afterwards so chained declarations get the same property-to-column mapping and
|
|
14
|
+
* the same generated names as schema-borne ones.
|
|
15
|
+
*
|
|
16
|
+
* @throws {UnsupportedArgumentError} when an override asks for something unrepresentable.
|
|
17
|
+
*/
|
|
18
|
+
export declare const schemaToTableSpec: (alias: string, schema: AnySchema | undefined, pgSchema: string, table: string, autoSync: boolean, extraIndexes?: PgIndexSpec[]) => TableSpec;
|
|
19
|
+
/**
|
|
20
|
+
* AJV keyword registration for consumers running in strict mode. The mapper reads the raw
|
|
21
|
+
* schema object and never validates through AJV, so this is purely to stop strict mode
|
|
22
|
+
* rejecting a schema that carries `pg` overrides.
|
|
23
|
+
*/
|
|
24
|
+
export declare const pgKeyword: {
|
|
25
|
+
keyword: string;
|
|
26
|
+
valid: boolean;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/utils/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAKpC,OAAO,KAAK,EAC+B,WAAW,EACrB,SAAS,EACzC,MAAM,aAAa,CAAA;AA0BpB;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAmB3C,CAAA;AA4KD;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,MAAM,EAAE,QAAQ,SAAS,GAAG,SAAS,EAAE,UAAU,MAAM,EAAE,OAAO,MAAM,EAC7E,UAAU,OAAO,EAAE,eAAc,WAAW,EAAO,KAClD,SA+KF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;CAAuC,CAAA"}
|