@owlmeans/postgres-resource 0.1.18-rc.40 → 0.1.18-rc.42
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/README.md +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/postgres-resource/SKILL.md +2 -2
- package/build/declarations.d.ts +1 -8
- package/build/declarations.d.ts.map +1 -1
- package/build/declarations.js.map +1 -1
- package/build/errors.d.ts +3 -1
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js +4 -2
- package/build/errors.js.map +1 -1
- package/build/resource.d.ts +1 -1
- package/build/resource.d.ts.map +1 -1
- package/build/resource.js.map +1 -1
- package/build/types.d.ts +6 -1
- package/build/types.d.ts.map +1 -1
- package/build/types.local.d.ts +14 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/build/utils/consts.local.d.ts +33 -0
- package/build/utils/consts.local.d.ts.map +1 -0
- package/build/utils/consts.local.js +61 -0
- package/build/utils/consts.local.js.map +1 -0
- package/build/utils/criteria.d.ts +2 -2
- package/build/utils/criteria.d.ts.map +1 -1
- package/build/utils/criteria.js +2 -3
- package/build/utils/criteria.js.map +1 -1
- package/build/utils/diff.d.ts.map +1 -1
- package/build/utils/diff.js +5 -3
- package/build/utils/diff.js.map +1 -1
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js.map +1 -1
- package/build/utils/introspect.d.ts.map +1 -1
- package/build/utils/introspect.js +1 -29
- package/build/utils/introspect.js.map +1 -1
- package/build/utils/life-cycle.d.ts +3 -8
- package/build/utils/life-cycle.d.ts.map +1 -1
- package/build/utils/life-cycle.js +5 -2
- package/build/utils/life-cycle.js.map +1 -1
- package/build/utils/marshal.d.ts.map +1 -1
- package/build/utils/marshal.js +11 -5
- package/build/utils/marshal.js.map +1 -1
- package/build/utils/migrations.d.ts.map +1 -1
- package/build/utils/migrations.js +9 -6
- package/build/utils/migrations.js.map +1 -1
- package/build/utils/name.d.ts.map +1 -1
- package/build/utils/name.js +2 -2
- package/build/utils/name.js.map +1 -1
- package/build/utils/schema.d.ts.map +1 -1
- package/build/utils/schema.js +6 -4
- package/build/utils/schema.js.map +1 -1
- package/build/utils/sql.d.ts +1 -9
- package/build/utils/sql.d.ts.map +1 -1
- package/build/utils/sql.js +0 -0
- package/build/utils/sql.js.map +1 -1
- package/build/utils/sync.d.ts.map +1 -1
- package/build/utils/sync.js +6 -16
- package/build/utils/sync.js.map +1 -1
- package/build/utils/types.d.ts +18 -0
- package/build/utils/types.d.ts.map +1 -0
- package/build/utils/types.js +2 -0
- package/build/utils/types.js.map +1 -0
- package/build/utils/types.local.d.ts +33 -0
- package/build/utils/types.local.d.ts.map +1 -0
- package/build/utils/types.local.js +2 -0
- package/build/utils/types.local.js.map +1 -0
- package/package.json +14 -13
- package/src/declarations.ts +1 -9
- package/src/errors.ts +5 -13
- package/src/resource.ts +3 -11
- package/src/types.local.ts +16 -0
- package/src/types.ts +7 -4
- package/src/utils/consts.local.ts +68 -0
- package/src/utils/criteria.ts +4 -19
- package/src/utils/diff.ts +5 -4
- package/src/utils/index.ts +1 -0
- package/src/utils/introspect.ts +1 -32
- package/src/utils/life-cycle.ts +8 -15
- package/src/utils/marshal.ts +12 -7
- package/src/utils/migrations.ts +9 -6
- package/src/utils/name.ts +2 -3
- package/src/utils/schema.ts +8 -32
- package/src/utils/sql.ts +0 -0
- package/src/utils/sync.ts +6 -18
- package/src/utils/types.local.ts +35 -0
- package/src/utils/types.ts +19 -0
- package/tests/diff.spec.ts +24 -0
- package/tests/errors.spec.ts +11 -0
package/src/utils/schema.ts
CHANGED
|
@@ -1,35 +1,15 @@
|
|
|
1
1
|
import { UnsupportedArgumentError } from '@owlmeans/resource'
|
|
2
2
|
import type { AnySchema } from 'ajv'
|
|
3
|
+
import { logger } from '@owlmeans/log'
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
DEF_ID_DEFAULT, DEF_JSON_TYPE, DEF_SQL_TYPE, ID_FIELD, PG_KEYWORD, PgIndexMethod
|
|
6
7
|
} from '../consts.js'
|
|
7
|
-
import type {
|
|
8
|
-
ColumnJsonType, ColumnSpec, PgCheckSpec, PgIndexSpec, PgPropertyOverride, PgRootOverride,
|
|
9
|
-
PgReferenceSpec, PgUniqueSpec, TableSpec
|
|
10
|
-
} from '../types.js'
|
|
8
|
+
import type { ColumnJsonType, ColumnSpec, PgCheckSpec, PgIndexSpec, PgPropertyOverride, PgReferenceSpec, PgUniqueSpec, TableSpec } from '../types.js'
|
|
11
9
|
import { pgIdentifier, qualify, quoteIdent } from './name.js'
|
|
10
|
+
import type { RawProperty, RawSchema } from './types.local.js'
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
type?: string | string[]
|
|
15
|
-
format?: string
|
|
16
|
-
enum?: unknown[]
|
|
17
|
-
items?: RawProperty
|
|
18
|
-
maxLength?: number
|
|
19
|
-
maximum?: number
|
|
20
|
-
minimum?: number
|
|
21
|
-
default?: unknown
|
|
22
|
-
nullable?: boolean
|
|
23
|
-
secure?: boolean
|
|
24
|
-
[PG_KEYWORD]?: PgPropertyOverride
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface RawSchema {
|
|
28
|
-
properties?: Record<string, RawProperty>
|
|
29
|
-
required?: string[]
|
|
30
|
-
allOf?: RawSchema[]
|
|
31
|
-
[PG_KEYWORD]?: PgRootOverride
|
|
32
|
-
}
|
|
12
|
+
const log = logger('postgres-resource')
|
|
33
13
|
|
|
34
14
|
const INT32_MAX = 2147483647
|
|
35
15
|
|
|
@@ -244,10 +224,7 @@ const byName = <T extends { name?: string }>(specs: T[], alias: string, kind: st
|
|
|
244
224
|
for (const spec of specs) {
|
|
245
225
|
const name = spec.name!
|
|
246
226
|
if (seen.has(name)) {
|
|
247
|
-
|
|
248
|
-
`@owlmeans/postgres-resource: ${alias} declares ${kind} "${name}" more than once —`
|
|
249
|
-
+ ' keeping the first declaration.'
|
|
250
|
-
)
|
|
227
|
+
log.warn('Duplicate declaration, keeping the first one', { resource: alias, kind, name })
|
|
251
228
|
continue
|
|
252
229
|
}
|
|
253
230
|
seen.set(name, spec)
|
|
@@ -305,10 +282,9 @@ export const schemaToTableSpec = (
|
|
|
305
282
|
|
|
306
283
|
if (override.index != null && override.index !== false) {
|
|
307
284
|
if (column.secure) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
)
|
|
285
|
+
log.warn('Index on a secure column will never match: encryption is non-deterministic', {
|
|
286
|
+
resource: alias, column: name,
|
|
287
|
+
})
|
|
312
288
|
}
|
|
313
289
|
const specs = override.index === true
|
|
314
290
|
? [{ columns: [name] }]
|
package/src/utils/sql.ts
CHANGED
|
Binary file
|
package/src/utils/sync.ts
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
|
+
import { logger } from '@owlmeans/log'
|
|
1
2
|
import type { PoolClient } from 'pg'
|
|
2
3
|
|
|
3
|
-
import { PgErrorCode } from '../consts.js'
|
|
4
4
|
import { PostgresCastRequired, PostgresSyncError, describePgError, pgErrorToResourceError } from '../errors.js'
|
|
5
5
|
import type { DdlPlan, TableSpec } from '../types.js'
|
|
6
6
|
import { countNonNull } from './introspect.js'
|
|
7
7
|
import { advisoryKey, quoteIdent } from './name.js'
|
|
8
|
+
import { CAST_DATA_CODES } from './consts.local.js'
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
* Codes that mean "the cast is legal, the data isn't": a `text` column holding `'a'` retyped to
|
|
11
|
-
* `integer`, a value too wide for the new length, a number past the new range. Postgres refuses
|
|
12
|
-
* each of these rather than truncating, and the remedy is the same one a refused cast needs — so
|
|
13
|
-
* they are only read as a cast problem here, inside DDL. On the CRUD path the identical code means
|
|
14
|
-
* a caller passed a bad value, which is a different bug with a different fix.
|
|
15
|
-
*/
|
|
16
|
-
const CAST_DATA_CODES: string[] = [
|
|
17
|
-
PgErrorCode.InvalidTextRepresentation,
|
|
18
|
-
PgErrorCode.StringDataRightTruncation,
|
|
19
|
-
PgErrorCode.NumericValueOutOfRange
|
|
20
|
-
]
|
|
10
|
+
const log = logger('postgres-resource')
|
|
21
11
|
|
|
22
12
|
/**
|
|
23
13
|
* Serialize initialization across replicas. A session level lock, not `xact`, because
|
|
@@ -75,11 +65,9 @@ export const applyPlan = async (
|
|
|
75
65
|
for (const statement of plan.statements) {
|
|
76
66
|
current = statement.sql
|
|
77
67
|
if (statement.destructive === true && (statement.affected ?? 0) > 0) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
+ ' or set `pg: { managed: false }` to keep it.'
|
|
82
|
-
)
|
|
68
|
+
log.warn('Dropping a column that holds values; declare it under pg.unmanaged or set pg.managed false to keep it', {
|
|
69
|
+
table: spec.qualified, column: statement.target, rows: statement.affected,
|
|
70
|
+
}, { event: 'migration.drop' })
|
|
83
71
|
}
|
|
84
72
|
await client.query(statement.sql)
|
|
85
73
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SQL } from 'drizzle-orm'
|
|
2
|
+
import { PG_KEYWORD } from '../consts.js'
|
|
3
|
+
import type { PgPropertyOverride, PgRootOverride } from '../types.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A value at a path, as the jsonb it is (`#>`) and as text (`#>>`). The path is ONE bound `text[]`
|
|
7
|
+
* parameter, so a segment holding a comma or a brace stays one segment.
|
|
8
|
+
*/
|
|
9
|
+
export interface PathTarget {
|
|
10
|
+
value: SQL
|
|
11
|
+
text: SQL
|
|
12
|
+
/** Absent, or a JSON `null` — what every other store reads as "no value". */
|
|
13
|
+
missing: SQL
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RawProperty {
|
|
17
|
+
type?: string | string[]
|
|
18
|
+
format?: string
|
|
19
|
+
enum?: unknown[]
|
|
20
|
+
items?: RawProperty
|
|
21
|
+
maxLength?: number
|
|
22
|
+
maximum?: number
|
|
23
|
+
minimum?: number
|
|
24
|
+
default?: unknown
|
|
25
|
+
nullable?: boolean
|
|
26
|
+
secure?: boolean
|
|
27
|
+
[PG_KEYWORD]?: PgPropertyOverride
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RawSchema {
|
|
31
|
+
properties?: Record<string, RawProperty>
|
|
32
|
+
required?: string[]
|
|
33
|
+
allOf?: RawSchema[]
|
|
34
|
+
[PG_KEYWORD]?: PgRootOverride
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PgRuntimeTable, TableSpec } from '../types.js'
|
|
2
|
+
import type { MigrationReport } from '@owlmeans/resource'
|
|
3
|
+
import type { BasicContext } from '@owlmeans/context'
|
|
4
|
+
|
|
5
|
+
export interface TableInit {
|
|
6
|
+
spec: TableSpec
|
|
7
|
+
/** The Drizzle table CRUD is built against. */
|
|
8
|
+
entity: PgRuntimeTable
|
|
9
|
+
reports: MigrationReport[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The context a placeholder resolves through.
|
|
14
|
+
*
|
|
15
|
+
* Resolution reads exactly one thing — `context.resource(alias).table` — so the config a context
|
|
16
|
+
* carries is irrelevant here, and pinning one would force every caller holding a server context
|
|
17
|
+
* to cast its way in. Deliberately open, and named so callers pass what they already have.
|
|
18
|
+
*/
|
|
19
|
+
export type PlaceholderContext = BasicContext<any>
|
package/tests/diff.spec.ts
CHANGED
|
@@ -109,3 +109,27 @@ describe('@owlmeans/postgres-resource — comparing a declared definition with t
|
|
|
109
109
|
}
|
|
110
110
|
})
|
|
111
111
|
})
|
|
112
|
+
|
|
113
|
+
describe('@owlmeans/postgres-resource — NOT NULL constraints of Postgres 18', () => {
|
|
114
|
+
test('a not-null constraint row is never planned for a drop', () => {
|
|
115
|
+
// From 18 every NOT NULL is a pg_constraint row named `<table>_<column>_not_null`. The platform
|
|
116
|
+
// names its own constraints after the table too, so it took these for its own leftovers and sent
|
|
117
|
+
// `DROP CONSTRAINT …_id_not_null` — which Postgres refuses on a primary key column.
|
|
118
|
+
const live: LiveTable = {
|
|
119
|
+
exists: true,
|
|
120
|
+
columns: [
|
|
121
|
+
columnOf({ name: 'id', type: 'text' }),
|
|
122
|
+
columnOf({ name: 'title', type: 'text', notNull: false }),
|
|
123
|
+
],
|
|
124
|
+
indexes: [],
|
|
125
|
+
constraints: [
|
|
126
|
+
{ name: 'tasks_id_not_null', type: 'n', definition: 'NOT NULL id' },
|
|
127
|
+
{ name: 'task_entry_id_not_null', type: 'n', definition: 'NOT NULL id' },
|
|
128
|
+
],
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const dropped = planSync(specOf('string'), live).statements.filter(statement => statement.kind === 'drop-constraint')
|
|
132
|
+
expect(dropped).toEqual([])
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
package/tests/errors.spec.ts
CHANGED
|
@@ -97,6 +97,17 @@ describe('@owlmeans/postgres-resource — driver error translation', () => {
|
|
|
97
97
|
expect(pgErrorToResourceError(original)).toBe(original)
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
+
test('preserves the executed SQL and driver cause for migration diagnostics', () => {
|
|
101
|
+
const original = driver({ code: '42703', message: 'column o.enquiryId does not exist' })
|
|
102
|
+
const query = 'UPDATE "app"."opportunity" o SET "enquiryId" = $1 WHERE o."enquiryId" IS NULL'
|
|
103
|
+
const failure = pgErrorToResourceError(original, query)
|
|
104
|
+
|
|
105
|
+
expect(failure).toBeInstanceOf(PostgresError)
|
|
106
|
+
expect(failure.message).toContain('42703')
|
|
107
|
+
expect(failure.message).toContain(`SQL: ${query}`)
|
|
108
|
+
expect(failure.cause).toBe(original)
|
|
109
|
+
})
|
|
110
|
+
|
|
100
111
|
test('leaves an error that is not from the driver alone', () => {
|
|
101
112
|
const plain = new Error('not postgres at all')
|
|
102
113
|
|