@owlmeans/postgres-resource 0.1.18-rc.41 → 0.1.18-rc.43
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 +13 -13
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/postgres-resource/SKILL.md +2 -2
- package/build/consts.d.ts +9 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +6 -0
- package/build/consts.js.map +1 -1
- package/build/declarations/types.d.ts +12 -0
- package/build/declarations/types.d.ts.map +1 -0
- package/build/declarations/types.js +2 -0
- package/build/declarations/types.js.map +1 -0
- package/build/declarations.d.ts +6 -9
- package/build/declarations.d.ts.map +1 -1
- package/build/declarations.js +25 -15
- package/build/declarations.js.map +1 -1
- package/build/errors.d.ts +0 -20
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js +1 -111
- package/build/errors.js.map +1 -1
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/pg-error/types.d.ts +24 -0
- package/build/pg-error/types.d.ts.map +1 -0
- package/build/pg-error/types.js +2 -0
- package/build/pg-error/types.js.map +1 -0
- package/build/pg-error.d.ts +4 -0
- package/build/pg-error.d.ts.map +1 -0
- package/build/pg-error.js +100 -0
- package/build/pg-error.js.map +1 -0
- package/build/resource.d.ts +1 -1
- package/build/resource.d.ts.map +1 -1
- package/build/resource.js +15 -11
- 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 +35 -0
- package/build/utils/consts.local.d.ts.map +1 -0
- package/build/utils/consts.local.js +63 -0
- package/build/utils/consts.local.js.map +1 -0
- package/build/utils/criteria/types.d.ts +31 -0
- package/build/utils/criteria/types.d.ts.map +1 -0
- package/build/utils/criteria/types.js +2 -0
- package/build/utils/criteria/types.js.map +1 -0
- package/build/utils/criteria.d.ts +3 -27
- package/build/utils/criteria.d.ts.map +1 -1
- package/build/utils/criteria.js +88 -107
- package/build/utils/criteria.js.map +1 -1
- package/build/utils/diff/types.d.ts +40 -0
- package/build/utils/diff/types.d.ts.map +1 -0
- package/build/utils/diff/types.js +2 -0
- package/build/utils/diff/types.js.map +1 -0
- package/build/utils/diff.d.ts +5 -35
- package/build/utils/diff.d.ts.map +1 -1
- package/build/utils/diff.js +321 -343
- package/build/utils/diff.js.map +1 -1
- package/build/utils/index.d.ts +10 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js.map +1 -1
- package/build/utils/introspect/types.d.ts +9 -0
- package/build/utils/introspect/types.d.ts.map +1 -0
- package/build/utils/introspect/types.js +2 -0
- package/build/utils/introspect/types.js.map +1 -0
- package/build/utils/introspect.d.ts +3 -3
- package/build/utils/introspect.d.ts.map +1 -1
- package/build/utils/introspect.js +42 -67
- package/build/utils/introspect.js.map +1 -1
- package/build/utils/life-cycle/types.d.ts +37 -0
- package/build/utils/life-cycle/types.d.ts.map +1 -0
- package/build/utils/life-cycle/types.js +2 -0
- package/build/utils/life-cycle/types.js.map +1 -0
- package/build/utils/life-cycle.d.ts +3 -38
- package/build/utils/life-cycle.d.ts.map +1 -1
- package/build/utils/life-cycle.js +90 -107
- package/build/utils/life-cycle.js.map +1 -1
- package/build/utils/marshal/types.d.ts +39 -0
- package/build/utils/marshal/types.d.ts.map +1 -0
- package/build/utils/marshal/types.js +2 -0
- package/build/utils/marshal/types.js.map +1 -0
- package/build/utils/marshal.d.ts +4 -32
- package/build/utils/marshal.d.ts.map +1 -1
- package/build/utils/marshal.js +124 -141
- package/build/utils/marshal.js.map +1 -1
- package/build/utils/migrations.d.ts.map +1 -1
- package/build/utils/migrations.js +41 -36
- package/build/utils/migrations.js.map +1 -1
- package/build/utils/name/types.d.ts +43 -0
- package/build/utils/name/types.d.ts.map +1 -0
- package/build/utils/name/types.js +2 -0
- package/build/utils/name/types.js.map +1 -0
- package/build/utils/name.d.ts +6 -35
- package/build/utils/name.d.ts.map +1 -1
- package/build/utils/name.js +40 -59
- package/build/utils/name.js.map +1 -1
- package/build/utils/schema/types.d.ts +22 -0
- package/build/utils/schema/types.d.ts.map +1 -0
- package/build/utils/schema/types.js +2 -0
- package/build/utils/schema/types.js.map +1 -0
- package/build/utils/schema.d.ts +4 -24
- package/build/utils/schema.d.ts.map +1 -1
- package/build/utils/schema.js +341 -352
- package/build/utils/schema.js.map +1 -1
- package/build/utils/sql/types.d.ts +25 -0
- package/build/utils/sql/types.d.ts.map +1 -0
- package/build/utils/sql/types.js +2 -0
- package/build/utils/sql/types.js.map +1 -0
- package/build/utils/sql.d.ts +5 -27
- 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/types.d.ts +25 -0
- package/build/utils/sync/types.d.ts.map +1 -0
- package/build/utils/sync/types.js +2 -0
- package/build/utils/sync/types.js.map +1 -0
- package/build/utils/sync.d.ts +2 -19
- package/build/utils/sync.d.ts.map +1 -1
- package/build/utils/sync.js +67 -84
- 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/consts.ts +7 -0
- package/src/declarations/types.ts +12 -0
- package/src/declarations.ts +30 -23
- package/src/errors.ts +1 -131
- package/src/index.ts +3 -0
- package/src/pg-error/types.ts +23 -0
- package/src/pg-error.ts +114 -0
- package/src/resource.ts +20 -24
- package/src/types.local.ts +16 -0
- package/src/types.ts +8 -4
- package/src/utils/consts.local.ts +71 -0
- package/src/utils/criteria/types.ts +32 -0
- package/src/utils/criteria.ts +99 -129
- package/src/utils/diff/types.ts +41 -0
- package/src/utils/diff.ts +331 -348
- package/src/utils/index.ts +10 -0
- package/src/utils/introspect/types.ts +9 -0
- package/src/utils/introspect.ts +54 -79
- package/src/utils/life-cycle/types.ts +41 -0
- package/src/utils/life-cycle.ts +108 -134
- package/src/utils/marshal/types.ts +40 -0
- package/src/utils/marshal.ts +134 -147
- package/src/utils/migrations.ts +39 -33
- package/src/utils/name/types.ts +44 -0
- package/src/utils/name.ts +49 -63
- package/src/utils/schema/types.ts +26 -0
- package/src/utils/schema.ts +366 -397
- package/src/utils/sql/types.ts +25 -0
- package/src/utils/sql.ts +0 -0
- package/src/utils/sync/types.ts +25 -0
- package/src/utils/sync.ts +65 -83
- package/src/utils/types.local.ts +35 -0
- package/src/utils/types.ts +19 -0
- package/tests/criteria.spec.ts +4 -1
- package/tests/diff.spec.ts +28 -1
- package/tests/errors.spec.ts +4 -3
- package/tests/name.spec.ts +5 -3
- package/tests/schema.spec.ts +3 -1
- package/tests/sql.spec.ts +20 -22
package/src/utils/criteria.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { UnsupportedArgumentError } from '@owlmeans/resource'
|
|
2
|
-
import
|
|
3
|
-
import { and, asc, desc, or, param, sql } from 'drizzle-orm'
|
|
4
|
-
import type { SQL } from 'drizzle-orm'
|
|
1
|
+
import { UnsupportedArgumentError, type Criteria, type FieldOperators, type Sort } from '@owlmeans/resource'
|
|
2
|
+
import { and, asc, desc, or, param, sql, type SQL } from 'drizzle-orm'
|
|
5
3
|
|
|
6
4
|
import type { ColumnSpec, PgRuntimeTable, TableSpec } from '../types.js'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
5
|
+
import { COMPARISON } from './consts.local.js'
|
|
6
|
+
import type { PathTarget } from './types.local.js'
|
|
7
|
+
import type { PgCriteriaHelper } from './criteria/types.js'
|
|
11
8
|
|
|
12
9
|
const columnRef = (table: PgRuntimeTable, column: ColumnSpec): SQL => sql`${table[column.property]}`
|
|
13
10
|
|
|
@@ -166,17 +163,6 @@ const escapeLike = (value: string): string => value.replace(/[\\%_]/g, match =>
|
|
|
166
163
|
|
|
167
164
|
// ─── Dotted paths into a jsonb column ────────────────────────────────────────────────────────────
|
|
168
165
|
|
|
169
|
-
/**
|
|
170
|
-
* A value at a path, as the jsonb it is (`#>`) and as text (`#>>`). The path is ONE bound `text[]`
|
|
171
|
-
* parameter, so a segment holding a comma or a brace stays one segment.
|
|
172
|
-
*/
|
|
173
|
-
interface PathTarget {
|
|
174
|
-
value: SQL
|
|
175
|
-
text: SQL
|
|
176
|
-
/** Absent, or a JSON `null` — what every other store reads as "no value". */
|
|
177
|
-
missing: SQL
|
|
178
|
-
}
|
|
179
|
-
|
|
180
166
|
const pathTarget = (table: PgRuntimeTable, column: ColumnSpec, path: string[]): PathTarget => {
|
|
181
167
|
const value = sql`(${columnRef(table, column)} #> ${param(path)}::text[])`
|
|
182
168
|
|
|
@@ -295,131 +281,115 @@ const isOperatorSpec = (value: unknown): value is FieldOperators<any> =>
|
|
|
295
281
|
value != null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)
|
|
296
282
|
&& Object.keys(value).some(key => key.startsWith('$'))
|
|
297
283
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
* the whole table is the failure mode worth being loud about.
|
|
304
|
-
*
|
|
305
|
-
* @throws {UnsupportedArgumentError}
|
|
306
|
-
*/
|
|
307
|
-
export const criteriaToSql = <T>(
|
|
308
|
-
criteria: Criteria<T> | undefined, spec: TableSpec, table: PgRuntimeTable
|
|
309
|
-
): SQL | undefined => {
|
|
310
|
-
const conditions = build(criteria, spec, table)
|
|
284
|
+
export const createPgCriteriaHelper = (): PgCriteriaHelper => {
|
|
285
|
+
const criteriaToSql = <T>(
|
|
286
|
+
criteria: Criteria<T> | undefined, spec: TableSpec, table: PgRuntimeTable
|
|
287
|
+
): SQL | undefined => {
|
|
288
|
+
const conditions = build(criteria, spec, table)
|
|
311
289
|
|
|
312
|
-
|
|
313
|
-
}
|
|
290
|
+
return conditions.length > 0 ? and(...conditions) : undefined
|
|
291
|
+
}
|
|
314
292
|
|
|
315
|
-
const build = <T>(
|
|
316
|
-
|
|
317
|
-
): SQL[] => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
293
|
+
const build = <T>(
|
|
294
|
+
criteria: Criteria<T> | undefined, spec: TableSpec, table: PgRuntimeTable
|
|
295
|
+
): SQL[] => {
|
|
296
|
+
const conditions: SQL[] = []
|
|
297
|
+
for (const [key, raw] of Object.entries(criteria ?? {})) {
|
|
298
|
+
if (raw === undefined) {
|
|
299
|
+
continue
|
|
300
|
+
}
|
|
323
301
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
302
|
+
if (key === '$and' || key === '$or') {
|
|
303
|
+
const parts = (Array.isArray(raw) ? raw : [raw])
|
|
304
|
+
.map(entry => and(...build(entry as Criteria<T>, spec, table)))
|
|
305
|
+
.filter((entry): entry is SQL => entry != null)
|
|
306
|
+
if (parts.length > 0) {
|
|
307
|
+
const combined = key === '$and' ? and(...parts) : or(...parts)
|
|
308
|
+
if (combined != null) {
|
|
309
|
+
conditions.push(combined)
|
|
310
|
+
}
|
|
332
311
|
}
|
|
312
|
+
continue
|
|
333
313
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
314
|
+
if (key === '$not') {
|
|
315
|
+
const inner = and(...build(raw as Criteria<T>, spec, table))
|
|
316
|
+
if (inner != null) {
|
|
317
|
+
conditions.push(sql`NOT (${inner})`)
|
|
318
|
+
}
|
|
319
|
+
continue
|
|
340
320
|
}
|
|
341
|
-
continue
|
|
342
|
-
}
|
|
343
321
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
322
|
+
/** `profile.city` reaches into a jsonb column rather than naming a column. */
|
|
323
|
+
const [head, ...path] = key.split('.')
|
|
324
|
+
const column = spec.byProperty[head]
|
|
325
|
+
if (column == null) {
|
|
326
|
+
throw new UnsupportedArgumentError(`criteria:${key}`)
|
|
327
|
+
}
|
|
328
|
+
if (path.length > 0) {
|
|
329
|
+
if (!column.jsonb) {
|
|
330
|
+
throw new UnsupportedArgumentError(`criteria-path:${key}`)
|
|
331
|
+
}
|
|
332
|
+
conditions.push(...pathCondition(pathTarget(table, column, path), raw, key))
|
|
333
|
+
continue
|
|
353
334
|
}
|
|
354
|
-
conditions.push(...pathCondition(pathTarget(table, column, path), raw, key))
|
|
355
|
-
continue
|
|
356
|
-
}
|
|
357
335
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
336
|
+
if (raw === null) {
|
|
337
|
+
conditions.push(sql`${columnRef(table, column)} IS NULL`)
|
|
338
|
+
continue
|
|
339
|
+
}
|
|
340
|
+
if (isOperatorSpec(raw)) {
|
|
341
|
+
conditions.push(...operators(table, column, raw))
|
|
342
|
+
continue
|
|
343
|
+
}
|
|
344
|
+
if (Array.isArray(raw)) {
|
|
345
|
+
/**
|
|
346
|
+
* A bare array means `IN` for a relational store, which is overwhelmingly the intent.
|
|
347
|
+
* Exact array equality against a `text[]` column stays available as `{ $eq: [...] }`.
|
|
348
|
+
*/
|
|
349
|
+
conditions.push(inList(table, column, raw, false))
|
|
350
|
+
continue
|
|
351
|
+
}
|
|
352
|
+
if (column.jsonb && typeof raw === 'object') {
|
|
353
|
+
conditions.push(sql`${columnRef(table, column)} @> ${JSON.stringify(raw)}`)
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
conditions.push(sql`${columnRef(table, column)} = ${value(raw, column)}`)
|
|
377
357
|
}
|
|
378
|
-
conditions.push(sql`${columnRef(table, column)} = ${value(raw, column)}`)
|
|
379
|
-
}
|
|
380
358
|
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Translate a {@link Sort} list into ORDER BY. A bare field name is ascending;
|
|
386
|
-
* `{ field, order: 'desc' }` reverses it — the same meaning every backend gives it.
|
|
387
|
-
*
|
|
388
|
-
* The primary key is always appended as a tiebreak. Postgres has no implicit row order, so
|
|
389
|
-
* paginating on a non-unique sort key silently duplicates and skips rows between pages —
|
|
390
|
-
* a difference from mongo that would otherwise surface as a data bug rather than an error.
|
|
391
|
-
*
|
|
392
|
-
* A dotted path is a column this table does not have: criteria can reach into jsonb, ORDER BY
|
|
393
|
-
* cannot, and refusing is better than ordering by something the caller did not name.
|
|
394
|
-
*
|
|
395
|
-
* @throws {UnsupportedArgumentError}
|
|
396
|
-
*/
|
|
397
|
-
export const sortToSql = <T>(
|
|
398
|
-
sort: Sort<T>[] | undefined, spec: TableSpec, table: PgRuntimeTable
|
|
399
|
-
): SQL[] => {
|
|
400
|
-
const order: SQL[] = []
|
|
401
|
-
const seen: string[] = []
|
|
402
|
-
|
|
403
|
-
for (const entry of sort ?? []) {
|
|
404
|
-
const property = typeof entry === 'string' ? entry : entry.field
|
|
405
|
-
const descending = typeof entry !== 'string' && entry.order === 'desc'
|
|
406
|
-
const column = spec.byProperty[property]
|
|
407
|
-
if (column == null) {
|
|
408
|
-
throw new UnsupportedArgumentError(`sort:${property}`)
|
|
409
|
-
}
|
|
410
|
-
seen.push(column.column)
|
|
411
|
-
order.push(descending ? desc(table[column.property]) : asc(table[column.property]))
|
|
359
|
+
return conditions
|
|
412
360
|
}
|
|
413
361
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
362
|
+
const sortToSql = <T>(
|
|
363
|
+
sort: Sort<T>[] | undefined, spec: TableSpec, table: PgRuntimeTable
|
|
364
|
+
): SQL[] => {
|
|
365
|
+
const order: SQL[] = []
|
|
366
|
+
const seen: string[] = []
|
|
367
|
+
|
|
368
|
+
for (const entry of sort ?? []) {
|
|
369
|
+
const property = typeof entry === 'string' ? entry : entry.field
|
|
370
|
+
const descending = typeof entry !== 'string' && entry.order === 'desc'
|
|
371
|
+
const column = spec.byProperty[property]
|
|
372
|
+
if (column == null) {
|
|
373
|
+
throw new UnsupportedArgumentError(`sort:${property}`)
|
|
374
|
+
}
|
|
375
|
+
seen.push(column.column)
|
|
376
|
+
order.push(descending ? desc(table[column.property]) : asc(table[column.property]))
|
|
417
377
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
378
|
+
|
|
379
|
+
for (const key of spec.primaryKey) {
|
|
380
|
+
if (seen.includes(key)) {
|
|
381
|
+
continue
|
|
382
|
+
}
|
|
383
|
+
const column = spec.byColumn[key]
|
|
384
|
+
if (column != null) {
|
|
385
|
+
order.push(asc(table[column.property]))
|
|
386
|
+
}
|
|
421
387
|
}
|
|
388
|
+
|
|
389
|
+
return order
|
|
422
390
|
}
|
|
423
391
|
|
|
424
|
-
return
|
|
392
|
+
return { criteriaToSql, sortToSql }
|
|
425
393
|
}
|
|
394
|
+
|
|
395
|
+
export const pgCriteriaHelper = createPgCriteriaHelper()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { BasicContext } from '@owlmeans/context'
|
|
2
|
+
|
|
3
|
+
import type { DdlPlan, DdlStatement, LiveTable, TableSpec } from '../../types.js'
|
|
4
|
+
|
|
5
|
+
/** Structure reconciliation: the DDL that converges a live table onto its compiled spec. */
|
|
6
|
+
export interface PgDiffHelper {
|
|
7
|
+
/**
|
|
8
|
+
* The form an index or constraint definition is compared in, so the statement this package emits
|
|
9
|
+
* and the text Postgres reports for the object it created compare equal.
|
|
10
|
+
*
|
|
11
|
+
* Postgres deparses what it stores: it leaves an identifier unquoted when it can, casts every
|
|
12
|
+
* literal (`''::text`), casts a `varchar` column inside an expression, wraps a partial predicate and
|
|
13
|
+
* each of its operator expressions in parentheses, and rewrites `x IN (…)` as `x = ANY (ARRAY[…])`.
|
|
14
|
+
* Comparing the raw texts made every boot drop and recreate every declared index and every enum
|
|
15
|
+
* `CHECK`. So both sides lose case, quotes, casts, whitespace and parentheses, and `= ANY (ARRAY[…])`
|
|
16
|
+
* reads as `IN (…)`. What that cannot tell apart — two definitions differing only in how their
|
|
17
|
+
* operators group — is a change to make under a new name.
|
|
18
|
+
*/
|
|
19
|
+
canonicalDefinition: (definition: string) => string
|
|
20
|
+
/**
|
|
21
|
+
* Foreign keys are planned separately from the rest of the table, and applied only once
|
|
22
|
+
* every resource has initialized: a key points at a table another resource owns, and that
|
|
23
|
+
* resource's own `init()` may not have run yet.
|
|
24
|
+
*
|
|
25
|
+
* @throws {PostgresPlaceholderError} when a target can't be resolved.
|
|
26
|
+
*/
|
|
27
|
+
planForeignKeys: (spec: TableSpec, context?: BasicContext<any>) => DdlStatement[]
|
|
28
|
+
/**
|
|
29
|
+
* Compare the compiled specification against the live table and produce the ordered DDL
|
|
30
|
+
* that converges one onto the other.
|
|
31
|
+
*
|
|
32
|
+
* Statement order is load bearing:
|
|
33
|
+
* - `DROP COLUMN` is last, because a `USING` cast or a backfill may legitimately read a
|
|
34
|
+
* column that is about to disappear.
|
|
35
|
+
* - A primary key constraint is never dropped: it cascades into every referencing key and
|
|
36
|
+
* can't be undone inside the same transaction once dependents exist.
|
|
37
|
+
*
|
|
38
|
+
* Foreign keys are not part of the plan at all — see {@link PgDiffHelper.planForeignKeys}.
|
|
39
|
+
*/
|
|
40
|
+
planSync: (spec: TableSpec, live: LiveTable, additive?: boolean) => DdlPlan
|
|
41
|
+
}
|