@stonyx/orm 0.3.2-beta.275 → 0.3.2-beta.277
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/column-conversion.d.ts +34 -0
- package/dist/column-conversion.js +34 -0
- package/dist/postgres/column-rules.d.ts +33 -0
- package/dist/postgres/column-rules.js +34 -0
- package/dist/postgres/postgres-db.d.ts +4 -0
- package/dist/postgres/postgres-db.js +16 -9
- package/package.json +3 -3
- package/src/column-conversion.ts +53 -0
- package/src/postgres/column-rules.ts +37 -0
- package/src/postgres/postgres-db.ts +20 -9
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared write-path column conversion (#292).
|
|
3
|
+
*
|
|
4
|
+
* This module owns the *iteration* only — the single place where a write
|
|
5
|
+
* payload is walked column-by-column and each value is handed to the driver's
|
|
6
|
+
* own conversion rule. The rules themselves are genuinely disjoint per driver
|
|
7
|
+
* (Postgres stringifies for JSONB; DynamoDB's DocumentClient marshals objects
|
|
8
|
+
* and arrays natively and must NOT be given stringified values) and therefore
|
|
9
|
+
* deliberately live with their drivers rather than being merged here.
|
|
10
|
+
*
|
|
11
|
+
* The duplicated create/update iteration was the drift vector that produced
|
|
12
|
+
* #292: `_recordToRow` converted, `_persistUpdate` did not. Routing both
|
|
13
|
+
* through `convertRow` removes the opportunity for the two to diverge again.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* A driver-supplied per-column conversion rule.
|
|
17
|
+
*
|
|
18
|
+
* @param value the raw value about to be bound into the query
|
|
19
|
+
* @param columnType the column's declared type from `schema.columns`, or
|
|
20
|
+
* `undefined` when the column is not an attribute column
|
|
21
|
+
* (e.g. `updated_at`, FK columns and `id`, which are added
|
|
22
|
+
* outside the attribute loop). Rules MUST be a no-op for
|
|
23
|
+
* `undefined`.
|
|
24
|
+
*/
|
|
25
|
+
export type ColumnRule = (value: unknown, columnType: string | undefined) => unknown;
|
|
26
|
+
/**
|
|
27
|
+
* Applies `rule` to every column of a complete write payload.
|
|
28
|
+
*
|
|
29
|
+
* Call this on the *finished* payload immediately before handing it to the
|
|
30
|
+
* query builder — not inside a payload-building loop. Columns appended after
|
|
31
|
+
* such a loop (FKs, `updated_at`) would otherwise bypass conversion, which is
|
|
32
|
+
* the same class of defect #292 reports.
|
|
33
|
+
*/
|
|
34
|
+
export declare function convertRow(row: Record<string, unknown>, columns: Record<string, string>, rule: ColumnRule): Record<string, unknown>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared write-path column conversion (#292).
|
|
3
|
+
*
|
|
4
|
+
* This module owns the *iteration* only — the single place where a write
|
|
5
|
+
* payload is walked column-by-column and each value is handed to the driver's
|
|
6
|
+
* own conversion rule. The rules themselves are genuinely disjoint per driver
|
|
7
|
+
* (Postgres stringifies for JSONB; DynamoDB's DocumentClient marshals objects
|
|
8
|
+
* and arrays natively and must NOT be given stringified values) and therefore
|
|
9
|
+
* deliberately live with their drivers rather than being merged here.
|
|
10
|
+
*
|
|
11
|
+
* The duplicated create/update iteration was the drift vector that produced
|
|
12
|
+
* #292: `_recordToRow` converted, `_persistUpdate` did not. Routing both
|
|
13
|
+
* through `convertRow` removes the opportunity for the two to diverge again.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Applies `rule` to every column of a complete write payload.
|
|
17
|
+
*
|
|
18
|
+
* Call this on the *finished* payload immediately before handing it to the
|
|
19
|
+
* query builder — not inside a payload-building loop. Columns appended after
|
|
20
|
+
* such a loop (FKs, `updated_at`) would otherwise bypass conversion, which is
|
|
21
|
+
* the same class of defect #292 reports.
|
|
22
|
+
*/
|
|
23
|
+
export function convertRow(row, columns, rule) {
|
|
24
|
+
const converted = {};
|
|
25
|
+
for (const [col, value] of Object.entries(row)) {
|
|
26
|
+
// `Object.hasOwn`, not `columns[col]`: a column named `toString` or
|
|
27
|
+
// `constructor` would otherwise reach the rule with a function as its
|
|
28
|
+
// "declared type". Inert for the shipped Postgres rule (strict equality
|
|
29
|
+
// against 'JSONB'), but `ColumnRule` is the extension point other drivers
|
|
30
|
+
// write against, and the obvious `if (columnType)` rule would invert.
|
|
31
|
+
converted[col] = rule(value, Object.hasOwn(columns, col) ? columns[col] : undefined);
|
|
32
|
+
}
|
|
33
|
+
return converted;
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ColumnRule } from '../column-conversion.js';
|
|
2
|
+
/**
|
|
3
|
+
* PostgreSQL's write-path conversion rule (#292).
|
|
4
|
+
*
|
|
5
|
+
* JSONB columns must receive a JSON *string*. node-pg's `prepareValue`
|
|
6
|
+
* (`pg/lib/utils.js`) checks `Array.isArray` before its object fallback, so a
|
|
7
|
+
* raw array is rendered as a Postgres array literal (`{"1","2","3"}`) rather
|
|
8
|
+
* than JSON. `[]` becomes `{}`, which is valid JSON, so it lands silently
|
|
9
|
+
* corrupted with no error.
|
|
10
|
+
*
|
|
11
|
+
* Two behaviours this rule must keep:
|
|
12
|
+
*
|
|
13
|
+
* - `typeof value === 'string'` passes through untouched. A JSONB column
|
|
14
|
+
* already holding a JSON string (`'{"a":1}'`) must store the object it
|
|
15
|
+
* describes; an unconditional `JSON.stringify` would double-encode it.
|
|
16
|
+
* - An `undefined` column type is a no-op. `updated_at`, `id` and FK columns
|
|
17
|
+
* are added outside the attribute loop and are absent from
|
|
18
|
+
* `schema.columns`.
|
|
19
|
+
*
|
|
20
|
+
* The `'JSONB'` sentinel is the exact literal emitted by
|
|
21
|
+
* `src/postgres/type-map.ts`, the only module that produces the values in
|
|
22
|
+
* `schema.columns`. The two producing functions are `getPgType` and
|
|
23
|
+
* `getVectorType` (`src/postgres/schema-introspector.ts` writes `columns[key]`
|
|
24
|
+
* at exactly three sites, all routed through those two); `mysqlTypeToPg` is not
|
|
25
|
+
* exported and is reachable only from inside `getPgType`.
|
|
26
|
+
*
|
|
27
|
+
* One documented gap: `getPgType` returns a consumer transform's declared
|
|
28
|
+
* `pgType` verbatim, so a consumer writing `pgType: 'jsonb'` puts a value in
|
|
29
|
+
* `schema.columns` that type-map never authored and this exact-match sentinel
|
|
30
|
+
* misses. It misses identically on the create path, pre-fix and post-fix --
|
|
31
|
+
* pre-existing and symmetric, not introduced here.
|
|
32
|
+
*/
|
|
33
|
+
export declare const postgresJsonRule: ColumnRule;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL's write-path conversion rule (#292).
|
|
3
|
+
*
|
|
4
|
+
* JSONB columns must receive a JSON *string*. node-pg's `prepareValue`
|
|
5
|
+
* (`pg/lib/utils.js`) checks `Array.isArray` before its object fallback, so a
|
|
6
|
+
* raw array is rendered as a Postgres array literal (`{"1","2","3"}`) rather
|
|
7
|
+
* than JSON. `[]` becomes `{}`, which is valid JSON, so it lands silently
|
|
8
|
+
* corrupted with no error.
|
|
9
|
+
*
|
|
10
|
+
* Two behaviours this rule must keep:
|
|
11
|
+
*
|
|
12
|
+
* - `typeof value === 'string'` passes through untouched. A JSONB column
|
|
13
|
+
* already holding a JSON string (`'{"a":1}'`) must store the object it
|
|
14
|
+
* describes; an unconditional `JSON.stringify` would double-encode it.
|
|
15
|
+
* - An `undefined` column type is a no-op. `updated_at`, `id` and FK columns
|
|
16
|
+
* are added outside the attribute loop and are absent from
|
|
17
|
+
* `schema.columns`.
|
|
18
|
+
*
|
|
19
|
+
* The `'JSONB'` sentinel is the exact literal emitted by
|
|
20
|
+
* `src/postgres/type-map.ts`, the only module that produces the values in
|
|
21
|
+
* `schema.columns`. The two producing functions are `getPgType` and
|
|
22
|
+
* `getVectorType` (`src/postgres/schema-introspector.ts` writes `columns[key]`
|
|
23
|
+
* at exactly three sites, all routed through those two); `mysqlTypeToPg` is not
|
|
24
|
+
* exported and is reachable only from inside `getPgType`.
|
|
25
|
+
*
|
|
26
|
+
* One documented gap: `getPgType` returns a consumer transform's declared
|
|
27
|
+
* `pgType` verbatim, so a consumer writing `pgType: 'jsonb'` puts a value in
|
|
28
|
+
* `schema.columns` that type-map never authored and this exact-match sentinel
|
|
29
|
+
* misses. It misses identically on the create path, pre-fix and post-fix --
|
|
30
|
+
* pre-existing and symmetric, not introduced here.
|
|
31
|
+
*/
|
|
32
|
+
export const postgresJsonRule = (value, columnType) => columnType === 'JSONB' && typeof value !== 'string'
|
|
33
|
+
? JSON.stringify(value)
|
|
34
|
+
: value;
|
|
@@ -8,6 +8,8 @@ import { createRecord } from '../manage-record.js';
|
|
|
8
8
|
import { confirm } from '@stonyx/utils/prompt';
|
|
9
9
|
import { readFile } from '@stonyx/utils/file';
|
|
10
10
|
import { getPluralName } from '../plural-registry.js';
|
|
11
|
+
import { convertRow } from '../column-conversion.js';
|
|
12
|
+
import { postgresJsonRule } from './column-rules.js';
|
|
11
13
|
import config from 'stonyx/config';
|
|
12
14
|
import log from 'stonyx/log';
|
|
13
15
|
import path from 'path';
|
|
@@ -57,6 +59,8 @@ interface PostgresDeps {
|
|
|
57
59
|
confirm: typeof confirm;
|
|
58
60
|
readFile: typeof readFile;
|
|
59
61
|
getPluralName: typeof getPluralName;
|
|
62
|
+
convertRow: typeof convertRow;
|
|
63
|
+
postgresJsonRule: typeof postgresJsonRule;
|
|
60
64
|
config: typeof config;
|
|
61
65
|
log: typeof log;
|
|
62
66
|
path: typeof path;
|
|
@@ -8,6 +8,8 @@ import { createRecord } from '../manage-record.js';
|
|
|
8
8
|
import { confirm } from '@stonyx/utils/prompt';
|
|
9
9
|
import { readFile } from '@stonyx/utils/file';
|
|
10
10
|
import { getPluralName } from '../plural-registry.js';
|
|
11
|
+
import { convertRow } from '../column-conversion.js';
|
|
12
|
+
import { postgresJsonRule } from './column-rules.js';
|
|
11
13
|
import { isDbError } from '../utils.js';
|
|
12
14
|
import config from 'stonyx/config';
|
|
13
15
|
import log from 'stonyx/log';
|
|
@@ -18,7 +20,8 @@ const defaultDeps = {
|
|
|
18
20
|
introspectModels, introspectViews, getTopologicalOrder, schemasToSnapshot,
|
|
19
21
|
loadLatestSnapshot, detectSchemaDrift,
|
|
20
22
|
buildInsert, buildUpdate, buildDelete, buildSelect, buildVectorSearch, buildHybridSearch,
|
|
21
|
-
createRecord, store, confirm, readFile, getPluralName, config, log, path
|
|
23
|
+
createRecord, store, confirm, readFile, getPluralName, config, log, path,
|
|
24
|
+
convertRow, postgresJsonRule
|
|
22
25
|
};
|
|
23
26
|
export default class PostgresDB {
|
|
24
27
|
/** PostgreSQL extensions to enable on pool init. Subclasses can override. */
|
|
@@ -417,7 +420,9 @@ export default class PostgresDB {
|
|
|
417
420
|
if (isPendingId) {
|
|
418
421
|
delete insertData.id;
|
|
419
422
|
}
|
|
420
|
-
|
|
423
|
+
// Convert the *complete* payload (attributes + id + FKs) in one place (#292).
|
|
424
|
+
const row = this.deps.convertRow(insertData, schema.columns, this.deps.postgresJsonRule);
|
|
425
|
+
const { sql, values } = this.deps.buildInsert(schema.table, row);
|
|
421
426
|
const result = await this.requirePool().query(sql, values);
|
|
422
427
|
// Re-key the record in the store if PostgreSQL generated the ID (via RETURNING)
|
|
423
428
|
if (isPendingId && result.rows.length > 0) {
|
|
@@ -469,7 +474,11 @@ export default class PostgresDB {
|
|
|
469
474
|
return;
|
|
470
475
|
// PostgreSQL doesn't have ON UPDATE CURRENT_TIMESTAMP -- set updated_at manually
|
|
471
476
|
changedData.updated_at = new Date();
|
|
472
|
-
|
|
477
|
+
// Same conversion, same choke point as the create path (#292). Columns
|
|
478
|
+
// appended after the attribute loop (FKs, updated_at) are absent from
|
|
479
|
+
// schema.columns and pass through untouched.
|
|
480
|
+
const row = this.deps.convertRow(changedData, schema.columns, this.deps.postgresJsonRule);
|
|
481
|
+
const { sql, values } = this.deps.buildUpdate(schema.table, id, row);
|
|
473
482
|
await this.requirePool().query(sql, values);
|
|
474
483
|
}
|
|
475
484
|
async _persistDelete(modelName, context) {
|
|
@@ -490,13 +499,11 @@ export default class PostgresDB {
|
|
|
490
499
|
if (data.id !== undefined) {
|
|
491
500
|
row.id = data.id;
|
|
492
501
|
}
|
|
493
|
-
// Attribute columns
|
|
494
|
-
|
|
502
|
+
// Attribute columns. Type conversion is NOT applied here -- it is applied
|
|
503
|
+
// once to the finished payload by the caller, via convertRow (#292).
|
|
504
|
+
for (const col of Object.keys(schema.columns)) {
|
|
495
505
|
if (data[col] !== undefined) {
|
|
496
|
-
|
|
497
|
-
row[col] = pgType === 'JSONB' && typeof data[col] !== 'string'
|
|
498
|
-
? JSON.stringify(data[col])
|
|
499
|
-
: data[col];
|
|
506
|
+
row[col] = data[col];
|
|
500
507
|
}
|
|
501
508
|
}
|
|
502
509
|
// FK columns from relationships
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"stonyx-async",
|
|
5
5
|
"stonyx-module"
|
|
6
6
|
],
|
|
7
|
-
"version": "0.3.2-beta.
|
|
7
|
+
"version": "0.3.2-beta.277",
|
|
8
8
|
"description": "",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"type": "module",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
},
|
|
62
62
|
"homepage": "https://github.com/abofs/stonyx-orm#readme",
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@stonyx/cron": "0.2.1-beta.
|
|
64
|
+
"@stonyx/cron": "0.2.1-beta.146",
|
|
65
65
|
"@stonyx/events": "0.1.1-beta.64",
|
|
66
66
|
"@stonyx/utils": "0.2.3-beta.27",
|
|
67
67
|
"stonyx": "0.2.3-beta.98"
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
|
-
"@stonyx/rest-server": "0.2.1-beta.
|
|
94
|
+
"@stonyx/rest-server": "0.2.1-beta.147",
|
|
95
95
|
"@types/node": "^25.6.0",
|
|
96
96
|
"mysql2": "^3.20.0",
|
|
97
97
|
"pg": "^8.20.0",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared write-path column conversion (#292).
|
|
3
|
+
*
|
|
4
|
+
* This module owns the *iteration* only — the single place where a write
|
|
5
|
+
* payload is walked column-by-column and each value is handed to the driver's
|
|
6
|
+
* own conversion rule. The rules themselves are genuinely disjoint per driver
|
|
7
|
+
* (Postgres stringifies for JSONB; DynamoDB's DocumentClient marshals objects
|
|
8
|
+
* and arrays natively and must NOT be given stringified values) and therefore
|
|
9
|
+
* deliberately live with their drivers rather than being merged here.
|
|
10
|
+
*
|
|
11
|
+
* The duplicated create/update iteration was the drift vector that produced
|
|
12
|
+
* #292: `_recordToRow` converted, `_persistUpdate` did not. Routing both
|
|
13
|
+
* through `convertRow` removes the opportunity for the two to diverge again.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A driver-supplied per-column conversion rule.
|
|
18
|
+
*
|
|
19
|
+
* @param value the raw value about to be bound into the query
|
|
20
|
+
* @param columnType the column's declared type from `schema.columns`, or
|
|
21
|
+
* `undefined` when the column is not an attribute column
|
|
22
|
+
* (e.g. `updated_at`, FK columns and `id`, which are added
|
|
23
|
+
* outside the attribute loop). Rules MUST be a no-op for
|
|
24
|
+
* `undefined`.
|
|
25
|
+
*/
|
|
26
|
+
export type ColumnRule = (value: unknown, columnType: string | undefined) => unknown;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Applies `rule` to every column of a complete write payload.
|
|
30
|
+
*
|
|
31
|
+
* Call this on the *finished* payload immediately before handing it to the
|
|
32
|
+
* query builder — not inside a payload-building loop. Columns appended after
|
|
33
|
+
* such a loop (FKs, `updated_at`) would otherwise bypass conversion, which is
|
|
34
|
+
* the same class of defect #292 reports.
|
|
35
|
+
*/
|
|
36
|
+
export function convertRow(
|
|
37
|
+
row: Record<string, unknown>,
|
|
38
|
+
columns: Record<string, string>,
|
|
39
|
+
rule: ColumnRule
|
|
40
|
+
): Record<string, unknown> {
|
|
41
|
+
const converted: Record<string, unknown> = {};
|
|
42
|
+
|
|
43
|
+
for (const [col, value] of Object.entries(row)) {
|
|
44
|
+
// `Object.hasOwn`, not `columns[col]`: a column named `toString` or
|
|
45
|
+
// `constructor` would otherwise reach the rule with a function as its
|
|
46
|
+
// "declared type". Inert for the shipped Postgres rule (strict equality
|
|
47
|
+
// against 'JSONB'), but `ColumnRule` is the extension point other drivers
|
|
48
|
+
// write against, and the obvious `if (columnType)` rule would invert.
|
|
49
|
+
converted[col] = rule(value, Object.hasOwn(columns, col) ? columns[col] : undefined);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return converted;
|
|
53
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ColumnRule } from '../column-conversion.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PostgreSQL's write-path conversion rule (#292).
|
|
5
|
+
*
|
|
6
|
+
* JSONB columns must receive a JSON *string*. node-pg's `prepareValue`
|
|
7
|
+
* (`pg/lib/utils.js`) checks `Array.isArray` before its object fallback, so a
|
|
8
|
+
* raw array is rendered as a Postgres array literal (`{"1","2","3"}`) rather
|
|
9
|
+
* than JSON. `[]` becomes `{}`, which is valid JSON, so it lands silently
|
|
10
|
+
* corrupted with no error.
|
|
11
|
+
*
|
|
12
|
+
* Two behaviours this rule must keep:
|
|
13
|
+
*
|
|
14
|
+
* - `typeof value === 'string'` passes through untouched. A JSONB column
|
|
15
|
+
* already holding a JSON string (`'{"a":1}'`) must store the object it
|
|
16
|
+
* describes; an unconditional `JSON.stringify` would double-encode it.
|
|
17
|
+
* - An `undefined` column type is a no-op. `updated_at`, `id` and FK columns
|
|
18
|
+
* are added outside the attribute loop and are absent from
|
|
19
|
+
* `schema.columns`.
|
|
20
|
+
*
|
|
21
|
+
* The `'JSONB'` sentinel is the exact literal emitted by
|
|
22
|
+
* `src/postgres/type-map.ts`, the only module that produces the values in
|
|
23
|
+
* `schema.columns`. The two producing functions are `getPgType` and
|
|
24
|
+
* `getVectorType` (`src/postgres/schema-introspector.ts` writes `columns[key]`
|
|
25
|
+
* at exactly three sites, all routed through those two); `mysqlTypeToPg` is not
|
|
26
|
+
* exported and is reachable only from inside `getPgType`.
|
|
27
|
+
*
|
|
28
|
+
* One documented gap: `getPgType` returns a consumer transform's declared
|
|
29
|
+
* `pgType` verbatim, so a consumer writing `pgType: 'jsonb'` puts a value in
|
|
30
|
+
* `schema.columns` that type-map never authored and this exact-match sentinel
|
|
31
|
+
* misses. It misses identically on the create path, pre-fix and post-fix --
|
|
32
|
+
* pre-existing and symmetric, not introduced here.
|
|
33
|
+
*/
|
|
34
|
+
export const postgresJsonRule: ColumnRule = (value, columnType) =>
|
|
35
|
+
columnType === 'JSONB' && typeof value !== 'string'
|
|
36
|
+
? JSON.stringify(value)
|
|
37
|
+
: value;
|
|
@@ -8,6 +8,8 @@ import { createRecord } from '../manage-record.js';
|
|
|
8
8
|
import { confirm } from '@stonyx/utils/prompt';
|
|
9
9
|
import { readFile } from '@stonyx/utils/file';
|
|
10
10
|
import { getPluralName } from '../plural-registry.js';
|
|
11
|
+
import { convertRow } from '../column-conversion.js';
|
|
12
|
+
import { postgresJsonRule } from './column-rules.js';
|
|
11
13
|
import { isDbError } from '../utils.js';
|
|
12
14
|
import config from 'stonyx/config';
|
|
13
15
|
import log from 'stonyx/log';
|
|
@@ -61,6 +63,8 @@ interface PostgresDeps {
|
|
|
61
63
|
confirm: typeof confirm;
|
|
62
64
|
readFile: typeof readFile;
|
|
63
65
|
getPluralName: typeof getPluralName;
|
|
66
|
+
convertRow: typeof convertRow;
|
|
67
|
+
postgresJsonRule: typeof postgresJsonRule;
|
|
64
68
|
config: typeof config;
|
|
65
69
|
log: typeof log;
|
|
66
70
|
path: typeof path;
|
|
@@ -73,7 +77,8 @@ const defaultDeps: PostgresDeps = {
|
|
|
73
77
|
introspectModels, introspectViews, getTopologicalOrder, schemasToSnapshot,
|
|
74
78
|
loadLatestSnapshot, detectSchemaDrift,
|
|
75
79
|
buildInsert, buildUpdate, buildDelete, buildSelect, buildVectorSearch, buildHybridSearch,
|
|
76
|
-
createRecord, store, confirm, readFile, getPluralName, config, log, path
|
|
80
|
+
createRecord, store, confirm, readFile, getPluralName, config, log, path,
|
|
81
|
+
convertRow, postgresJsonRule
|
|
77
82
|
};
|
|
78
83
|
|
|
79
84
|
export default class PostgresDB {
|
|
@@ -535,7 +540,10 @@ export default class PostgresDB {
|
|
|
535
540
|
delete insertData.id;
|
|
536
541
|
}
|
|
537
542
|
|
|
538
|
-
|
|
543
|
+
// Convert the *complete* payload (attributes + id + FKs) in one place (#292).
|
|
544
|
+
const row = this.deps.convertRow(insertData, schema.columns, this.deps.postgresJsonRule);
|
|
545
|
+
|
|
546
|
+
const { sql, values } = this.deps.buildInsert(schema.table, row);
|
|
539
547
|
|
|
540
548
|
const result = await this.requirePool().query(sql, values);
|
|
541
549
|
|
|
@@ -600,7 +608,12 @@ export default class PostgresDB {
|
|
|
600
608
|
// PostgreSQL doesn't have ON UPDATE CURRENT_TIMESTAMP -- set updated_at manually
|
|
601
609
|
changedData.updated_at = new Date();
|
|
602
610
|
|
|
603
|
-
|
|
611
|
+
// Same conversion, same choke point as the create path (#292). Columns
|
|
612
|
+
// appended after the attribute loop (FKs, updated_at) are absent from
|
|
613
|
+
// schema.columns and pass through untouched.
|
|
614
|
+
const row = this.deps.convertRow(changedData, schema.columns, this.deps.postgresJsonRule);
|
|
615
|
+
|
|
616
|
+
const { sql, values } = this.deps.buildUpdate(schema.table, id, row);
|
|
604
617
|
await this.requirePool().query(sql, values);
|
|
605
618
|
}
|
|
606
619
|
|
|
@@ -626,13 +639,11 @@ export default class PostgresDB {
|
|
|
626
639
|
row.id = data.id;
|
|
627
640
|
}
|
|
628
641
|
|
|
629
|
-
// Attribute columns
|
|
630
|
-
|
|
642
|
+
// Attribute columns. Type conversion is NOT applied here -- it is applied
|
|
643
|
+
// once to the finished payload by the caller, via convertRow (#292).
|
|
644
|
+
for (const col of Object.keys(schema.columns)) {
|
|
631
645
|
if (data[col] !== undefined) {
|
|
632
|
-
|
|
633
|
-
row[col] = pgType === 'JSONB' && typeof data[col] !== 'string'
|
|
634
|
-
? JSON.stringify(data[col])
|
|
635
|
-
: data[col];
|
|
646
|
+
row[col] = data[col];
|
|
636
647
|
}
|
|
637
648
|
}
|
|
638
649
|
|