@restura/core 2.1.0 → 2.1.2
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/index.d.ts +3 -2
- package/dist/index.js +50 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import * as express from 'express';
|
|
4
4
|
import { IncomingHttpHeaders } from 'http2';
|
|
5
5
|
import { QueryResultRow, QueryConfigValues, QueryResult, PoolConfig, Pool, ClientConfig, Client } from 'pg';
|
|
6
|
-
import
|
|
6
|
+
import * as pegjs from 'pegjs';
|
|
7
7
|
|
|
8
8
|
type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
|
9
9
|
interface ResturaLogger {
|
|
@@ -1155,7 +1155,7 @@ declare class ResturaEngine {
|
|
|
1155
1155
|
}
|
|
1156
1156
|
declare const restura: ResturaEngine;
|
|
1157
1157
|
|
|
1158
|
-
declare const filterPsqlParser:
|
|
1158
|
+
declare const filterPsqlParser: pegjs.Parser;
|
|
1159
1159
|
|
|
1160
1160
|
declare abstract class SqlEngine {
|
|
1161
1161
|
runQueryForRoute(req: RsRequest<unknown>, routeData: StandardRouteData, schema: ResturaSchema): Promise<DynamicObject | any[] | boolean>;
|
|
@@ -1358,6 +1358,7 @@ interface TriggerSqlOptions {
|
|
|
1358
1358
|
delivery?: 'direct' | 'outbox';
|
|
1359
1359
|
channel?: string;
|
|
1360
1360
|
tableColumns?: string[];
|
|
1361
|
+
columnTypes?: Record<string, string>;
|
|
1361
1362
|
}
|
|
1362
1363
|
interface SchemaGenerationOptions {
|
|
1363
1364
|
eventDelivery?: 'direct' | 'outbox';
|
package/dist/index.js
CHANGED
|
@@ -259,7 +259,7 @@ var SqlUtils = class _SqlUtils {
|
|
|
259
259
|
if (type.startsWith("decimal") || type.startsWith("numeric")) return "string";
|
|
260
260
|
if (type.indexOf("int") > -1 || type.startsWith("double") || type.startsWith("float") || type.indexOf("serial") > -1 || type.startsWith("real") || type.startsWith("double precision"))
|
|
261
261
|
return "number";
|
|
262
|
-
if (type === "json") {
|
|
262
|
+
if (type === "json" || type === "jsonb") {
|
|
263
263
|
if (!value) return "object";
|
|
264
264
|
return value.split(",").map((val) => {
|
|
265
265
|
return val.replace(/['"]/g, "");
|
|
@@ -2359,23 +2359,31 @@ CREATE INDEX IF NOT EXISTS "${OUTBOX_TABLE_NAME}_processedOn_index" ON "${OUTBOX
|
|
|
2359
2359
|
}
|
|
2360
2360
|
function resolveNotifyColumns(tableName, notify, tableColumns) {
|
|
2361
2361
|
if (!notify) return [];
|
|
2362
|
+
let columns;
|
|
2362
2363
|
if (notify === "ALL") {
|
|
2363
2364
|
if (!tableColumns) {
|
|
2364
2365
|
throw new Error(
|
|
2365
2366
|
`notify: "ALL" on table "${tableName}" requires tableColumns so the sensitive-column denylist can be applied. Pass options.tableColumns or list columns explicitly.`
|
|
2366
2367
|
);
|
|
2367
2368
|
}
|
|
2368
|
-
|
|
2369
|
+
columns = tableColumns.filter((column) => !isSensitiveColumnName(column));
|
|
2370
|
+
} else {
|
|
2371
|
+
columns = notify.map((entry) => {
|
|
2372
|
+
if (entry.startsWith("!")) return entry.slice(1);
|
|
2373
|
+
if (isSensitiveColumnName(entry)) {
|
|
2374
|
+
throw new Error(
|
|
2375
|
+
`Refusing to include sensitive column "${tableName}"."${entry}" in notify trigger payloads. Remove it from the notify list or prefix it with '!' to force-include it.`
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
return entry;
|
|
2379
|
+
});
|
|
2369
2380
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
}
|
|
2377
|
-
return entry;
|
|
2378
|
-
});
|
|
2381
|
+
if (!columns.length) {
|
|
2382
|
+
throw new Error(
|
|
2383
|
+
`notify config on table "${tableName}" resolves to no columns \u2014 remove the notify key or list at least one column.`
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
return columns;
|
|
2379
2387
|
}
|
|
2380
2388
|
function sqlStringLiteral(value) {
|
|
2381
2389
|
return value.replace(/'/g, "''");
|
|
@@ -2425,7 +2433,14 @@ function buildTriggerFunctionSql(tableName, operation, notify, options) {
|
|
|
2425
2433
|
const outboxDeclare = delivery === "outbox" ? "\n outbox_id BIGINT;" : "";
|
|
2426
2434
|
const legacyDrop = operation === "update" && tableName !== tableName.toLowerCase() ? `DROP TRIGGER IF EXISTS ${tableName}_update ON "${tableName}";
|
|
2427
2435
|
` : "";
|
|
2428
|
-
|
|
2436
|
+
let updateScope = "";
|
|
2437
|
+
let updateGuard = "";
|
|
2438
|
+
if (operation === "update") {
|
|
2439
|
+
updateScope = ` OF ${columns.map((column) => `"${column}"`).join(", ")}`;
|
|
2440
|
+
const columnComparison = (column) => options?.columnTypes?.[column] === "JSON" ? `OLD."${column}"::jsonb IS DISTINCT FROM NEW."${column}"::jsonb` : `OLD."${column}" IS DISTINCT FROM NEW."${column}"`;
|
|
2441
|
+
updateGuard = `
|
|
2442
|
+
WHEN (${columns.map(columnComparison).join(" OR ")})`;
|
|
2443
|
+
}
|
|
2429
2444
|
return `
|
|
2430
2445
|
CREATE OR REPLACE FUNCTION ${functionName}()
|
|
2431
2446
|
RETURNS TRIGGER AS $$
|
|
@@ -2440,7 +2455,7 @@ END;
|
|
|
2440
2455
|
$$ LANGUAGE plpgsql;
|
|
2441
2456
|
|
|
2442
2457
|
${legacyDrop}CREATE OR REPLACE TRIGGER "${tableName}_${operation}"
|
|
2443
|
-
AFTER ${operation.toUpperCase()} ON "${tableName}"
|
|
2458
|
+
AFTER ${operation.toUpperCase()}${updateScope} ON "${tableName}"
|
|
2444
2459
|
FOR EACH ROW${updateGuard}
|
|
2445
2460
|
EXECUTE FUNCTION ${functionName}();
|
|
2446
2461
|
`;
|
|
@@ -2465,7 +2480,8 @@ function generateNotifyTriggersSql(schema, options) {
|
|
|
2465
2480
|
const triggerOptions = {
|
|
2466
2481
|
delivery: options?.eventDelivery,
|
|
2467
2482
|
channel: options?.outboxChannel,
|
|
2468
|
-
tableColumns: table.columns.map((column) => column.name)
|
|
2483
|
+
tableColumns: table.columns.map((column) => column.name),
|
|
2484
|
+
columnTypes: Object.fromEntries(table.columns.map((column) => [column.name, column.type]))
|
|
2469
2485
|
};
|
|
2470
2486
|
statements.push(createInsertTriggerSql(table.name, table.notify, triggerOptions));
|
|
2471
2487
|
statements.push(createUpdateTriggerSql(table.name, table.notify, triggerOptions));
|
|
@@ -2796,7 +2812,26 @@ var EventOutboxConsumer = class {
|
|
|
2796
2812
|
};
|
|
2797
2813
|
|
|
2798
2814
|
// src/restura/sql/filterPsqlParser.ts
|
|
2815
|
+
import format2 from "pg-format";
|
|
2816
|
+
|
|
2817
|
+
// src/restura/sql/compilePegParser.ts
|
|
2799
2818
|
import peg from "pegjs";
|
|
2819
|
+
function compilePegParser(grammar, dependencies, modules) {
|
|
2820
|
+
const parserSource = peg.generate(grammar, {
|
|
2821
|
+
output: "source",
|
|
2822
|
+
format: "commonjs",
|
|
2823
|
+
dependencies
|
|
2824
|
+
});
|
|
2825
|
+
const moduleShim = { exports: {} };
|
|
2826
|
+
const requireShim = (moduleId) => {
|
|
2827
|
+
if (moduleId in modules) return modules[moduleId];
|
|
2828
|
+
throw new Error(`compilePegParser: no module provided for dependency '${moduleId}'`);
|
|
2829
|
+
};
|
|
2830
|
+
new Function("module", "exports", "require", parserSource)(moduleShim, moduleShim.exports, requireShim);
|
|
2831
|
+
return moduleShim.exports;
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
// src/restura/sql/filterPsqlParser.ts
|
|
2800
2835
|
var initializers = `
|
|
2801
2836
|
// Quotes a SQL identifier (column/table name) with double quotes, escaping any embedded quotes
|
|
2802
2837
|
function quoteSqlIdentity(value) {
|
|
@@ -3132,10 +3167,7 @@ ValueWithPipesChar
|
|
|
3132
3167
|
/ c:":" !":" { return c; }
|
|
3133
3168
|
`;
|
|
3134
3169
|
var fullGrammar = entryGrammar + oldGrammar + newGrammar;
|
|
3135
|
-
var filterPsqlParser =
|
|
3136
|
-
format: "commonjs",
|
|
3137
|
-
dependencies: { format: "pg-format" }
|
|
3138
|
-
});
|
|
3170
|
+
var filterPsqlParser = compilePegParser(fullGrammar, { format: "pg-format" }, { "pg-format": format2 });
|
|
3139
3171
|
var filterPsqlParser_default = filterPsqlParser;
|
|
3140
3172
|
|
|
3141
3173
|
// src/restura/sql/PsqlEngine.ts
|