fastify-auto-sqlapi 0.0.4 → 0.1.1
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/AGENTS.md +15 -2
- package/AGENTS_BACKEND.md +160 -32
- package/AGENTS_FRONTEND.md +220 -49
- package/README.md +147 -27
- package/dist/bin/generate-schema.js +12 -29
- package/dist/bin/generate-tables.js +10 -33
- package/dist/bin/utils.d.ts +29 -1
- package/dist/bin/utils.d.ts.map +1 -1
- package/dist/bin/utils.js +71 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/cli/load-dependency.d.ts +8 -0
- package/dist/lib/cli/load-dependency.d.ts.map +1 -0
- package/dist/lib/cli/load-dependency.js +21 -0
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
- package/dist/lib/cli/mysql-introspect.js +2 -11
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
- package/dist/lib/cli/pg-introspect.js +2 -17
- package/dist/lib/cli/schema-codegen.d.ts +1 -1
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
- package/dist/lib/cli/schema-codegen.js +11 -4
- package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
- package/dist/lib/cli/tables-codegen.js +4 -3
- package/dist/lib/db.d.ts +1 -0
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +33 -49
- package/dist/lib/dialect.d.ts +17 -0
- package/dist/lib/dialect.d.ts.map +1 -1
- package/dist/lib/dialect.js +32 -0
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.js +26 -50
- package/dist/lib/engine/rest/delete.d.ts.map +1 -1
- package/dist/lib/engine/rest/delete.js +5 -10
- package/dist/lib/engine/rest/get.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.js +4 -6
- package/dist/lib/engine/rest/insert.d.ts.map +1 -1
- package/dist/lib/engine/rest/insert.js +20 -39
- package/dist/lib/engine/rest/update.d.ts.map +1 -1
- package/dist/lib/engine/rest/update.js +35 -67
- package/dist/lib/engine/search/search.d.ts.map +1 -1
- package/dist/lib/engine/search/search.js +650 -130
- package/dist/lib/engine/write-helpers.d.ts +39 -2
- package/dist/lib/engine/write-helpers.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.js +58 -15
- package/dist/lib/errors.d.ts +11 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +12 -0
- package/dist/lib/naming.d.ts +11 -2
- package/dist/lib/naming.d.ts.map +1 -1
- package/dist/lib/naming.js +33 -5
- package/dist/lib/schema/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/schema/bulk-upsert.js +9 -25
- package/dist/lib/schema/helpers.d.ts +28 -1
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +38 -0
- package/dist/lib/schema/insert.d.ts.map +1 -1
- package/dist/lib/schema/insert.js +9 -19
- package/dist/lib/schema/search.d.ts.map +1 -1
- package/dist/lib/schema/search.js +147 -53
- package/dist/lib/schema/update.d.ts.map +1 -1
- package/dist/lib/schema/update.js +9 -25
- package/dist/lib/sql-api.d.ts +6 -6
- package/dist/lib/sql-api.d.ts.map +1 -1
- package/dist/lib/sql-api.js +8 -8
- package/dist/lib/table-helpers.d.ts +6 -1
- package/dist/lib/table-helpers.d.ts.map +1 -1
- package/dist/lib/table-helpers.js +51 -3
- package/dist/lib/tenant.d.ts +23 -0
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +57 -9
- package/dist/routes/auto/bulk-delete.routes.d.ts.map +1 -1
- package/dist/routes/auto/bulk-delete.routes.js +17 -23
- package/dist/routes/auto/bulk-upsert.routes.d.ts.map +1 -1
- package/dist/routes/auto/bulk-upsert.routes.js +16 -22
- package/dist/routes/auto/delete.routes.d.ts.map +1 -1
- package/dist/routes/auto/delete.routes.js +18 -24
- package/dist/routes/auto/get.routes.d.ts.map +1 -1
- package/dist/routes/auto/get.routes.js +16 -23
- package/dist/routes/auto/insert.routes.d.ts.map +1 -1
- package/dist/routes/auto/insert.routes.js +19 -26
- package/dist/routes/auto/route-helpers.d.ts +27 -2
- package/dist/routes/auto/route-helpers.d.ts.map +1 -1
- package/dist/routes/auto/route-helpers.js +39 -1
- package/dist/routes/auto/search.routes.d.ts.map +1 -1
- package/dist/routes/auto/search.routes.js +47 -47
- package/dist/routes/auto/update.routes.d.ts.map +1 -1
- package/dist/routes/auto/update.routes.js +20 -27
- package/dist/types/cli.d.ts +22 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +1 -0
- package/dist/types/computed.d.ts +29 -0
- package/dist/types/computed.d.ts.map +1 -0
- package/dist/types/computed.js +1 -0
- package/dist/types/conditions.d.ts +16 -0
- package/dist/types/conditions.d.ts.map +1 -0
- package/dist/types/conditions.js +1 -0
- package/dist/types/db.d.ts +22 -0
- package/dist/types/db.d.ts.map +1 -0
- package/dist/types/db.js +1 -0
- package/dist/types/dml.d.ts +64 -0
- package/dist/types/dml.d.ts.map +1 -0
- package/dist/types/dml.js +1 -0
- package/dist/types/join.d.ts +44 -0
- package/dist/types/join.d.ts.map +1 -0
- package/dist/types/join.js +1 -0
- package/dist/types/plugin.d.ts +20 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +1 -0
- package/dist/types/schema.d.ts +10 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +1 -0
- package/dist/types/search.d.ts +45 -0
- package/dist/types/search.d.ts.map +1 -0
- package/dist/types/search.js +1 -0
- package/dist/types/table.d.ts +52 -0
- package/dist/types/table.d.ts.map +1 -0
- package/dist/types/table.js +7 -0
- package/dist/types/tenant.d.ts +19 -0
- package/dist/types/tenant.d.ts.map +1 -0
- package/dist/types/tenant.js +1 -0
- package/dist/types/validation.d.ts +21 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +1 -0
- package/dist/types.d.ts +12 -258
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -7
- package/package.json +8 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadOptionalDependency } from './load-dependency.js';
|
|
2
2
|
export function buildMysqlConnectionConfig() {
|
|
3
3
|
if (process.env.DATABASE_URL) {
|
|
4
4
|
const url = new URL(process.env.DATABASE_URL);
|
|
@@ -66,17 +66,8 @@ function mapMysqlType(dataType) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
export async function introspectMysqlTables(connectionConfig, schema) {
|
|
69
|
-
// Dynamic import: mysql2 is optional peer dependency.
|
|
70
|
-
// Use createRequire from cwd so it works with npm link.
|
|
71
69
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const require = createRequire(process.cwd() + '/noop.js');
|
|
75
|
-
mysql2 = require('mysql2/promise');
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
throw new Error('mysql2 is required for MySQL/MariaDB introspection. Install it with: npm install mysql2');
|
|
79
|
-
}
|
|
70
|
+
const mysql2 = loadOptionalDependency('mysql2/promise', 'npm install mysql2');
|
|
80
71
|
const connection = await mysql2.createConnection(connectionConfig);
|
|
81
72
|
try {
|
|
82
73
|
const [rows] = await connection.query(`SELECT table_name, column_name, data_type, column_default, is_nullable
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/pg-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wBAAgB,qBAAqB,IAAI,MAAM,CAY9C;AAED,wBAAsB,gBAAgB,CACpC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"pg-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/pg-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wBAAgB,qBAAqB,IAAI,MAAM,CAY9C;AAED,wBAAsB,gBAAgB,CACpC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CAqBvB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadOptionalDependency } from './load-dependency.js';
|
|
2
2
|
export function buildConnectionString() {
|
|
3
3
|
if (process.env.DATABASE_URL) {
|
|
4
4
|
return process.env.DATABASE_URL;
|
|
@@ -11,23 +11,8 @@ export function buildConnectionString() {
|
|
|
11
11
|
return `postgres://${user}:${password}@${host}:${port}/${db}`;
|
|
12
12
|
}
|
|
13
13
|
export async function introspectTables(connectionString, schema) {
|
|
14
|
-
// Dynamic require from cwd so it works with npm link
|
|
15
14
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const require = createRequire(process.cwd() + '/noop.js');
|
|
19
|
-
pg = require('pg');
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
try {
|
|
23
|
-
// Fallback: resolve from the package's own node_modules
|
|
24
|
-
const require = createRequire(import.meta.url);
|
|
25
|
-
pg = require('pg');
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
throw new Error('pg is required for PostgreSQL introspection. Install it with: npm install pg');
|
|
29
|
-
}
|
|
30
|
-
}
|
|
15
|
+
const pg = loadOptionalDependency('pg', 'npm install pg');
|
|
31
16
|
const client = new pg.Client({ connectionString });
|
|
32
17
|
try {
|
|
33
18
|
await client.connect();
|
|
@@ -4,5 +4,5 @@ export declare function convertColType(udtName: string, columnInfo: {
|
|
|
4
4
|
is_nullable: string;
|
|
5
5
|
}): string;
|
|
6
6
|
export declare function buildTableMap(rows: ColumnInfo[]): TableMap;
|
|
7
|
-
export declare function generateSchemaFile(schemaName: string, tableName: string, fields: Record<string, string>): string;
|
|
7
|
+
export declare function generateSchemaFile(schemaName: string, tableName: string, fields: Record<string, string>, colMap: Record<string, string>): string;
|
|
8
8
|
//# sourceMappingURL=schema-codegen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/schema-codegen.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE3D,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE;IAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACjE,MAAM,CAoDR;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,QAAQ,
|
|
1
|
+
{"version":3,"file":"schema-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/schema-codegen.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE3D,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE;IAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACjE,MAAM,CAoDR;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,QAAQ,CAqB1D;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,MAAM,CA2CR"}
|
|
@@ -57,20 +57,26 @@ export function buildTableMap(rows) {
|
|
|
57
57
|
acc[schemaName] = {
|
|
58
58
|
name: row.table_name,
|
|
59
59
|
fields: {},
|
|
60
|
+
colMap: {},
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
+
const fieldName = toCamelCase(row.column_name);
|
|
64
|
+
acc[schemaName].fields[fieldName] = convertColType(row.udt_name, row);
|
|
65
|
+
acc[schemaName].colMap[fieldName] = row.column_name;
|
|
63
66
|
return acc;
|
|
64
67
|
}, {});
|
|
65
68
|
}
|
|
66
|
-
export function generateSchemaFile(schemaName, tableName, fields) {
|
|
69
|
+
export function generateSchemaFile(schemaName, tableName, fields, colMap) {
|
|
67
70
|
const fieldsStr = JSON.stringify(fields, null, 2).replace(/"(.*?)"\s*:\s*"Type(.*?)"/g, '$1: Type$2');
|
|
71
|
+
const colMapStr = JSON.stringify(colMap, null, 2);
|
|
68
72
|
return `// Auto-generated by fastify-auto-sqlapi. Do not edit.
|
|
69
|
-
import { Type
|
|
73
|
+
import { Type } from "fastify-auto-sqlapi";
|
|
70
74
|
import type { Static } from "fastify-auto-sqlapi";
|
|
71
75
|
|
|
72
76
|
const _Schema = ${fieldsStr};
|
|
73
77
|
|
|
78
|
+
const _colMap: Record<string, string> = ${colMapStr};
|
|
79
|
+
|
|
74
80
|
const validation = Type.Object(_Schema, { $id: "${schemaName}" });
|
|
75
81
|
const partialValidation = Type.Object(
|
|
76
82
|
Object.fromEntries(
|
|
@@ -83,7 +89,8 @@ const partialValidation = Type.Object(
|
|
|
83
89
|
);
|
|
84
90
|
|
|
85
91
|
export const Schema = {
|
|
86
|
-
col: (f: keyof typeof _Schema) =>
|
|
92
|
+
col: (f: keyof typeof _Schema) => _colMap[f as string] ?? f,
|
|
93
|
+
colMap: _colMap,
|
|
87
94
|
fields: _Schema,
|
|
88
95
|
validation,
|
|
89
96
|
tableName: "${tableName}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/tables-codegen.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA2BpE;AAgBD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAwB3E;AAID,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"tables-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/tables-codegen.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA2BpE;AAgBD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAwB3E;AAID,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,MAAM,CAwHhG;AAID,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAqBrE;AAID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CA6FlE"}
|
|
@@ -98,6 +98,7 @@ export function generateSingleTableFile(schema, allSchemas) {
|
|
|
98
98
|
const lines = [];
|
|
99
99
|
// Imports
|
|
100
100
|
lines.push(`import {defineTable, exportTableInfo, Type} from 'fastify-auto-sqlapi';`);
|
|
101
|
+
lines.push(`import type {ValidationError} from 'fastify-auto-sqlapi';`);
|
|
101
102
|
lines.push(`import {${schema.schemaName} as Schema} from '../schemas/${schema.schemaName}';`);
|
|
102
103
|
// Commented imports for related schemas
|
|
103
104
|
for (const relSchema of relatedSchemas) {
|
|
@@ -157,7 +158,7 @@ export function generateSingleTableFile(schema, allSchemas) {
|
|
|
157
158
|
// validate: use first non-PK field for example
|
|
158
159
|
const exampleField = schema.fields.find(f => f !== pk) || schema.fields[0];
|
|
159
160
|
lines.push(` validate: async (db, req, main, secondaries) => {`);
|
|
160
|
-
lines.push(` const errors = [];`);
|
|
161
|
+
lines.push(` const errors: ValidationError[] = [];`);
|
|
161
162
|
lines.push(` // if (!main.${exampleField}) errors.push(['${exampleField}', 'required']);`);
|
|
162
163
|
lines.push(` return errors;`);
|
|
163
164
|
lines.push(` },`);
|
|
@@ -209,7 +210,7 @@ export function generateTablesFile(schemas) {
|
|
|
209
210
|
const lines = [];
|
|
210
211
|
// Imports
|
|
211
212
|
lines.push(`import {defineTable, exportTableInfo, Type} from 'fastify-auto-sqlapi';`);
|
|
212
|
-
lines.push(`import type { DbTables } from 'fastify-auto-sqlapi';`);
|
|
213
|
+
lines.push(`import type { DbTables, ValidationError } from 'fastify-auto-sqlapi';`);
|
|
213
214
|
lines.push(``);
|
|
214
215
|
// Schema imports
|
|
215
216
|
for (const schema of schemas) {
|
|
@@ -247,7 +248,7 @@ export function generateTablesFile(schemas) {
|
|
|
247
248
|
lines.push(` // schemaOverrides: {},`);
|
|
248
249
|
const exampleFieldLegacy = schema.fields.find(f => f !== pk) || schema.fields[0];
|
|
249
250
|
lines.push(` validate: async (db, req, main, secondaries) => {`);
|
|
250
|
-
lines.push(` const errors = [];`);
|
|
251
|
+
lines.push(` const errors: ValidationError[] = [];`);
|
|
251
252
|
lines.push(` // if (!main.${exampleFieldLegacy}) errors.push(['${exampleFieldLegacy}', 'required']);`);
|
|
252
253
|
lines.push(` return errors;`);
|
|
253
254
|
lines.push(` },`);
|
package/dist/lib/db.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare class QueryClient {
|
|
|
13
13
|
get qi(): (id: string) => string;
|
|
14
14
|
get ph(): (i: number) => string;
|
|
15
15
|
get dialectName(): string;
|
|
16
|
+
get dateTrunc(): SqlDialect['dateTrunc'];
|
|
16
17
|
setDebug(mode: boolean): void;
|
|
17
18
|
expression(value: string): Expression;
|
|
18
19
|
query<T = Record<string, unknown>>(text: string, values?: unknown[]): Promise<SqlResult<T>>;
|
package/dist/lib/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAQ/C,sCAAsC;AACtC,eAAO,MAAM,WAAW,GAAI,GAAG,MAAM,WAA0B,CAAC;AAEhE,qBAAa,WAAW;;IACtB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,UAAU;IAKnD,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAE/B;IAED,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAE9B;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,SAAS,IAAI,UAAU,CAAC,WAAW,CAAC,CAEvC;IAED,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI7B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU;IAIrC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAmGlB,MAAM,CACV,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,EAChB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAgB7B,cAAc,CAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,EAChB,YAAY,EAAE,MAAM,EAAE,EACtB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAkB7B,UAAU,CACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,QAAQ,EAAE,EACnB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,SAAS,SAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAoB/B,kBAAkB,CACtB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,QAAQ,EAAE,EACnB,YAAY,EAAE,MAAM,EAAE,EACtB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,SAAS,SAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAuB/B,MAAM,CACV,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,EAChB,KAAK,EAAE,QAAQ,EACf,cAAc,CAAC,EAAE,gBAAgB,GAChC,OAAO,CAAC,MAAM,CAAC;IAuBZ,MAAM,CACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,QAAQ,GACd,OAAO,CAAC,MAAM,CAAC;IAYZ,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACxC,SAAS,EACT,OAAa,EACb,KAAK,EACL,MAAM,EACN,KAAY,EACZ,OAAY,EACZ,KAAU,EACV,QAAgB,GACjB,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;CAchC;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,WAAW,CAGb"}
|
package/dist/lib/db.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Expression } from 'node-condition-builder';
|
|
2
2
|
import { getDialect } from './dialect.js';
|
|
3
|
+
// Default chunk size for bulk INSERT/UPSERT. Picked to keep parameter count well under
|
|
4
|
+
// PostgreSQL's hard limit of 65535 bind parameters per statement (≈ 130 columns × 500 rows).
|
|
5
|
+
// Override per-call via the `chunkSize` argument when working with extremely wide tables.
|
|
3
6
|
const DEFAULT_CHUNK_SIZE = 500;
|
|
4
7
|
/** @deprecated Use db.qi() instead */
|
|
5
8
|
export const escapeIdent = (f) => f.replace(/"/g, '""');
|
|
@@ -20,6 +23,9 @@ export class QueryClient {
|
|
|
20
23
|
get dialectName() {
|
|
21
24
|
return this.dialect.name;
|
|
22
25
|
}
|
|
26
|
+
get dateTrunc() {
|
|
27
|
+
return this.dialect.dateTrunc.bind(this.dialect);
|
|
28
|
+
}
|
|
23
29
|
setDebug(mode) {
|
|
24
30
|
this.debug = mode;
|
|
25
31
|
}
|
|
@@ -73,26 +79,14 @@ export class QueryClient {
|
|
|
73
79
|
}
|
|
74
80
|
return { values, rows: rowPlaceholders.join(', ') };
|
|
75
81
|
}
|
|
76
|
-
// Build the update SET for upsert: dialect-specific
|
|
77
|
-
#upsertUpdateSet(fields, conflictKeys) {
|
|
78
|
-
const updateCols = fields.filter((f) => !conflictKeys.includes(f));
|
|
79
|
-
if (!updateCols.length)
|
|
80
|
-
return '';
|
|
81
|
-
if (this.dialect.name === 'postgres') {
|
|
82
|
-
return updateCols
|
|
83
|
-
.map((f) => `${this.dialect.qi(f)} = EXCLUDED.${this.dialect.qi(f)}`)
|
|
84
|
-
.join(', ');
|
|
85
|
-
}
|
|
86
|
-
// MySQL/MariaDB: VALUES(col) syntax
|
|
87
|
-
return updateCols
|
|
88
|
-
.map((f) => `${this.dialect.qi(f)} = VALUES(${this.dialect.qi(f)})`)
|
|
89
|
-
.join(', ');
|
|
90
|
-
}
|
|
91
82
|
#quotedPkCols(pkCol) {
|
|
92
83
|
const cols = Array.isArray(pkCol) ? pkCol : [pkCol];
|
|
93
84
|
return cols.map((c) => this.dialect.qi(c)).join(', ');
|
|
94
85
|
}
|
|
95
|
-
#
|
|
86
|
+
#insertHead(table, fields) {
|
|
87
|
+
return `INSERT INTO ${this.dialect.qi(table)} (${this.#q(fields)})`;
|
|
88
|
+
}
|
|
89
|
+
#mysqlPkRow(pkCol, record, insertId) {
|
|
96
90
|
const cols = Array.isArray(pkCol) ? pkCol : [pkCol];
|
|
97
91
|
const result = {};
|
|
98
92
|
for (const col of cols) {
|
|
@@ -106,53 +100,51 @@ export class QueryClient {
|
|
|
106
100
|
return { [pkCol]: insertId };
|
|
107
101
|
return result;
|
|
108
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Single source of truth for "extract PK rows from an insert/upsert result". When the
|
|
105
|
+
* dialect supports RETURNING (postgres/mariadb), returns `result.rows` directly. Otherwise
|
|
106
|
+
* (mysql) synthesizes PK rows from `insertId` + the input records (assumes
|
|
107
|
+
* `auto_increment_increment=1` for multi-row inserts).
|
|
108
|
+
*/
|
|
109
|
+
#extractPkRows(result, pkCol, inputs) {
|
|
110
|
+
if (this.dialect.supportsReturning)
|
|
111
|
+
return result.rows;
|
|
112
|
+
const firstId = result.insertId;
|
|
113
|
+
return inputs.map((rec, j) => this.#mysqlPkRow(pkCol, rec, typeof firstId === 'number' ? firstId + j : undefined));
|
|
114
|
+
}
|
|
109
115
|
async insert(table, record, pkCol) {
|
|
110
116
|
const values = [];
|
|
111
117
|
const { fields, placeholders } = this.#params(record, values);
|
|
112
118
|
if (!fields.length)
|
|
113
119
|
throw new Error('Cannot execute empty insert');
|
|
114
120
|
const returning = this.dialect.returningPk(this.#quotedPkCols(pkCol));
|
|
115
|
-
const result = await this.query(
|
|
121
|
+
const result = await this.query(`${this.#insertHead(table, fields)}
|
|
116
122
|
VALUES (${placeholders.join(', ')})${returning}`, values);
|
|
117
|
-
|
|
118
|
-
return result.rows[0];
|
|
119
|
-
}
|
|
120
|
-
return this.#mysqlPkResult(pkCol, record, result.insertId);
|
|
123
|
+
return this.#extractPkRows(result, pkCol, [record])[0];
|
|
121
124
|
}
|
|
122
125
|
async insertOrUpdate(table, record, conflictKeys, pkCol) {
|
|
123
126
|
const values = [];
|
|
124
127
|
const { fields, placeholders } = this.#params(record, values);
|
|
125
128
|
if (!fields.length)
|
|
126
129
|
throw new Error('Cannot execute empty insert');
|
|
127
|
-
const
|
|
128
|
-
const upsertClause = this.dialect.upsertSql(this.#q(conflictKeys), updateSet);
|
|
130
|
+
const upsertClause = this.dialect.upsertSql(this.#q(conflictKeys), this.dialect.upsertUpdateSet(fields, conflictKeys));
|
|
129
131
|
const returning = this.dialect.returningPk(this.#quotedPkCols(pkCol));
|
|
130
|
-
const result = await this.query(
|
|
132
|
+
const result = await this.query(`${this.#insertHead(table, fields)}
|
|
131
133
|
VALUES (${placeholders.join(', ')})
|
|
132
134
|
${upsertClause}${returning}`, values);
|
|
133
|
-
|
|
134
|
-
return result.rows[0];
|
|
135
|
-
}
|
|
136
|
-
return this.#mysqlPkResult(pkCol, record, result.insertId);
|
|
135
|
+
return this.#extractPkRows(result, pkCol, [record])[0];
|
|
137
136
|
}
|
|
138
137
|
async bulkInsert(table, records, pkCol, chunkSize = DEFAULT_CHUNK_SIZE) {
|
|
139
138
|
if (!records.length)
|
|
140
139
|
return [];
|
|
141
140
|
const fields = Object.keys(records[0]);
|
|
142
|
-
const results = [];
|
|
143
141
|
const returning = this.dialect.returningPk(this.#quotedPkCols(pkCol));
|
|
142
|
+
const results = [];
|
|
144
143
|
for (let i = 0; i < records.length; i += chunkSize) {
|
|
145
144
|
const chunk = records.slice(i, i + chunkSize);
|
|
146
145
|
const { values, rows } = this.#bulkValues(fields, chunk);
|
|
147
|
-
const result = await this.query(
|
|
148
|
-
|
|
149
|
-
results.push(...result.rows);
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
for (const rec of chunk) {
|
|
153
|
-
results.push(this.#mysqlPkResult(pkCol, rec, result.insertId));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
146
|
+
const result = await this.query(`${this.#insertHead(table, fields)} VALUES ${rows}${returning}`, values);
|
|
147
|
+
results.push(...this.#extractPkRows(result, pkCol, chunk));
|
|
156
148
|
}
|
|
157
149
|
return results;
|
|
158
150
|
}
|
|
@@ -160,24 +152,16 @@ export class QueryClient {
|
|
|
160
152
|
if (!records.length)
|
|
161
153
|
return [];
|
|
162
154
|
const fields = Object.keys(records[0]);
|
|
163
|
-
const
|
|
164
|
-
const upsertClause = this.dialect.upsertSql(this.#q(conflictKeys), updateSet);
|
|
155
|
+
const upsertClause = this.dialect.upsertSql(this.#q(conflictKeys), this.dialect.upsertUpdateSet(fields, conflictKeys));
|
|
165
156
|
const returning = this.dialect.returningPk(this.#quotedPkCols(pkCol));
|
|
166
157
|
const results = [];
|
|
167
158
|
for (let i = 0; i < records.length; i += chunkSize) {
|
|
168
159
|
const chunk = records.slice(i, i + chunkSize);
|
|
169
160
|
const { values, rows } = this.#bulkValues(fields, chunk);
|
|
170
|
-
const result = await this.query(
|
|
161
|
+
const result = await this.query(`${this.#insertHead(table, fields)}
|
|
171
162
|
VALUES ${rows}
|
|
172
163
|
${upsertClause}${returning}`, values);
|
|
173
|
-
|
|
174
|
-
results.push(...result.rows);
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
for (const rec of chunk) {
|
|
178
|
-
results.push(this.#mysqlPkResult(pkCol, rec, result.insertId));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
164
|
+
results.push(...this.#extractPkRows(result, pkCol, chunk));
|
|
181
165
|
}
|
|
182
166
|
return results;
|
|
183
167
|
}
|
package/dist/lib/dialect.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type DialectName = 'postgres' | 'mysql' | 'mariadb';
|
|
2
|
+
export type TruncateUnit = 'year' | 'quarter' | 'month' | 'day' | 'hour';
|
|
2
3
|
export interface SqlDialect {
|
|
3
4
|
name: DialectName;
|
|
4
5
|
/** Quote identifier: "id" (postgres) or `id` (mysql/mariadb) */
|
|
@@ -12,6 +13,22 @@ export interface SqlDialect {
|
|
|
12
13
|
upsertSql(quotedConflictKeys: string, quotedUpdateSet: string): string;
|
|
13
14
|
/** RETURNING pk_col suffix or empty string */
|
|
14
15
|
returningPk(quotedPkCol: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Build the SET body for an upsert: the column list (separated by ', ') that the dialect
|
|
18
|
+
* uses to copy "incoming row" values into a conflicting row.
|
|
19
|
+
* - postgres: `"col" = EXCLUDED."col"`
|
|
20
|
+
* - mysql/mariadb: `` `col` = VALUES(`col`) ``
|
|
21
|
+
* Returns '' when there is nothing to update (i.e. all `fields` are conflict keys).
|
|
22
|
+
*/
|
|
23
|
+
upsertUpdateSet(fields: string[], conflictKeys: string[]): string;
|
|
24
|
+
/**
|
|
25
|
+
* Truncate a timestamp/date column to the start of the given unit and
|
|
26
|
+
* return it as an ISO-style string. The output is always a STRING:
|
|
27
|
+
* - year/quarter/month/day → 'YYYY-MM-DD' (e.g. '2026-04-01')
|
|
28
|
+
* - hour → 'YYYY-MM-DDTHH:00:00'
|
|
29
|
+
* `qualifiedCol` is a fully-qualified, already-quoted column expression.
|
|
30
|
+
*/
|
|
31
|
+
dateTrunc(unit: TruncateUnit, qualifiedCol: string): string;
|
|
15
32
|
}
|
|
16
33
|
export declare function getDialect(name: DialectName): SqlDialect;
|
|
17
34
|
//# sourceMappingURL=dialect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../../src/lib/dialect.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,gEAAgE;IAChE,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,sDAAsD;IACtD,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,oCAAoC;IACpC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC;IAChC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,4CAA4C;IAC5C,SAAS,CAAC,kBAAkB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;IACvE,8CAA8C;IAC9C,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../../src/lib/dialect.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3D,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAEzE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,gEAAgE;IAChE,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,sDAAsD;IACtD,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,oCAAoC;IACpC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC;IAChC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,4CAA4C;IAC5C,SAAS,CAAC,kBAAkB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;IACvE,8CAA8C;IAC9C,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IACzC;;;;;;OAMG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAClE;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7D;AAqFD,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAIxD"}
|
package/dist/lib/dialect.js
CHANGED
|
@@ -9,10 +9,36 @@ const PostgresDialect = {
|
|
|
9
9
|
return `ON CONFLICT (${quotedConflictKeys}) DO NOTHING`;
|
|
10
10
|
return `ON CONFLICT (${quotedConflictKeys}) DO UPDATE SET ${quotedUpdateSet}`;
|
|
11
11
|
},
|
|
12
|
+
upsertUpdateSet(fields, conflictKeys) {
|
|
13
|
+
const updateCols = fields.filter((f) => !conflictKeys.includes(f));
|
|
14
|
+
if (!updateCols.length)
|
|
15
|
+
return '';
|
|
16
|
+
return updateCols.map((f) => `${this.qi(f)} = EXCLUDED.${this.qi(f)}`).join(', ');
|
|
17
|
+
},
|
|
12
18
|
returningPk(quotedPkCol) {
|
|
13
19
|
return ` RETURNING ${quotedPkCol}`;
|
|
14
20
|
},
|
|
21
|
+
dateTrunc(unit, col) {
|
|
22
|
+
const fmt = unit === 'hour' ? `'YYYY-MM-DD"T"HH24:00:00'` : `'YYYY-MM-DD'`;
|
|
23
|
+
// DATE_TRUNC accepts a literal unit string, never user input — `unit` is whitelisted upstream.
|
|
24
|
+
return `TO_CHAR(DATE_TRUNC('${unit}', ${col}), ${fmt})`;
|
|
25
|
+
},
|
|
15
26
|
};
|
|
27
|
+
function mysqlDateTrunc(unit, col) {
|
|
28
|
+
switch (unit) {
|
|
29
|
+
case 'year': return `DATE_FORMAT(${col}, '%Y-01-01')`;
|
|
30
|
+
case 'quarter': return `CONCAT(YEAR(${col}), '-', LPAD((QUARTER(${col})-1)*3+1, 2, '0'), '-01')`;
|
|
31
|
+
case 'month': return `DATE_FORMAT(${col}, '%Y-%m-01')`;
|
|
32
|
+
case 'day': return `DATE_FORMAT(${col}, '%Y-%m-%d')`;
|
|
33
|
+
case 'hour': return `DATE_FORMAT(${col}, '%Y-%m-%dT%H:00:00')`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function mysqlUpsertUpdateSet(fields, conflictKeys) {
|
|
37
|
+
const updateCols = fields.filter((f) => !conflictKeys.includes(f));
|
|
38
|
+
if (!updateCols.length)
|
|
39
|
+
return '';
|
|
40
|
+
return updateCols.map((f) => `${this.qi(f)} = VALUES(${this.qi(f)})`).join(', ');
|
|
41
|
+
}
|
|
16
42
|
const MysqlDialect = {
|
|
17
43
|
name: 'mysql',
|
|
18
44
|
qi: (id) => `\`${id.replace(/`/g, '``')}\``,
|
|
@@ -20,13 +46,17 @@ const MysqlDialect = {
|
|
|
20
46
|
cbDialect: 'mysql',
|
|
21
47
|
supportsReturning: false,
|
|
22
48
|
upsertSql(_quotedConflictKeys, quotedUpdateSet) {
|
|
49
|
+
// MySQL requires at least one assignment in ON DUPLICATE KEY UPDATE; when there's nothing
|
|
50
|
+
// user-meaningful to update, assign the first conflict key to itself (no-op).
|
|
23
51
|
if (!quotedUpdateSet)
|
|
24
52
|
return `ON DUPLICATE KEY UPDATE ${_quotedConflictKeys.split(', ')[0]} = ${_quotedConflictKeys.split(', ')[0]}`;
|
|
25
53
|
return `ON DUPLICATE KEY UPDATE ${quotedUpdateSet}`;
|
|
26
54
|
},
|
|
55
|
+
upsertUpdateSet: mysqlUpsertUpdateSet,
|
|
27
56
|
returningPk() {
|
|
28
57
|
return '';
|
|
29
58
|
},
|
|
59
|
+
dateTrunc: mysqlDateTrunc,
|
|
30
60
|
};
|
|
31
61
|
const MariadbDialect = {
|
|
32
62
|
name: 'mariadb',
|
|
@@ -39,9 +69,11 @@ const MariadbDialect = {
|
|
|
39
69
|
return `ON DUPLICATE KEY UPDATE ${_quotedConflictKeys.split(', ')[0]} = ${_quotedConflictKeys.split(', ')[0]}`;
|
|
40
70
|
return `ON DUPLICATE KEY UPDATE ${quotedUpdateSet}`;
|
|
41
71
|
},
|
|
72
|
+
upsertUpdateSet: mysqlUpsertUpdateSet,
|
|
42
73
|
returningPk(quotedPkCol) {
|
|
43
74
|
return ` RETURNING ${quotedPkCol}`;
|
|
44
75
|
},
|
|
76
|
+
dateTrunc: mysqlDateTrunc,
|
|
45
77
|
};
|
|
46
78
|
const dialects = {
|
|
47
79
|
postgres: PostgresDialect,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-upsert.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/bulk/bulk-upsert.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bulk-upsert.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/bulk/bulk-upsert.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAEjB,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAkF5F"}
|
|
@@ -1,80 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { runValidation, runBulkValidation } from '../validate.js';
|
|
1
|
+
import { processSecondaries, processDeletions, prepareInsertRecord } from '../write-helpers.js';
|
|
2
|
+
import { enforceTenantOnWrites } from '../../tenant.js';
|
|
3
|
+
import { runBulkValidation } from '../validate.js';
|
|
5
4
|
import { primaryAsString } from '../../../types.js';
|
|
6
5
|
export async function bulkUpsertEngine(params) {
|
|
7
6
|
const { db, tableConf, dbTables, request, items, tenant } = params;
|
|
8
7
|
if (!items.length)
|
|
9
8
|
return [];
|
|
9
|
+
const schema = tableConf.Schema;
|
|
10
10
|
const pk = primaryAsString(tableConf.primary);
|
|
11
|
-
const pkCol =
|
|
12
|
-
// 1.
|
|
13
|
-
|
|
14
|
-
let rec = snakecaseRecord(item.main);
|
|
15
|
-
rec = removeExcludedFields(rec, tableConf);
|
|
16
|
-
return rec;
|
|
17
|
-
});
|
|
18
|
-
// 1b. Tenant: inject or validate FK
|
|
19
|
-
if (tenant) {
|
|
20
|
-
if ('through' in tenant.scope) {
|
|
21
|
-
// Indirect: collect all FK values, validate in batch
|
|
22
|
-
const scope = tenant.scope;
|
|
23
|
-
const fkCol = scope.through.localField;
|
|
24
|
-
const fkValues = preparedMains.map((rec) => rec[fkCol]);
|
|
25
|
-
await validateTenantFK(db, scope, tenant.ids, fkValues);
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
for (const rec of preparedMains) {
|
|
29
|
-
injectTenantValue(rec, tenant.scope, tenant.ids);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
// 2. Custom validation (receives original camelCase records)
|
|
11
|
+
const pkCol = schema.col(pk);
|
|
12
|
+
// 1. Bulk validation runs once for all items (skips per-item validate). Otherwise per-item
|
|
13
|
+
// validation happens inside prepareInsertRecord below.
|
|
34
14
|
if (tableConf.validateBulk) {
|
|
35
15
|
await runBulkValidation(db, request, tableConf, items);
|
|
36
16
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (tableConf.beforeInsert) {
|
|
44
|
-
for (const rec of preparedMains) {
|
|
45
|
-
await tableConf.beforeInsert(db, request, rec);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
17
|
+
// 2. For each item: validate (unless validateBulk ran) → beforeInsert → snake → remove excluded.
|
|
18
|
+
const prepared = await Promise.all(items.map((item) => prepareInsertRecord({ db, tableConf, request }, item.main, item.secondaries, { skipValidate: !!tableConf.validateBulk })));
|
|
19
|
+
const inputMains = prepared.map((p) => p.camel);
|
|
20
|
+
const preparedMains = prepared.map((p) => p.snake);
|
|
21
|
+
// 3. Tenant: inject (direct) or batch-validate FK (indirect) — enforced after user mutations
|
|
22
|
+
await enforceTenantOnWrites(db, tenant, preparedMains);
|
|
48
23
|
// 4. Bulk upsert all mains in one query → returns PK-only
|
|
49
|
-
const upsertKeys = tableConf.upsertMap?.get(
|
|
24
|
+
const upsertKeys = tableConf.upsertMap?.get(schema);
|
|
50
25
|
let pkRows;
|
|
51
26
|
if (upsertKeys) {
|
|
52
|
-
const conflictCols = upsertKeys.map((k) =>
|
|
53
|
-
pkRows = await db.bulkInsertOrUpdate(
|
|
27
|
+
const conflictCols = upsertKeys.map((k) => schema.col(k));
|
|
28
|
+
pkRows = await db.bulkInsertOrUpdate(schema.tableName, preparedMains, conflictCols, pkCol);
|
|
54
29
|
}
|
|
55
30
|
else {
|
|
56
|
-
pkRows = await db.bulkInsert(
|
|
31
|
+
pkRows = await db.bulkInsert(schema.tableName, preparedMains, pkCol);
|
|
57
32
|
}
|
|
58
|
-
|
|
59
|
-
// 5. Process secondaries and deletions per item
|
|
33
|
+
// 5. Process secondaries, deletions, afterInsert per item (all camelCase)
|
|
60
34
|
const results = [];
|
|
61
35
|
for (let i = 0; i < items.length; i++) {
|
|
62
36
|
const item = items[i];
|
|
63
|
-
const mainPk =
|
|
64
|
-
|
|
65
|
-
// For
|
|
66
|
-
const
|
|
37
|
+
const mainPk = pkRows[i]; // PK-only (already in camelCase field name since PK uses camelCase field)
|
|
38
|
+
// pkRows entries come from SQL RETURNING or insertId — keys are DB column names.
|
|
39
|
+
// For simple PK named the same in API and DB (like 'id') this is transparent.
|
|
40
|
+
const result = { main: { [pk]: mainPk[pkCol] ?? mainPk[pk] } };
|
|
41
|
+
// FK auto-fill: input camelCase + generated PK
|
|
42
|
+
const mainForFK = { ...inputMains[i], [pk]: mainPk[pkCol] ?? mainPk[pk] };
|
|
67
43
|
if (item.secondaries && Object.keys(item.secondaries).length > 0) {
|
|
68
44
|
const sec = await processSecondaries(db, tableConf, dbTables, mainForFK, item.secondaries);
|
|
69
45
|
if (Object.keys(sec).length > 0)
|
|
70
46
|
result.secondaries = sec;
|
|
71
47
|
}
|
|
72
48
|
if (item.deletions && Object.keys(item.deletions).length > 0) {
|
|
73
|
-
const del = await processDeletions(db, tableConf, item.deletions);
|
|
49
|
+
const del = await processDeletions(db, tableConf, mainForFK, item.deletions);
|
|
74
50
|
if (Object.keys(del).length > 0)
|
|
75
51
|
result.deletions = del;
|
|
76
52
|
}
|
|
77
|
-
// 6. afterInsert hook per item
|
|
53
|
+
// 6. afterInsert hook per item (camelCase)
|
|
78
54
|
if (tableConf.afterInsert) {
|
|
79
55
|
await tableConf.afterInsert(db, request, mainForFK, result.secondaries);
|
|
80
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/delete.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/delete.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAY,MAAM,mBAAmB,CAAC;AAE9E,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAsB9E"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildTenantDeleteWhere } from '../../tenant.js';
|
|
2
|
+
import { httpError } from '../../errors.js';
|
|
2
3
|
import { primaryAsString } from '../../../types.js';
|
|
3
4
|
export async function deleteEngine(params) {
|
|
4
5
|
const { db, tableConf, id, tenant } = params;
|
|
@@ -7,19 +8,13 @@ export async function deleteEngine(params) {
|
|
|
7
8
|
const tableName = tableConf.Schema.tableName;
|
|
8
9
|
if (!tenant) {
|
|
9
10
|
const affectedRows = await db.delete(tableName, { [pkCol]: id });
|
|
10
|
-
if (affectedRows === 0)
|
|
11
|
-
|
|
12
|
-
err.statusCode = 404;
|
|
13
|
-
throw err;
|
|
14
|
-
}
|
|
11
|
+
if (affectedRows === 0)
|
|
12
|
+
throw httpError(404, `Record not found: ${id}`);
|
|
15
13
|
return { main: { [pk]: id } };
|
|
16
14
|
}
|
|
17
15
|
const { where, values } = buildTenantDeleteWhere(db, tableName, pkCol, id, tenant);
|
|
18
16
|
const result = await db.query(`DELETE FROM ${db.qi(tableName)} WHERE ${where}`, values);
|
|
19
|
-
if (result.affectedRows === 0)
|
|
20
|
-
|
|
21
|
-
err.statusCode = 404;
|
|
22
|
-
throw err;
|
|
23
|
-
}
|
|
17
|
+
if (result.affectedRows === 0)
|
|
18
|
+
throw httpError(404, `Record not found: ${id}`);
|
|
24
19
|
return { main: { [pk]: id } };
|
|
25
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/get.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/get.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAuB,MAAM,mBAAmB,CAAC;AAEnF,wBAAsB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CA6BrE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ConditionBuilder } from 'node-condition-builder';
|
|
2
2
|
import { camelcaseObject } from '../../naming.js';
|
|
3
3
|
import { buildTenantCondition, buildTenantJoin } from '../../tenant.js';
|
|
4
|
+
import { httpError } from '../../errors.js';
|
|
4
5
|
import { primaryAsString } from '../../../types.js';
|
|
5
6
|
export async function getEngine(params) {
|
|
6
7
|
const { db, tableConf, id, tenant } = params;
|
|
@@ -23,10 +24,7 @@ export async function getEngine(params) {
|
|
|
23
24
|
limit: '1',
|
|
24
25
|
joins: joins.length > 0 ? joins : undefined,
|
|
25
26
|
});
|
|
26
|
-
if (rows.length === 0)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
throw err;
|
|
30
|
-
}
|
|
31
|
-
return { main: camelcaseObject(rows[0]) };
|
|
27
|
+
if (rows.length === 0)
|
|
28
|
+
throw httpError(404, `Record not found: ${id}`);
|
|
29
|
+
return { main: camelcaseObject(rows[0], tableConf.Schema) };
|
|
32
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/insert.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/insert.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA0D9E"}
|