relq 1.0.0
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/LICENSE +21 -0
- package/README.md +862 -0
- package/dist/addons/buffer.js +1869 -0
- package/dist/addons/pg-cursor.js +1425 -0
- package/dist/addons/pg-format.js +2248 -0
- package/dist/addons/pg.js +4790 -0
- package/dist/bin/relq.js +2 -0
- package/dist/cjs/cache/index.cjs +9 -0
- package/dist/cjs/cache/query-cache.cjs +311 -0
- package/dist/cjs/cli/commands/add.cjs +82 -0
- package/dist/cjs/cli/commands/commit.cjs +145 -0
- package/dist/cjs/cli/commands/diff.cjs +84 -0
- package/dist/cjs/cli/commands/export.cjs +333 -0
- package/dist/cjs/cli/commands/fetch.cjs +59 -0
- package/dist/cjs/cli/commands/generate.cjs +242 -0
- package/dist/cjs/cli/commands/history.cjs +165 -0
- package/dist/cjs/cli/commands/import.cjs +524 -0
- package/dist/cjs/cli/commands/init.cjs +437 -0
- package/dist/cjs/cli/commands/introspect.cjs +142 -0
- package/dist/cjs/cli/commands/log.cjs +62 -0
- package/dist/cjs/cli/commands/migrate.cjs +167 -0
- package/dist/cjs/cli/commands/pull.cjs +410 -0
- package/dist/cjs/cli/commands/push.cjs +165 -0
- package/dist/cjs/cli/commands/rollback.cjs +169 -0
- package/dist/cjs/cli/commands/status.cjs +110 -0
- package/dist/cjs/cli/commands/sync.cjs +79 -0
- package/dist/cjs/cli/index.cjs +275 -0
- package/dist/cjs/cli/utils/change-tracker.cjs +446 -0
- package/dist/cjs/cli/utils/commit-manager.cjs +239 -0
- package/dist/cjs/cli/utils/config-loader.cjs +127 -0
- package/dist/cjs/cli/utils/env-loader.cjs +62 -0
- package/dist/cjs/cli/utils/fast-introspect.cjs +398 -0
- package/dist/cjs/cli/utils/git-utils.cjs +404 -0
- package/dist/cjs/cli/utils/migration-generator.cjs +269 -0
- package/dist/cjs/cli/utils/relqignore.cjs +114 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +515 -0
- package/dist/cjs/cli/utils/schema-comparator.cjs +313 -0
- package/dist/cjs/cli/utils/schema-diff.cjs +284 -0
- package/dist/cjs/cli/utils/schema-hash.cjs +108 -0
- package/dist/cjs/cli/utils/schema-introspect.cjs +455 -0
- package/dist/cjs/cli/utils/snapshot-manager.cjs +223 -0
- package/dist/cjs/cli/utils/spinner.cjs +108 -0
- package/dist/cjs/cli/utils/sql-generator.cjs +520 -0
- package/dist/cjs/cli/utils/sql-parser.cjs +999 -0
- package/dist/cjs/cli/utils/type-generator.cjs +2061 -0
- package/dist/cjs/condition/array-condition-builder.cjs +503 -0
- package/dist/cjs/condition/array-numeric-condition-builder.cjs +186 -0
- package/dist/cjs/condition/array-specialized-condition-builder.cjs +206 -0
- package/dist/cjs/condition/array-string-condition-builder.cjs +146 -0
- package/dist/cjs/condition/base-condition-builder.cjs +2 -0
- package/dist/cjs/condition/condition-collector.cjs +284 -0
- package/dist/cjs/condition/fulltext-condition-builder.cjs +61 -0
- package/dist/cjs/condition/geometric-condition-builder.cjs +208 -0
- package/dist/cjs/condition/index.cjs +25 -0
- package/dist/cjs/condition/jsonb-condition-builder.cjs +160 -0
- package/dist/cjs/condition/network-condition-builder.cjs +230 -0
- package/dist/cjs/condition/range-condition-builder.cjs +82 -0
- package/dist/cjs/config/config.cjs +190 -0
- package/dist/cjs/config/index.cjs +9 -0
- package/dist/cjs/constants/pg-values.cjs +68 -0
- package/dist/cjs/copy/copy-builder.cjs +316 -0
- package/dist/cjs/copy/index.cjs +6 -0
- package/dist/cjs/core/query-builder.cjs +440 -0
- package/dist/cjs/core/relq-client.cjs +1831 -0
- package/dist/cjs/core/typed-kuery-client.cjs +2 -0
- package/dist/cjs/count/count-builder.cjs +88 -0
- package/dist/cjs/count/index.cjs +5 -0
- package/dist/cjs/cte/cte-builder.cjs +89 -0
- package/dist/cjs/cte/index.cjs +5 -0
- package/dist/cjs/ddl/function.cjs +48 -0
- package/dist/cjs/ddl/index.cjs +7 -0
- package/dist/cjs/ddl/sql.cjs +54 -0
- package/dist/cjs/delete/delete-builder.cjs +135 -0
- package/dist/cjs/delete/index.cjs +5 -0
- package/dist/cjs/errors/relq-errors.cjs +329 -0
- package/dist/cjs/examples/fulltext-search-test.cjs +122 -0
- package/dist/cjs/explain/explain-builder.cjs +99 -0
- package/dist/cjs/explain/index.cjs +5 -0
- package/dist/cjs/function/create-function-builder.cjs +196 -0
- package/dist/cjs/function/index.cjs +6 -0
- package/dist/cjs/functions/advanced-functions.cjs +241 -0
- package/dist/cjs/functions/case-builder.cjs +66 -0
- package/dist/cjs/functions/geometric-functions.cjs +104 -0
- package/dist/cjs/functions/index.cjs +184 -0
- package/dist/cjs/functions/network-functions.cjs +86 -0
- package/dist/cjs/functions/sql-functions.cjs +431 -0
- package/dist/cjs/index.cjs +164 -0
- package/dist/cjs/indexing/create-index-builder.cjs +187 -0
- package/dist/cjs/indexing/drop-index-builder.cjs +89 -0
- package/dist/cjs/indexing/index-types.cjs +2 -0
- package/dist/cjs/indexing/index.cjs +8 -0
- package/dist/cjs/insert/conflict-builder.cjs +173 -0
- package/dist/cjs/insert/index.cjs +5 -0
- package/dist/cjs/insert/insert-builder.cjs +254 -0
- package/dist/cjs/introspect/index.cjs +229 -0
- package/dist/cjs/maintenance/index.cjs +6 -0
- package/dist/cjs/maintenance/vacuum-builder.cjs +166 -0
- package/dist/cjs/pubsub/index.cjs +7 -0
- package/dist/cjs/pubsub/listen-notify-builder.cjs +57 -0
- package/dist/cjs/pubsub/listener-connection.cjs +180 -0
- package/dist/cjs/raw/index.cjs +5 -0
- package/dist/cjs/raw/raw-query-builder.cjs +27 -0
- package/dist/cjs/schema/index.cjs +15 -0
- package/dist/cjs/schema/schema-builder.cjs +1167 -0
- package/dist/cjs/schema-builder.cjs +21 -0
- package/dist/cjs/schema-definition/column-types.cjs +829 -0
- package/dist/cjs/schema-definition/index.cjs +62 -0
- package/dist/cjs/schema-definition/introspection.cjs +620 -0
- package/dist/cjs/schema-definition/partitions.cjs +129 -0
- package/dist/cjs/schema-definition/pg-enum.cjs +76 -0
- package/dist/cjs/schema-definition/pg-function.cjs +91 -0
- package/dist/cjs/schema-definition/pg-sequence.cjs +56 -0
- package/dist/cjs/schema-definition/pg-trigger.cjs +108 -0
- package/dist/cjs/schema-definition/relations.cjs +98 -0
- package/dist/cjs/schema-definition/sql-expressions.cjs +202 -0
- package/dist/cjs/schema-definition/table-definition.cjs +636 -0
- package/dist/cjs/select/aggregate-builder.cjs +179 -0
- package/dist/cjs/select/index.cjs +5 -0
- package/dist/cjs/select/select-builder.cjs +233 -0
- package/dist/cjs/sequence/index.cjs +7 -0
- package/dist/cjs/sequence/sequence-builder.cjs +264 -0
- package/dist/cjs/table/alter-table-builder.cjs +146 -0
- package/dist/cjs/table/constraint-builder.cjs +102 -0
- package/dist/cjs/table/create-table-builder.cjs +248 -0
- package/dist/cjs/table/index.cjs +17 -0
- package/dist/cjs/table/partition-builder.cjs +131 -0
- package/dist/cjs/table/truncate-builder.cjs +70 -0
- package/dist/cjs/transaction/index.cjs +6 -0
- package/dist/cjs/transaction/transaction-builder.cjs +78 -0
- package/dist/cjs/trigger/create-trigger-builder.cjs +174 -0
- package/dist/cjs/trigger/index.cjs +6 -0
- package/dist/cjs/types/aggregate-types.cjs +2 -0
- package/dist/cjs/types/config-types.cjs +40 -0
- package/dist/cjs/types/inference-types.cjs +18 -0
- package/dist/cjs/types/pagination-types.cjs +7 -0
- package/dist/cjs/types/result-types.cjs +2 -0
- package/dist/cjs/types/schema-types.cjs +2 -0
- package/dist/cjs/types/subscription-types.cjs +2 -0
- package/dist/cjs/types.cjs +2 -0
- package/dist/cjs/update/array-update-builder.cjs +205 -0
- package/dist/cjs/update/index.cjs +13 -0
- package/dist/cjs/update/update-builder.cjs +195 -0
- package/dist/cjs/utils/case-converter.cjs +58 -0
- package/dist/cjs/utils/environment-detection.cjs +120 -0
- package/dist/cjs/utils/index.cjs +10 -0
- package/dist/cjs/utils/pool-defaults.cjs +106 -0
- package/dist/cjs/utils/type-coercion.cjs +118 -0
- package/dist/cjs/view/create-view-builder.cjs +180 -0
- package/dist/cjs/view/index.cjs +7 -0
- package/dist/cjs/window/index.cjs +5 -0
- package/dist/cjs/window/window-builder.cjs +80 -0
- package/dist/config.cjs +1 -0
- package/dist/config.d.ts +655 -0
- package/dist/config.js +1 -0
- package/dist/esm/cache/index.js +1 -0
- package/dist/esm/cache/query-cache.js +303 -0
- package/dist/esm/cli/commands/add.js +78 -0
- package/dist/esm/cli/commands/commit.js +109 -0
- package/dist/esm/cli/commands/diff.js +81 -0
- package/dist/esm/cli/commands/export.js +297 -0
- package/dist/esm/cli/commands/fetch.js +56 -0
- package/dist/esm/cli/commands/generate.js +206 -0
- package/dist/esm/cli/commands/history.js +129 -0
- package/dist/esm/cli/commands/import.js +488 -0
- package/dist/esm/cli/commands/init.js +401 -0
- package/dist/esm/cli/commands/introspect.js +106 -0
- package/dist/esm/cli/commands/log.js +59 -0
- package/dist/esm/cli/commands/migrate.js +131 -0
- package/dist/esm/cli/commands/pull.js +374 -0
- package/dist/esm/cli/commands/push.js +129 -0
- package/dist/esm/cli/commands/rollback.js +133 -0
- package/dist/esm/cli/commands/status.js +107 -0
- package/dist/esm/cli/commands/sync.js +76 -0
- package/dist/esm/cli/index.js +240 -0
- package/dist/esm/cli/utils/change-tracker.js +405 -0
- package/dist/esm/cli/utils/commit-manager.js +191 -0
- package/dist/esm/cli/utils/config-loader.js +86 -0
- package/dist/esm/cli/utils/env-loader.js +57 -0
- package/dist/esm/cli/utils/fast-introspect.js +362 -0
- package/dist/esm/cli/utils/git-utils.js +347 -0
- package/dist/esm/cli/utils/migration-generator.js +263 -0
- package/dist/esm/cli/utils/relqignore.js +74 -0
- package/dist/esm/cli/utils/repo-manager.js +444 -0
- package/dist/esm/cli/utils/schema-comparator.js +307 -0
- package/dist/esm/cli/utils/schema-diff.js +276 -0
- package/dist/esm/cli/utils/schema-hash.js +69 -0
- package/dist/esm/cli/utils/schema-introspect.js +418 -0
- package/dist/esm/cli/utils/snapshot-manager.js +179 -0
- package/dist/esm/cli/utils/spinner.js +101 -0
- package/dist/esm/cli/utils/sql-generator.js +504 -0
- package/dist/esm/cli/utils/sql-parser.js +992 -0
- package/dist/esm/cli/utils/type-generator.js +2058 -0
- package/dist/esm/condition/array-condition-builder.js +495 -0
- package/dist/esm/condition/array-numeric-condition-builder.js +182 -0
- package/dist/esm/condition/array-specialized-condition-builder.js +200 -0
- package/dist/esm/condition/array-string-condition-builder.js +142 -0
- package/dist/esm/condition/base-condition-builder.js +1 -0
- package/dist/esm/condition/condition-collector.js +275 -0
- package/dist/esm/condition/fulltext-condition-builder.js +53 -0
- package/dist/esm/condition/geometric-condition-builder.js +200 -0
- package/dist/esm/condition/index.js +7 -0
- package/dist/esm/condition/jsonb-condition-builder.js +152 -0
- package/dist/esm/condition/network-condition-builder.js +222 -0
- package/dist/esm/condition/range-condition-builder.js +74 -0
- package/dist/esm/config/config.js +150 -0
- package/dist/esm/config/index.js +1 -0
- package/dist/esm/constants/pg-values.js +63 -0
- package/dist/esm/copy/copy-builder.js +308 -0
- package/dist/esm/copy/index.js +1 -0
- package/dist/esm/core/query-builder.js +426 -0
- package/dist/esm/core/relq-client.js +1791 -0
- package/dist/esm/core/typed-kuery-client.js +1 -0
- package/dist/esm/count/count-builder.js +81 -0
- package/dist/esm/count/index.js +1 -0
- package/dist/esm/cte/cte-builder.js +82 -0
- package/dist/esm/cte/index.js +1 -0
- package/dist/esm/ddl/function.js +45 -0
- package/dist/esm/ddl/index.js +2 -0
- package/dist/esm/ddl/sql.js +51 -0
- package/dist/esm/delete/delete-builder.js +128 -0
- package/dist/esm/delete/index.js +1 -0
- package/dist/esm/errors/relq-errors.js +310 -0
- package/dist/esm/examples/fulltext-search-test.js +117 -0
- package/dist/esm/explain/explain-builder.js +95 -0
- package/dist/esm/explain/index.js +1 -0
- package/dist/esm/function/create-function-builder.js +188 -0
- package/dist/esm/function/index.js +1 -0
- package/dist/esm/functions/advanced-functions.js +231 -0
- package/dist/esm/functions/case-builder.js +58 -0
- package/dist/esm/functions/geometric-functions.js +97 -0
- package/dist/esm/functions/index.js +171 -0
- package/dist/esm/functions/network-functions.js +79 -0
- package/dist/esm/functions/sql-functions.js +421 -0
- package/dist/esm/index.js +34 -0
- package/dist/esm/indexing/create-index-builder.js +180 -0
- package/dist/esm/indexing/drop-index-builder.js +81 -0
- package/dist/esm/indexing/index-types.js +1 -0
- package/dist/esm/indexing/index.js +2 -0
- package/dist/esm/insert/conflict-builder.js +162 -0
- package/dist/esm/insert/index.js +1 -0
- package/dist/esm/insert/insert-builder.js +247 -0
- package/dist/esm/introspect/index.js +224 -0
- package/dist/esm/maintenance/index.js +1 -0
- package/dist/esm/maintenance/vacuum-builder.js +158 -0
- package/dist/esm/pubsub/index.js +1 -0
- package/dist/esm/pubsub/listen-notify-builder.js +48 -0
- package/dist/esm/pubsub/listener-connection.js +173 -0
- package/dist/esm/raw/index.js +1 -0
- package/dist/esm/raw/raw-query-builder.js +20 -0
- package/dist/esm/schema/index.js +1 -0
- package/dist/esm/schema/schema-builder.js +1150 -0
- package/dist/esm/schema-builder.js +2 -0
- package/dist/esm/schema-definition/column-types.js +738 -0
- package/dist/esm/schema-definition/index.js +10 -0
- package/dist/esm/schema-definition/introspection.js +614 -0
- package/dist/esm/schema-definition/partitions.js +123 -0
- package/dist/esm/schema-definition/pg-enum.js +70 -0
- package/dist/esm/schema-definition/pg-function.js +85 -0
- package/dist/esm/schema-definition/pg-sequence.js +50 -0
- package/dist/esm/schema-definition/pg-trigger.js +102 -0
- package/dist/esm/schema-definition/relations.js +90 -0
- package/dist/esm/schema-definition/sql-expressions.js +193 -0
- package/dist/esm/schema-definition/table-definition.js +630 -0
- package/dist/esm/select/aggregate-builder.js +172 -0
- package/dist/esm/select/index.js +1 -0
- package/dist/esm/select/select-builder.js +226 -0
- package/dist/esm/sequence/index.js +1 -0
- package/dist/esm/sequence/sequence-builder.js +255 -0
- package/dist/esm/table/alter-table-builder.js +138 -0
- package/dist/esm/table/constraint-builder.js +95 -0
- package/dist/esm/table/create-table-builder.js +241 -0
- package/dist/esm/table/index.js +5 -0
- package/dist/esm/table/partition-builder.js +121 -0
- package/dist/esm/table/truncate-builder.js +63 -0
- package/dist/esm/transaction/index.js +1 -0
- package/dist/esm/transaction/transaction-builder.js +70 -0
- package/dist/esm/trigger/create-trigger-builder.js +166 -0
- package/dist/esm/trigger/index.js +1 -0
- package/dist/esm/types/aggregate-types.js +1 -0
- package/dist/esm/types/config-types.js +36 -0
- package/dist/esm/types/inference-types.js +12 -0
- package/dist/esm/types/pagination-types.js +4 -0
- package/dist/esm/types/result-types.js +1 -0
- package/dist/esm/types/schema-types.js +1 -0
- package/dist/esm/types/subscription-types.js +1 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/update/array-update-builder.js +192 -0
- package/dist/esm/update/index.js +2 -0
- package/dist/esm/update/update-builder.js +188 -0
- package/dist/esm/utils/case-converter.js +55 -0
- package/dist/esm/utils/environment-detection.js +113 -0
- package/dist/esm/utils/index.js +2 -0
- package/dist/esm/utils/pool-defaults.js +100 -0
- package/dist/esm/utils/type-coercion.js +110 -0
- package/dist/esm/view/create-view-builder.js +171 -0
- package/dist/esm/view/index.js +1 -0
- package/dist/esm/window/index.js +1 -0
- package/dist/esm/window/window-builder.js +73 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +10341 -0
- package/dist/index.js +1 -0
- package/dist/schema-builder.cjs +1 -0
- package/dist/schema-builder.d.ts +2272 -0
- package/dist/schema-builder.js +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,1831 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.Relq = void 0;
|
|
40
|
+
const node_events_1 = require("node:events");
|
|
41
|
+
const config_types_1 = require("../types/config-types.cjs");
|
|
42
|
+
const select_builder_1 = require("../select/select-builder.cjs");
|
|
43
|
+
const aggregate_builder_1 = require("../select/aggregate-builder.cjs");
|
|
44
|
+
const insert_builder_1 = require("../insert/insert-builder.cjs");
|
|
45
|
+
const update_builder_1 = require("../update/update-builder.cjs");
|
|
46
|
+
const delete_builder_1 = require("../delete/delete-builder.cjs");
|
|
47
|
+
const count_builder_1 = require("../count/count-builder.cjs");
|
|
48
|
+
const raw_query_builder_1 = require("../raw/raw-query-builder.cjs");
|
|
49
|
+
const transaction_builder_1 = require("../transaction/transaction-builder.cjs");
|
|
50
|
+
const listener_connection_1 = require("../pubsub/listener-connection.cjs");
|
|
51
|
+
const environment_detection_1 = require("../utils/environment-detection.cjs");
|
|
52
|
+
const pool_defaults_1 = require("../utils/pool-defaults.cjs");
|
|
53
|
+
const relq_errors_1 = require("../errors/relq-errors.cjs");
|
|
54
|
+
const pagination_types_1 = require("../types/pagination-types.cjs");
|
|
55
|
+
const type_coercion_1 = require("../utils/type-coercion.cjs");
|
|
56
|
+
const pg_cursor_1 = __importDefault(require("../../addons/pg-cursor.js"));
|
|
57
|
+
const INTERNAL = Symbol('relq-internal');
|
|
58
|
+
let PgPool = null;
|
|
59
|
+
let PgClient = null;
|
|
60
|
+
const activeRelqInstances = new Set();
|
|
61
|
+
let cleanupHandlersRegistered = false;
|
|
62
|
+
function debugLog(config, ...args) {
|
|
63
|
+
if (config?.logLevel === 'debug') {
|
|
64
|
+
console.log('[Relq DEBUG]', ...args);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function registerGlobalCleanupHandlers() {
|
|
68
|
+
if (cleanupHandlersRegistered)
|
|
69
|
+
return;
|
|
70
|
+
if (typeof process === 'undefined' || !process.on)
|
|
71
|
+
return;
|
|
72
|
+
process.on('beforeExit', async () => {
|
|
73
|
+
if (activeRelqInstances.size === 0)
|
|
74
|
+
return;
|
|
75
|
+
await Promise.all(Array.from(activeRelqInstances).map(instance => instance.close().catch(err => console.error('Error closing database connection:', err))));
|
|
76
|
+
});
|
|
77
|
+
cleanupHandlersRegistered = true;
|
|
78
|
+
}
|
|
79
|
+
async function loadPg() {
|
|
80
|
+
if (!PgPool || !PgClient) {
|
|
81
|
+
try {
|
|
82
|
+
const pg = await Promise.resolve().then(() => __importStar(require("../../addons/pg.js")));
|
|
83
|
+
PgPool = pg.Pool;
|
|
84
|
+
PgClient = pg.Client;
|
|
85
|
+
return { Pool: pg.Pool, Client: pg.Client };
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
throw new relq_errors_1.RelqConfigError('Failed to load "pg" module', { field: 'pg', value: error instanceof Error ? error.message : String(error) });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { Pool: PgPool, Client: PgClient };
|
|
92
|
+
}
|
|
93
|
+
class Relq {
|
|
94
|
+
pool;
|
|
95
|
+
client;
|
|
96
|
+
config;
|
|
97
|
+
usePooling;
|
|
98
|
+
initialized = false;
|
|
99
|
+
initPromise;
|
|
100
|
+
isClosed = false;
|
|
101
|
+
environment;
|
|
102
|
+
poolErrorHandler;
|
|
103
|
+
poolConnectHandler;
|
|
104
|
+
poolRemoveHandler;
|
|
105
|
+
listener = null;
|
|
106
|
+
emitter = new node_events_1.EventEmitter();
|
|
107
|
+
columnMappings = new Map();
|
|
108
|
+
schema;
|
|
109
|
+
constructor(schema, config) {
|
|
110
|
+
const schemaObj = schema;
|
|
111
|
+
this.config = config;
|
|
112
|
+
this.schema = schema;
|
|
113
|
+
this.environment = (0, environment_detection_1.detectEnvironment)();
|
|
114
|
+
if (schema) {
|
|
115
|
+
this.buildColumnMappings(schema);
|
|
116
|
+
}
|
|
117
|
+
this.usePooling = this.determinePoolingStrategy(config);
|
|
118
|
+
this.validateConfiguration();
|
|
119
|
+
this.logEnvironmentInfo();
|
|
120
|
+
activeRelqInstances.add(this);
|
|
121
|
+
registerGlobalCleanupHandlers();
|
|
122
|
+
}
|
|
123
|
+
buildColumnMappings(schema) {
|
|
124
|
+
if (!schema || typeof schema !== 'object')
|
|
125
|
+
return;
|
|
126
|
+
const tables = schema.tables || schema;
|
|
127
|
+
for (const [tableName, tableDef] of Object.entries(tables)) {
|
|
128
|
+
if (!tableDef || typeof tableDef !== 'object')
|
|
129
|
+
continue;
|
|
130
|
+
const columns = tableDef.$columns;
|
|
131
|
+
if (!columns || typeof columns !== 'object')
|
|
132
|
+
continue;
|
|
133
|
+
const propToDb = new Map();
|
|
134
|
+
const dbToProp = new Map();
|
|
135
|
+
const propToType = new Map();
|
|
136
|
+
const propToCheckValues = new Map();
|
|
137
|
+
const propToValidate = new Map();
|
|
138
|
+
const propToFields = new Map();
|
|
139
|
+
for (const [propName, colDef] of Object.entries(columns)) {
|
|
140
|
+
const dbColName = colDef?.$columnName ?? propName;
|
|
141
|
+
const colType = colDef?.$type ?? 'TEXT';
|
|
142
|
+
propToDb.set(propName, dbColName);
|
|
143
|
+
dbToProp.set(dbColName, propName);
|
|
144
|
+
propToType.set(propName, colType);
|
|
145
|
+
debugLog(this.config, `buildColumnMappings: ${tableName}.${propName} -> type=${colType}, $validate=${!!colDef?.$validate}, $fields=${!!colDef?.$fields}, $checkValues=${!!colDef?.$checkValues}`);
|
|
146
|
+
if (colDef?.$checkValues && Array.isArray(colDef.$checkValues)) {
|
|
147
|
+
propToCheckValues.set(propName, colDef.$checkValues);
|
|
148
|
+
}
|
|
149
|
+
if (colDef?.$validate && typeof colDef.$validate === 'function') {
|
|
150
|
+
propToValidate.set(propName, colDef.$validate);
|
|
151
|
+
}
|
|
152
|
+
if (colDef?.$fields && typeof colDef.$fields === 'object') {
|
|
153
|
+
propToFields.set(propName, colDef.$fields);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const dbTableName = tableDef.$name ?? tableName;
|
|
157
|
+
this.columnMappings.set(dbTableName, {
|
|
158
|
+
propToDb,
|
|
159
|
+
dbToProp,
|
|
160
|
+
propToType,
|
|
161
|
+
propToCheckValues,
|
|
162
|
+
propToValidate,
|
|
163
|
+
propToFields
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
_transformToDbColumns(tableName, data) {
|
|
168
|
+
const mapping = this.columnMappings.get(tableName);
|
|
169
|
+
if (!mapping)
|
|
170
|
+
return data;
|
|
171
|
+
const result = {};
|
|
172
|
+
for (const [key, value] of Object.entries(data)) {
|
|
173
|
+
const dbColName = mapping.propToDb.get(key) ?? key;
|
|
174
|
+
const colType = mapping.propToType.get(key);
|
|
175
|
+
if (colType && typeof colType === 'string') {
|
|
176
|
+
result[dbColName] = (0, type_coercion_1.serializeValue)(value, colType);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
result[dbColName] = value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
_transformFromDbColumns(tableName, data) {
|
|
185
|
+
const mapping = this.columnMappings.get(tableName);
|
|
186
|
+
if (!mapping)
|
|
187
|
+
return data;
|
|
188
|
+
const result = {};
|
|
189
|
+
for (const [key, value] of Object.entries(data)) {
|
|
190
|
+
const propName = mapping.dbToProp.get(key) ?? key;
|
|
191
|
+
const colType = mapping.propToType.get(propName);
|
|
192
|
+
if (colType && typeof colType === 'string') {
|
|
193
|
+
result[propName] = (0, type_coercion_1.deserializeValue)(value, colType);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
result[propName] = value;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
_transformResultsFromDb(tableName, rows) {
|
|
202
|
+
const mapping = this.columnMappings.get(tableName);
|
|
203
|
+
if (!mapping)
|
|
204
|
+
return rows;
|
|
205
|
+
return rows.map(row => this._transformFromDbColumns(tableName, row));
|
|
206
|
+
}
|
|
207
|
+
_hasColumnMapping() {
|
|
208
|
+
return this.columnMappings.size > 0;
|
|
209
|
+
}
|
|
210
|
+
_validateData(tableName, data, operation) {
|
|
211
|
+
const validation = this.config.validation;
|
|
212
|
+
if (validation?.enabled === false)
|
|
213
|
+
return;
|
|
214
|
+
const mapping = this.columnMappings.get(tableName);
|
|
215
|
+
if (!mapping) {
|
|
216
|
+
debugLog(this.config, `No mapping found for table: ${tableName}. Available:`, Array.from(this.columnMappings.keys()));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const errors = [];
|
|
220
|
+
const validateLength = validation?.validateLength !== false;
|
|
221
|
+
const validateTypes = validation?.validateTypes !== false;
|
|
222
|
+
const onError = validation?.onError ?? 'throw';
|
|
223
|
+
debugLog(this.config, `Validating ${operation} on ${tableName}. Fields:`, Object.keys(data));
|
|
224
|
+
for (const [propName, value] of Object.entries(data)) {
|
|
225
|
+
if (value === null || value === undefined)
|
|
226
|
+
continue;
|
|
227
|
+
const colType = mapping.propToType.get(propName);
|
|
228
|
+
const colTypeStr = typeof colType === 'string' ? colType : null;
|
|
229
|
+
if (validateTypes && colTypeStr) {
|
|
230
|
+
const jsType = typeof value;
|
|
231
|
+
const upperType = colTypeStr.toUpperCase();
|
|
232
|
+
if (['INTEGER', 'INT', 'SMALLINT', 'BIGINT', 'INT2', 'INT4', 'INT8', 'SERIAL'].some(t => upperType.includes(t))) {
|
|
233
|
+
if (jsType !== 'number' && jsType !== 'bigint' && jsType !== 'string') {
|
|
234
|
+
errors.push(`${propName}: expected number for ${colType}, got ${jsType}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (['REAL', 'DOUBLE', 'FLOAT', 'NUMERIC', 'DECIMAL'].some(t => upperType.includes(t))) {
|
|
238
|
+
if (jsType !== 'number' && jsType !== 'string') {
|
|
239
|
+
errors.push(`${propName}: expected number for ${colType}, got ${jsType}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (upperType.includes('BOOL')) {
|
|
243
|
+
if (jsType !== 'boolean') {
|
|
244
|
+
errors.push(`${propName}: expected boolean for ${colType}, got ${jsType}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (['VARCHAR', 'CHAR', 'TEXT', 'UUID'].some(t => upperType.includes(t))) {
|
|
248
|
+
if (jsType !== 'string') {
|
|
249
|
+
errors.push(`${propName}: expected string for ${colType}, got ${jsType}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (validateLength && typeof value === 'string' && colTypeStr) {
|
|
254
|
+
const lengthMatch = colTypeStr.match(/(?:var)?char\((\d+)\)/i);
|
|
255
|
+
if (lengthMatch) {
|
|
256
|
+
const maxLength = parseInt(lengthMatch[1], 10);
|
|
257
|
+
if (value.length > maxLength) {
|
|
258
|
+
errors.push(`${propName}: string length ${value.length} exceeds ${colTypeStr} limit of ${maxLength}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const validateFn = mapping.propToValidate.get(propName);
|
|
263
|
+
if (validateFn) {
|
|
264
|
+
const isValid = validateFn(value);
|
|
265
|
+
debugLog(this.config, `Domain validation for '${propName}' with value '${value}': isValid=${isValid}`);
|
|
266
|
+
if (!isValid) {
|
|
267
|
+
errors.push(`${propName}: value '${value}' failed domain validation`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const compositeFields = mapping.propToFields.get(propName);
|
|
271
|
+
if (compositeFields && value && typeof value === 'object') {
|
|
272
|
+
this._validateComposite(propName, value, compositeFields, errors);
|
|
273
|
+
}
|
|
274
|
+
const checkValues = mapping.propToCheckValues.get(propName);
|
|
275
|
+
if (checkValues && checkValues.length > 0 && typeof value === 'string') {
|
|
276
|
+
if (!checkValues.includes(value)) {
|
|
277
|
+
errors.push(`${propName}: value '${value}' not in allowed values [${checkValues.join(', ')}]`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (errors.length > 0) {
|
|
282
|
+
const message = `Validation failed for ${operation} on ${tableName}:\n - ${errors.join('\n - ')}`;
|
|
283
|
+
debugLog(this.config, message);
|
|
284
|
+
if (onError === 'throw') {
|
|
285
|
+
throw new relq_errors_1.RelqQueryError(message);
|
|
286
|
+
}
|
|
287
|
+
else if (onError === 'warn') {
|
|
288
|
+
console.warn('[Relq]', message);
|
|
289
|
+
}
|
|
290
|
+
else if (onError === 'log') {
|
|
291
|
+
console.log('[Relq]', message);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
_validateComposite(propName, value, fields, errors) {
|
|
296
|
+
const validation = this.config.validation;
|
|
297
|
+
const validateLength = validation?.validateLength !== false;
|
|
298
|
+
const validateTypes = validation?.validateTypes !== false;
|
|
299
|
+
for (const [fieldName, colDef] of Object.entries(fields)) {
|
|
300
|
+
const colConfig = colDef;
|
|
301
|
+
const fieldValue = value[fieldName];
|
|
302
|
+
const colType = colConfig.$sqlType ?? colConfig.$config?.$type ?? (typeof colConfig.$type === 'string' ? colConfig.$type : 'TEXT');
|
|
303
|
+
const upperType = colType.toUpperCase();
|
|
304
|
+
const isNullable = colConfig.$config?.$nullable ?? colConfig.$nullable;
|
|
305
|
+
debugLog(this.config, `Composite field '${propName}.${fieldName}': value=${JSON.stringify(fieldValue)}, colType=${colType}, nullable=${isNullable}`);
|
|
306
|
+
if (fieldValue === null || fieldValue === undefined) {
|
|
307
|
+
if (isNullable === false) {
|
|
308
|
+
errors.push(`${propName}.${fieldName}: cannot be null`);
|
|
309
|
+
}
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (validateTypes && upperType) {
|
|
313
|
+
const jsType = typeof fieldValue;
|
|
314
|
+
if (['INTEGER', 'INT', 'SMALLINT', 'BIGINT', 'SERIAL'].some(t => upperType.includes(t))) {
|
|
315
|
+
if (jsType !== 'number' && jsType !== 'bigint' && jsType !== 'string') {
|
|
316
|
+
errors.push(`${propName}.${fieldName}: expected number, got ${jsType}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (['VARCHAR', 'CHAR', 'TEXT'].some(t => upperType.includes(t))) {
|
|
320
|
+
if (jsType !== 'string') {
|
|
321
|
+
errors.push(`${propName}.${fieldName}: expected string, got ${jsType}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (validateLength && typeof fieldValue === 'string' && typeof colType === 'string') {
|
|
326
|
+
const lengthMatch = colType.match(/(?:var)?char\((\d+)\)/i);
|
|
327
|
+
if (lengthMatch) {
|
|
328
|
+
const maxLength = parseInt(lengthMatch[1], 10);
|
|
329
|
+
if (fieldValue.length > maxLength) {
|
|
330
|
+
errors.push(`${propName}.${fieldName}: length ${fieldValue.length} exceeds ${maxLength}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (colConfig.$validate && typeof colConfig.$validate === 'function') {
|
|
335
|
+
if (!colConfig.$validate(fieldValue)) {
|
|
336
|
+
errors.push(`${propName}.${fieldName}: failed domain validation`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (colConfig.$fields && fieldValue && typeof fieldValue === 'object') {
|
|
340
|
+
this._validateComposite(`${propName}.${fieldName}`, fieldValue, colConfig.$fields, errors);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async _getClientForCursor() {
|
|
345
|
+
await this.initialize();
|
|
346
|
+
if (this.pool) {
|
|
347
|
+
const client = await this.pool.connect();
|
|
348
|
+
return {
|
|
349
|
+
client,
|
|
350
|
+
release: () => client.release()
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
else if (this.client) {
|
|
354
|
+
return {
|
|
355
|
+
client: this.client,
|
|
356
|
+
release: () => { }
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
throw new relq_errors_1.RelqConnectionError('No database connection available');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
get [INTERNAL]() {
|
|
364
|
+
return {
|
|
365
|
+
executeSelect: this._executeSelect.bind(this),
|
|
366
|
+
executeSelectOne: this._executeSelectOne.bind(this),
|
|
367
|
+
executeCount: this._executeCount.bind(this),
|
|
368
|
+
executeRun: this._executeRun.bind(this),
|
|
369
|
+
executeQuery: this._executeQuery.bind(this),
|
|
370
|
+
transformToDbColumns: this._transformToDbColumns.bind(this),
|
|
371
|
+
transformFromDbColumns: this._transformFromDbColumns.bind(this),
|
|
372
|
+
transformResultsFromDb: this._transformResultsFromDb.bind(this),
|
|
373
|
+
hasColumnMapping: this._hasColumnMapping.bind(this),
|
|
374
|
+
getClientForCursor: this._getClientForCursor.bind(this),
|
|
375
|
+
validateData: this._validateData.bind(this)
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
determinePoolingStrategy(config) {
|
|
379
|
+
if (typeof config.pooling === 'boolean') {
|
|
380
|
+
return config.pooling;
|
|
381
|
+
}
|
|
382
|
+
if (config.disableSmartDefaults) {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
return this.environment.type === 'traditional';
|
|
386
|
+
}
|
|
387
|
+
validateConfiguration() {
|
|
388
|
+
if (this.config.disableSmartDefaults)
|
|
389
|
+
return;
|
|
390
|
+
if (this.environment.type === 'edge') {
|
|
391
|
+
throw new relq_errors_1.RelqEnvironmentError('Cannot use Relq with database connections in edge runtime.\n' +
|
|
392
|
+
'Edge runtimes (Cloudflare Workers, Deno Deploy) do not support TCP connections.\n\n' +
|
|
393
|
+
'Options:\n' +
|
|
394
|
+
'1. Use query builder only: relq("table").select().toString()\n' +
|
|
395
|
+
'2. Use HTTP-based database (Supabase, Neon, Xata)\n' +
|
|
396
|
+
'3. Deploy to Node.js runtime', (0, environment_detection_1.getEnvironmentDescription)(this.environment), 'No TCP/PostgreSQL support');
|
|
397
|
+
}
|
|
398
|
+
const poolConfig = {
|
|
399
|
+
min: this.config.pool?.min,
|
|
400
|
+
max: this.config.pool?.max
|
|
401
|
+
};
|
|
402
|
+
const validation = (0, pool_defaults_1.validatePoolConfig)(poolConfig, this.environment);
|
|
403
|
+
if (!validation.valid && validation.errors.length > 0) {
|
|
404
|
+
const logLevel = this.config.logLevel ?? 'info';
|
|
405
|
+
if (logLevel !== 'silent') {
|
|
406
|
+
console.error('\n' + validation.errors.join('\n\n'));
|
|
407
|
+
}
|
|
408
|
+
throw new relq_errors_1.RelqConfigError(validation.errors[0]);
|
|
409
|
+
}
|
|
410
|
+
if (validation.warnings.length > 0) {
|
|
411
|
+
const logLevel = this.config.logLevel ?? 'info';
|
|
412
|
+
if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug') {
|
|
413
|
+
console.warn('\n' + validation.warnings.join('\n\n'));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
logEnvironmentInfo() {
|
|
418
|
+
const logLevel = this.config.logLevel ?? 'info';
|
|
419
|
+
if (logLevel === 'silent' || logLevel === 'error')
|
|
420
|
+
return;
|
|
421
|
+
const smartDefaults = (0, pool_defaults_1.getSmartPoolDefaults)();
|
|
422
|
+
const poolConfig = (0, pool_defaults_1.formatPoolConfig)({
|
|
423
|
+
min: this.config.pool?.min ?? smartDefaults.min,
|
|
424
|
+
max: this.config.pool?.max ?? smartDefaults.max,
|
|
425
|
+
idleTimeoutMillis: this.config.pool?.idleTimeoutMillis ?? smartDefaults.idleTimeoutMillis,
|
|
426
|
+
connectionTimeoutMillis: this.config.pool?.connectionTimeoutMillis ?? smartDefaults.connectionTimeoutMillis
|
|
427
|
+
});
|
|
428
|
+
if (this.environment.type === 'serverless') {
|
|
429
|
+
if (logLevel === 'info' || logLevel === 'debug') {
|
|
430
|
+
console.log(`\n🔵 Relq: ${this.environment.provider} detected (serverless)\n` +
|
|
431
|
+
` Pooling: ${this.usePooling ? 'enabled' : 'disabled'}\n` +
|
|
432
|
+
` Pool config: ${poolConfig}\n` +
|
|
433
|
+
` Recommendation: Use min: 0, max: 1 for serverless`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else if (this.environment.type === 'traditional') {
|
|
437
|
+
if (logLevel === 'debug') {
|
|
438
|
+
console.log(`\n🟢 Relq: Traditional server environment\n` +
|
|
439
|
+
` Pooling: ${this.usePooling ? 'enabled' : 'disabled'}\n` +
|
|
440
|
+
` Pool config: ${poolConfig}\n` +
|
|
441
|
+
` Connections created on demand, scale up under load`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async initialize() {
|
|
446
|
+
if (this.initialized)
|
|
447
|
+
return;
|
|
448
|
+
if (this.initPromise) {
|
|
449
|
+
return this.initPromise;
|
|
450
|
+
}
|
|
451
|
+
this.initPromise = (async () => {
|
|
452
|
+
const { Pool: PgPoolClass, Client: PgClientClass } = await loadPg();
|
|
453
|
+
if (this.usePooling) {
|
|
454
|
+
this.pool = new PgPoolClass((0, config_types_1.toPoolConfig)(this.config));
|
|
455
|
+
this.setupPoolErrorHandling();
|
|
456
|
+
this.pool.on('connect', (client) => {
|
|
457
|
+
this.emitter.emit('connect', client);
|
|
458
|
+
this.emitter.emit('acquire', client);
|
|
459
|
+
});
|
|
460
|
+
this.pool.on('error', (err) => this.emitter.emit('error', err));
|
|
461
|
+
this.pool.on('remove', (client) => this.emitter.emit('remove', client));
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
this.client = new PgClientClass({
|
|
465
|
+
host: this.config.host || 'localhost',
|
|
466
|
+
port: this.config.port || 5432,
|
|
467
|
+
database: this.config.database,
|
|
468
|
+
user: this.config.user,
|
|
469
|
+
password: this.config.password,
|
|
470
|
+
connectionString: this.config.connectionString,
|
|
471
|
+
ssl: this.config.ssl
|
|
472
|
+
});
|
|
473
|
+
this.client.on('end', () => this.emitter.emit('end'));
|
|
474
|
+
this.client.on('error', (err) => this.emitter.emit('error', err));
|
|
475
|
+
this.client.on('notice', (msg) => this.emitter.emit('notice', msg));
|
|
476
|
+
this.client.on('notification', (msg) => this.emitter.emit('notification', msg));
|
|
477
|
+
}
|
|
478
|
+
this.initialized = true;
|
|
479
|
+
})();
|
|
480
|
+
return this.initPromise;
|
|
481
|
+
}
|
|
482
|
+
setupPoolErrorHandling() {
|
|
483
|
+
if (!this.pool)
|
|
484
|
+
return;
|
|
485
|
+
this.poolErrorHandler = (err) => {
|
|
486
|
+
const errorCode = err.code;
|
|
487
|
+
const recoverableErrors = [
|
|
488
|
+
'ETIMEDOUT',
|
|
489
|
+
'ECONNRESET',
|
|
490
|
+
'57P02'
|
|
491
|
+
];
|
|
492
|
+
if (!recoverableErrors.includes(errorCode)) {
|
|
493
|
+
throw err;
|
|
494
|
+
}
|
|
495
|
+
console.warn('[Relq Pool] Recoverable connection error (pool will auto-recover):', {
|
|
496
|
+
code: errorCode,
|
|
497
|
+
message: err.message,
|
|
498
|
+
action: 'Connection removed from pool, will be replaced on next query'
|
|
499
|
+
});
|
|
500
|
+
};
|
|
501
|
+
this.poolConnectHandler = () => {
|
|
502
|
+
};
|
|
503
|
+
this.poolRemoveHandler = () => {
|
|
504
|
+
};
|
|
505
|
+
this.pool.on('error', this.poolErrorHandler);
|
|
506
|
+
this.pool.on('connect', this.poolConnectHandler);
|
|
507
|
+
this.pool.on('remove', this.poolRemoveHandler);
|
|
508
|
+
}
|
|
509
|
+
async connect() {
|
|
510
|
+
await this.initialize();
|
|
511
|
+
if (!this.usePooling && this.client) {
|
|
512
|
+
await this.client.connect();
|
|
513
|
+
this.emitter.emit('connect', this.client);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
async subscribe(channel, callback) {
|
|
517
|
+
if (!this.listener) {
|
|
518
|
+
this.listener = new listener_connection_1.ListenerConnection({
|
|
519
|
+
host: this.config.host,
|
|
520
|
+
port: this.config.port,
|
|
521
|
+
user: this.config.user,
|
|
522
|
+
password: this.config.password,
|
|
523
|
+
database: this.config.database,
|
|
524
|
+
connectionString: this.config.connectionString,
|
|
525
|
+
ssl: this.config.ssl
|
|
526
|
+
});
|
|
527
|
+
this.listener.on('error', (err) => {
|
|
528
|
+
this.emitter.emit('listenerError', err);
|
|
529
|
+
});
|
|
530
|
+
this.listener.on('connect', () => {
|
|
531
|
+
this.emitter.emit('listenerConnect');
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const sub = await this.listener.subscribe(channel);
|
|
535
|
+
sub.on('notification', callback);
|
|
536
|
+
return () => sub.close();
|
|
537
|
+
}
|
|
538
|
+
async close() {
|
|
539
|
+
if (!this.initialized || this.isClosed)
|
|
540
|
+
return;
|
|
541
|
+
try {
|
|
542
|
+
if (this.listener) {
|
|
543
|
+
await this.listener.close();
|
|
544
|
+
this.listener = null;
|
|
545
|
+
}
|
|
546
|
+
if (this.pool) {
|
|
547
|
+
if (this.poolErrorHandler) {
|
|
548
|
+
this.pool.removeListener('error', this.poolErrorHandler);
|
|
549
|
+
}
|
|
550
|
+
if (this.poolConnectHandler) {
|
|
551
|
+
this.pool.removeListener('connect', this.poolConnectHandler);
|
|
552
|
+
}
|
|
553
|
+
if (this.poolRemoveHandler) {
|
|
554
|
+
this.pool.removeListener('remove', this.poolRemoveHandler);
|
|
555
|
+
}
|
|
556
|
+
this.pool.removeAllListeners();
|
|
557
|
+
await this.pool.end();
|
|
558
|
+
}
|
|
559
|
+
else if (this.client) {
|
|
560
|
+
this.client.removeAllListeners();
|
|
561
|
+
await this.client.end();
|
|
562
|
+
}
|
|
563
|
+
this.isClosed = true;
|
|
564
|
+
activeRelqInstances.delete(this);
|
|
565
|
+
}
|
|
566
|
+
catch (error) {
|
|
567
|
+
console.error('Error closing database connection:', error);
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
get closed() {
|
|
572
|
+
return this.isClosed;
|
|
573
|
+
}
|
|
574
|
+
on(event, listener) {
|
|
575
|
+
this.emitter.on(event, listener);
|
|
576
|
+
return this;
|
|
577
|
+
}
|
|
578
|
+
once(event, listener) {
|
|
579
|
+
this.emitter.once(event, listener);
|
|
580
|
+
return this;
|
|
581
|
+
}
|
|
582
|
+
off(event, listener) {
|
|
583
|
+
this.emitter.off(event, listener);
|
|
584
|
+
return this;
|
|
585
|
+
}
|
|
586
|
+
async _executeQuery(sql) {
|
|
587
|
+
try {
|
|
588
|
+
if (!sql || typeof sql !== 'string' || sql.trim() === '') {
|
|
589
|
+
throw new relq_errors_1.RelqConfigError(`Invalid SQL query: ${sql === null ? 'null' : sql === undefined ? 'undefined' : 'empty string'}`);
|
|
590
|
+
}
|
|
591
|
+
await this.initialize();
|
|
592
|
+
const startTime = performance.now();
|
|
593
|
+
let result;
|
|
594
|
+
if (this.pool) {
|
|
595
|
+
result = await this.pool.query(sql);
|
|
596
|
+
}
|
|
597
|
+
else if (this.client) {
|
|
598
|
+
result = await this.client.query(sql);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
throw new relq_errors_1.RelqConfigError('No database connection available');
|
|
602
|
+
}
|
|
603
|
+
const duration = performance.now() - startTime;
|
|
604
|
+
return { result, duration };
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
throw (0, relq_errors_1.parsePostgresError)(error, sql);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
buildMetadata(result, duration) {
|
|
611
|
+
return {
|
|
612
|
+
rowCount: result.rowCount,
|
|
613
|
+
command: result.command,
|
|
614
|
+
duration,
|
|
615
|
+
fields: result.fields
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
async _executeSelect(sql, tableName) {
|
|
619
|
+
const { result, duration } = await this._executeQuery(sql);
|
|
620
|
+
const rows = tableName
|
|
621
|
+
? this._transformResultsFromDb(tableName, result.rows)
|
|
622
|
+
: result.rows;
|
|
623
|
+
return {
|
|
624
|
+
data: rows,
|
|
625
|
+
metadata: this.buildMetadata(result, duration)
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
async _executeSelectOne(sql, tableName) {
|
|
629
|
+
const { result, duration } = await this._executeQuery(sql);
|
|
630
|
+
const row = result.rows[0]
|
|
631
|
+
? (tableName ? this._transformFromDbColumns(tableName, result.rows[0]) : result.rows[0])
|
|
632
|
+
: null;
|
|
633
|
+
return {
|
|
634
|
+
data: row,
|
|
635
|
+
metadata: this.buildMetadata(result, duration)
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
async _executeCount(sql) {
|
|
639
|
+
const { result, duration } = await this._executeQuery(sql);
|
|
640
|
+
const count = result.rows[0]?.count
|
|
641
|
+
? parseInt(result.rows[0].count, 10)
|
|
642
|
+
: 0;
|
|
643
|
+
return {
|
|
644
|
+
count,
|
|
645
|
+
metadata: this.buildMetadata(result, duration)
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
async _executeRun(sql) {
|
|
649
|
+
const { result, duration } = await this._executeQuery(sql);
|
|
650
|
+
return {
|
|
651
|
+
success: true,
|
|
652
|
+
metadata: this.buildMetadata(result, duration)
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
get table() {
|
|
656
|
+
const relq = this;
|
|
657
|
+
const tableFunction = (tableName) => {
|
|
658
|
+
return new ConnectedQueryBuilder(tableName, relq);
|
|
659
|
+
};
|
|
660
|
+
return new Proxy(tableFunction, {
|
|
661
|
+
get(target, prop, receiver) {
|
|
662
|
+
if (prop in target) {
|
|
663
|
+
return Reflect.get(target, prop, receiver);
|
|
664
|
+
}
|
|
665
|
+
if (typeof prop === 'string' && relq.schema && prop in relq.schema) {
|
|
666
|
+
const tableDef = relq.schema[prop];
|
|
667
|
+
const sqlTableName = tableDef?.$name || prop;
|
|
668
|
+
return new ConnectedQueryBuilder(sqlTableName, relq);
|
|
669
|
+
}
|
|
670
|
+
return undefined;
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
raw(query, ...params) {
|
|
675
|
+
return new ConnectedRawQueryBuilder(query, params, this);
|
|
676
|
+
}
|
|
677
|
+
transaction() {
|
|
678
|
+
return new ConnectedTransactionBuilder(this);
|
|
679
|
+
}
|
|
680
|
+
with(name, query) {
|
|
681
|
+
return new ConnectedCTEBuilder(this).with(name, query);
|
|
682
|
+
}
|
|
683
|
+
async ctAs(tableName, query, options = {}) {
|
|
684
|
+
const queryStr = typeof query === 'string' ? query : query.toString();
|
|
685
|
+
const temp = options.temporary ? 'TEMPORARY ' : '';
|
|
686
|
+
const ifNotExists = options.ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
687
|
+
const sql = `CREATE ${temp}TABLE ${ifNotExists}"${tableName}" AS ${queryStr}`;
|
|
688
|
+
await this[INTERNAL].executeQuery(sql);
|
|
689
|
+
}
|
|
690
|
+
async explain(query, options = {}) {
|
|
691
|
+
const queryStr = typeof query === 'string' ? query : query.toString();
|
|
692
|
+
const parts = ['EXPLAIN'];
|
|
693
|
+
if (options.format)
|
|
694
|
+
parts.push(`(FORMAT ${options.format.toUpperCase()}`);
|
|
695
|
+
if (options.analyze)
|
|
696
|
+
parts.push(options.format ? ', ANALYZE' : '(ANALYZE');
|
|
697
|
+
if (options.verbose)
|
|
698
|
+
parts.push(options.format || options.analyze ? ', VERBOSE' : '(VERBOSE');
|
|
699
|
+
if (options.format || options.analyze || options.verbose)
|
|
700
|
+
parts.push(')');
|
|
701
|
+
const sql = `${parts.join(' ')} ${queryStr}`;
|
|
702
|
+
const result = await this[INTERNAL].executeQuery(sql);
|
|
703
|
+
if (options.format === 'json') {
|
|
704
|
+
return result.result.rows;
|
|
705
|
+
}
|
|
706
|
+
return result.result.rows.map((r) => r['QUERY PLAN'] || Object.values(r)[0]).join('\n');
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
exports.Relq = Relq;
|
|
710
|
+
class ConnectedQueryBuilder {
|
|
711
|
+
tableName;
|
|
712
|
+
relq;
|
|
713
|
+
constructor(tableName, relq) {
|
|
714
|
+
this.tableName = tableName;
|
|
715
|
+
this.relq = relq;
|
|
716
|
+
}
|
|
717
|
+
select(columns) {
|
|
718
|
+
let dbColumns = columns;
|
|
719
|
+
if (columns && this.relq[INTERNAL].hasColumnMapping()) {
|
|
720
|
+
if (Array.isArray(columns)) {
|
|
721
|
+
dbColumns = columns.map(col => {
|
|
722
|
+
if (Array.isArray(col)) {
|
|
723
|
+
const transformed = this.relq[INTERNAL].transformToDbColumns(this.tableName, { [col[0]]: true });
|
|
724
|
+
return [Object.keys(transformed)[0], col[1]];
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
const transformed = this.relq[INTERNAL].transformToDbColumns(this.tableName, { [col]: true });
|
|
728
|
+
return Object.keys(transformed)[0];
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
const transformed = this.relq[INTERNAL].transformToDbColumns(this.tableName, { [columns]: true });
|
|
734
|
+
dbColumns = Object.keys(transformed)[0];
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const builder = new select_builder_1.SelectBuilder(this.tableName, dbColumns);
|
|
738
|
+
return new ConnectedSelectBuilder(builder, this.relq, this.tableName, columns);
|
|
739
|
+
}
|
|
740
|
+
insert(data) {
|
|
741
|
+
const builder = new insert_builder_1.InsertBuilder(this.tableName, data);
|
|
742
|
+
return new ConnectedInsertBuilder(builder, this.relq);
|
|
743
|
+
}
|
|
744
|
+
update(data) {
|
|
745
|
+
const builder = new update_builder_1.UpdateBuilder(this.tableName, data);
|
|
746
|
+
return new ConnectedUpdateBuilder(builder, this.relq);
|
|
747
|
+
}
|
|
748
|
+
delete() {
|
|
749
|
+
const builder = new delete_builder_1.DeleteBuilder(this.tableName);
|
|
750
|
+
return new ConnectedDeleteBuilder(builder, this.relq);
|
|
751
|
+
}
|
|
752
|
+
count() {
|
|
753
|
+
const builder = new count_builder_1.CountBuilder(this.tableName);
|
|
754
|
+
return new ConnectedCountBuilder(builder, this.relq, this.tableName);
|
|
755
|
+
}
|
|
756
|
+
aggregate() {
|
|
757
|
+
const builder = new aggregate_builder_1.AggregateQueryBuilder(this.tableName);
|
|
758
|
+
return new ConnectedAggregateBuilder(builder, this.relq, this.tableName);
|
|
759
|
+
}
|
|
760
|
+
async findById(id) {
|
|
761
|
+
const pkColumn = this.getPrimaryKeyColumn();
|
|
762
|
+
const dbColumn = this.relq[INTERNAL].transformToDbColumns(this.tableName, { [pkColumn]: id });
|
|
763
|
+
const dbColName = Object.keys(dbColumn)[0];
|
|
764
|
+
const builder = new select_builder_1.SelectBuilder(this.tableName, ['*']);
|
|
765
|
+
builder.where(q => q.equal(dbColName, id));
|
|
766
|
+
builder.limit(1);
|
|
767
|
+
const sql = builder.toString();
|
|
768
|
+
const result = await this.relq[INTERNAL].executeSelectOne(sql, this.tableName);
|
|
769
|
+
return result.data;
|
|
770
|
+
}
|
|
771
|
+
async findOne(filter) {
|
|
772
|
+
const builder = new select_builder_1.SelectBuilder(this.tableName, ['*']);
|
|
773
|
+
const dbFilter = this.relq[INTERNAL].transformToDbColumns(this.tableName, filter);
|
|
774
|
+
builder.where(q => {
|
|
775
|
+
for (const [col, val] of Object.entries(dbFilter)) {
|
|
776
|
+
q.equal(col, val);
|
|
777
|
+
}
|
|
778
|
+
return q;
|
|
779
|
+
});
|
|
780
|
+
builder.limit(1);
|
|
781
|
+
const sql = builder.toString();
|
|
782
|
+
const result = await this.relq[INTERNAL].executeSelectOne(sql, this.tableName);
|
|
783
|
+
return result.data;
|
|
784
|
+
}
|
|
785
|
+
async findMany(filter) {
|
|
786
|
+
const builder = new select_builder_1.SelectBuilder(this.tableName, ['*']);
|
|
787
|
+
const dbFilter = this.relq[INTERNAL].transformToDbColumns(this.tableName, filter);
|
|
788
|
+
builder.where(q => {
|
|
789
|
+
for (const [col, val] of Object.entries(dbFilter)) {
|
|
790
|
+
q.equal(col, val);
|
|
791
|
+
}
|
|
792
|
+
return q;
|
|
793
|
+
});
|
|
794
|
+
const sql = builder.toString();
|
|
795
|
+
const result = await this.relq[INTERNAL].executeSelect(sql, this.tableName);
|
|
796
|
+
return result.data;
|
|
797
|
+
}
|
|
798
|
+
async exists(filter) {
|
|
799
|
+
const builder = new count_builder_1.CountBuilder(this.tableName);
|
|
800
|
+
const dbFilter = this.relq[INTERNAL].transformToDbColumns(this.tableName, filter);
|
|
801
|
+
builder.where(q => {
|
|
802
|
+
for (const [col, val] of Object.entries(dbFilter)) {
|
|
803
|
+
q.equal(col, val);
|
|
804
|
+
}
|
|
805
|
+
return q;
|
|
806
|
+
});
|
|
807
|
+
const sql = builder.toString();
|
|
808
|
+
const result = await this.relq[INTERNAL].executeCount(sql);
|
|
809
|
+
return result.count > 0;
|
|
810
|
+
}
|
|
811
|
+
async upsert(options) {
|
|
812
|
+
const dbCreate = this.relq[INTERNAL].transformToDbColumns(this.tableName, options.create);
|
|
813
|
+
const dbUpdate = this.relq[INTERNAL].transformToDbColumns(this.tableName, options.update);
|
|
814
|
+
const dbWhere = this.relq[INTERNAL].transformToDbColumns(this.tableName, options.where);
|
|
815
|
+
const conflictColumn = Object.keys(dbWhere)[0];
|
|
816
|
+
const builder = new insert_builder_1.InsertBuilder(this.tableName, dbCreate);
|
|
817
|
+
builder.onConflict(conflictColumn, cb => cb.doUpdate(dbUpdate));
|
|
818
|
+
builder.returning(['*']);
|
|
819
|
+
const sql = builder.toString();
|
|
820
|
+
const result = await this.relq[INTERNAL].executeQuery(sql);
|
|
821
|
+
const transformed = this.relq[INTERNAL].transformResultsFromDb(this.tableName, result.result.rows);
|
|
822
|
+
return transformed[0];
|
|
823
|
+
}
|
|
824
|
+
async insertMany(rows) {
|
|
825
|
+
if (rows.length === 0)
|
|
826
|
+
return [];
|
|
827
|
+
const firstDbRow = this.relq[INTERNAL].transformToDbColumns(this.tableName, rows[0]);
|
|
828
|
+
const builder = new insert_builder_1.InsertBuilder(this.tableName, firstDbRow);
|
|
829
|
+
for (let i = 1; i < rows.length; i++) {
|
|
830
|
+
const dbRow = this.relq[INTERNAL].transformToDbColumns(this.tableName, rows[i]);
|
|
831
|
+
builder.addRow(dbRow);
|
|
832
|
+
}
|
|
833
|
+
builder.returning(['*']);
|
|
834
|
+
const sql = builder.toString();
|
|
835
|
+
const result = await this.relq[INTERNAL].executeQuery(sql);
|
|
836
|
+
return this.relq[INTERNAL].transformResultsFromDb(this.tableName, result.result.rows);
|
|
837
|
+
}
|
|
838
|
+
paginate(options = {}) {
|
|
839
|
+
return new PaginateBuilder(this.relq, this.tableName, options.columns, options.where, options.orderBy);
|
|
840
|
+
}
|
|
841
|
+
async softDelete(filter) {
|
|
842
|
+
const dbFilter = this.relq[INTERNAL].transformToDbColumns(this.tableName, filter);
|
|
843
|
+
const builder = new update_builder_1.UpdateBuilder(this.tableName, { deleted_at: new Date() });
|
|
844
|
+
builder.where(q => {
|
|
845
|
+
for (const [col, val] of Object.entries(dbFilter)) {
|
|
846
|
+
q.equal(col, val);
|
|
847
|
+
}
|
|
848
|
+
return q;
|
|
849
|
+
});
|
|
850
|
+
builder.returning(['*']);
|
|
851
|
+
const sql = builder.toString();
|
|
852
|
+
const result = await this.relq[INTERNAL].executeQuery(sql);
|
|
853
|
+
if (result.result.rows.length === 0)
|
|
854
|
+
return null;
|
|
855
|
+
const transformed = this.relq[INTERNAL].transformResultsFromDb(this.tableName, result.result.rows);
|
|
856
|
+
return transformed[0];
|
|
857
|
+
}
|
|
858
|
+
async restore(filter) {
|
|
859
|
+
const dbFilter = this.relq[INTERNAL].transformToDbColumns(this.tableName, filter);
|
|
860
|
+
const builder = new update_builder_1.UpdateBuilder(this.tableName, { deleted_at: null });
|
|
861
|
+
builder.where(q => {
|
|
862
|
+
for (const [col, val] of Object.entries(dbFilter)) {
|
|
863
|
+
q.equal(col, val);
|
|
864
|
+
}
|
|
865
|
+
return q;
|
|
866
|
+
});
|
|
867
|
+
builder.returning(['*']);
|
|
868
|
+
const sql = builder.toString();
|
|
869
|
+
const result = await this.relq[INTERNAL].executeQuery(sql);
|
|
870
|
+
if (result.result.rows.length === 0)
|
|
871
|
+
return null;
|
|
872
|
+
const transformed = this.relq[INTERNAL].transformResultsFromDb(this.tableName, result.result.rows);
|
|
873
|
+
return transformed[0];
|
|
874
|
+
}
|
|
875
|
+
getPrimaryKeyColumn() {
|
|
876
|
+
const schema = this.relq.schema;
|
|
877
|
+
if (schema && schema[this.tableName]) {
|
|
878
|
+
const tableSchema = schema[this.tableName];
|
|
879
|
+
for (const [colName, colDef] of Object.entries(tableSchema)) {
|
|
880
|
+
if (colDef && typeof colDef === 'object' &&
|
|
881
|
+
('$primaryKey' in colDef ||
|
|
882
|
+
colDef.config?.$primaryKey === true)) {
|
|
883
|
+
return colName;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return 'id';
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
class ConnectedAggregateBuilder {
|
|
891
|
+
builder;
|
|
892
|
+
relq;
|
|
893
|
+
tableName;
|
|
894
|
+
constructor(builder, relq, tableName) {
|
|
895
|
+
this.builder = builder;
|
|
896
|
+
this.relq = relq;
|
|
897
|
+
this.tableName = tableName;
|
|
898
|
+
}
|
|
899
|
+
mapColumn(column) {
|
|
900
|
+
const transformed = this.relq[INTERNAL].transformToDbColumns(this.tableName, { [column]: true });
|
|
901
|
+
return Object.keys(transformed)[0] || column;
|
|
902
|
+
}
|
|
903
|
+
groupBy(...columns) {
|
|
904
|
+
const dbColumns = columns.map(col => this.mapColumn(col));
|
|
905
|
+
this.builder.groupBy(...dbColumns);
|
|
906
|
+
return this;
|
|
907
|
+
}
|
|
908
|
+
count(alias) {
|
|
909
|
+
this.builder.count(alias);
|
|
910
|
+
return this;
|
|
911
|
+
}
|
|
912
|
+
countColumn(column, alias) {
|
|
913
|
+
this.builder.countColumn(this.mapColumn(column), alias);
|
|
914
|
+
return this;
|
|
915
|
+
}
|
|
916
|
+
countDistinct(column, alias) {
|
|
917
|
+
this.builder.countDistinct(this.mapColumn(column), alias);
|
|
918
|
+
return this;
|
|
919
|
+
}
|
|
920
|
+
sum(column, alias) {
|
|
921
|
+
this.builder.sum(this.mapColumn(column), alias);
|
|
922
|
+
return this;
|
|
923
|
+
}
|
|
924
|
+
avg(column, alias) {
|
|
925
|
+
this.builder.avg(this.mapColumn(column), alias);
|
|
926
|
+
return this;
|
|
927
|
+
}
|
|
928
|
+
min(column, alias) {
|
|
929
|
+
this.builder.min(this.mapColumn(column), alias);
|
|
930
|
+
return this;
|
|
931
|
+
}
|
|
932
|
+
max(column, alias) {
|
|
933
|
+
this.builder.max(this.mapColumn(column), alias);
|
|
934
|
+
return this;
|
|
935
|
+
}
|
|
936
|
+
arrayAgg(column, alias) {
|
|
937
|
+
this.builder.arrayAgg(this.mapColumn(column), alias);
|
|
938
|
+
return this;
|
|
939
|
+
}
|
|
940
|
+
stringAgg(column, delimiter, alias) {
|
|
941
|
+
this.builder.stringAgg(this.mapColumn(column), delimiter || ', ', alias);
|
|
942
|
+
return this;
|
|
943
|
+
}
|
|
944
|
+
jsonAgg(column, alias) {
|
|
945
|
+
this.builder.jsonAgg(this.mapColumn(column), alias);
|
|
946
|
+
return this;
|
|
947
|
+
}
|
|
948
|
+
jsonbAgg(column, alias) {
|
|
949
|
+
this.builder.jsonbAgg(this.mapColumn(column), alias);
|
|
950
|
+
return this;
|
|
951
|
+
}
|
|
952
|
+
boolAnd(column, alias) {
|
|
953
|
+
this.builder.boolAnd(this.mapColumn(column), alias);
|
|
954
|
+
return this;
|
|
955
|
+
}
|
|
956
|
+
boolOr(column, alias) {
|
|
957
|
+
this.builder.boolOr(this.mapColumn(column), alias);
|
|
958
|
+
return this;
|
|
959
|
+
}
|
|
960
|
+
where(callback) {
|
|
961
|
+
this.builder.where(callback);
|
|
962
|
+
return this;
|
|
963
|
+
}
|
|
964
|
+
having(callback) {
|
|
965
|
+
this.builder.having(callback);
|
|
966
|
+
return this;
|
|
967
|
+
}
|
|
968
|
+
orderBy(column, direction = 'ASC') {
|
|
969
|
+
this.builder.orderBy(this.mapColumn(column), direction);
|
|
970
|
+
return this;
|
|
971
|
+
}
|
|
972
|
+
limit(count) {
|
|
973
|
+
this.builder.limit(count);
|
|
974
|
+
return this;
|
|
975
|
+
}
|
|
976
|
+
offset(count) {
|
|
977
|
+
this.builder.offset(count);
|
|
978
|
+
return this;
|
|
979
|
+
}
|
|
980
|
+
toString() {
|
|
981
|
+
return this.builder.toString();
|
|
982
|
+
}
|
|
983
|
+
async all() {
|
|
984
|
+
const sql = this.builder.toString();
|
|
985
|
+
const result = await this.relq[INTERNAL].executeSelect(sql, this.tableName);
|
|
986
|
+
const numericAliases = this.builder.getNumericAliases();
|
|
987
|
+
if (numericAliases.length > 0) {
|
|
988
|
+
for (const row of result.data) {
|
|
989
|
+
for (const alias of numericAliases) {
|
|
990
|
+
if (alias in row && typeof row[alias] === 'string') {
|
|
991
|
+
row[alias] = Number(row[alias]);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return result.data;
|
|
997
|
+
}
|
|
998
|
+
async get() {
|
|
999
|
+
this.builder.limit(1);
|
|
1000
|
+
const sql = this.builder.toString();
|
|
1001
|
+
const result = await this.relq[INTERNAL].executeSelectOne(sql, this.tableName);
|
|
1002
|
+
const numericAliases = this.builder.getNumericAliases();
|
|
1003
|
+
if (numericAliases.length > 0 && result.data) {
|
|
1004
|
+
for (const alias of numericAliases) {
|
|
1005
|
+
if (alias in result.data && typeof result.data[alias] === 'string') {
|
|
1006
|
+
result.data[alias] = Number(result.data[alias]);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return result.data;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
class ConnectedSelectBuilder {
|
|
1014
|
+
builder;
|
|
1015
|
+
relq;
|
|
1016
|
+
tableName;
|
|
1017
|
+
columns;
|
|
1018
|
+
constructor(builder, relq, tableName, columns) {
|
|
1019
|
+
this.builder = builder;
|
|
1020
|
+
this.relq = relq;
|
|
1021
|
+
this.tableName = tableName;
|
|
1022
|
+
this.columns = columns;
|
|
1023
|
+
}
|
|
1024
|
+
where(callback) {
|
|
1025
|
+
this.builder.where(callback);
|
|
1026
|
+
return this;
|
|
1027
|
+
}
|
|
1028
|
+
orderBy(column, direction) {
|
|
1029
|
+
const dbColumn = this.relq[INTERNAL].hasColumnMapping()
|
|
1030
|
+
? Object.keys(this.relq[INTERNAL].transformToDbColumns(this.tableName, { [column]: true }))[0]
|
|
1031
|
+
: column;
|
|
1032
|
+
this.builder.orderBy(dbColumn, direction);
|
|
1033
|
+
return this;
|
|
1034
|
+
}
|
|
1035
|
+
limit(count) {
|
|
1036
|
+
this.builder.limit(count);
|
|
1037
|
+
return this;
|
|
1038
|
+
}
|
|
1039
|
+
offset(count) {
|
|
1040
|
+
this.builder.offset(count);
|
|
1041
|
+
return this;
|
|
1042
|
+
}
|
|
1043
|
+
groupBy(...columns) {
|
|
1044
|
+
const dbColumns = this.relq[INTERNAL].hasColumnMapping()
|
|
1045
|
+
? columns.map(col => Object.keys(this.relq[INTERNAL].transformToDbColumns(this.tableName, { [col]: true }))[0])
|
|
1046
|
+
: columns;
|
|
1047
|
+
this.builder.groupBy(...dbColumns);
|
|
1048
|
+
return this;
|
|
1049
|
+
}
|
|
1050
|
+
having(callback) {
|
|
1051
|
+
this.builder.having(callback);
|
|
1052
|
+
return this;
|
|
1053
|
+
}
|
|
1054
|
+
join(table, condition) {
|
|
1055
|
+
this.builder.join(table, condition);
|
|
1056
|
+
return this;
|
|
1057
|
+
}
|
|
1058
|
+
leftJoin(table, condition) {
|
|
1059
|
+
this.builder.leftJoin(table, condition);
|
|
1060
|
+
return this;
|
|
1061
|
+
}
|
|
1062
|
+
rightJoin(table, condition) {
|
|
1063
|
+
this.builder.rightJoin(table, condition);
|
|
1064
|
+
return this;
|
|
1065
|
+
}
|
|
1066
|
+
innerJoin(table, condition) {
|
|
1067
|
+
this.builder.innerJoin(table, condition);
|
|
1068
|
+
return this;
|
|
1069
|
+
}
|
|
1070
|
+
distinct() {
|
|
1071
|
+
this.builder.distinct();
|
|
1072
|
+
return this;
|
|
1073
|
+
}
|
|
1074
|
+
distinctOn(...columns) {
|
|
1075
|
+
this.builder.distinctOn(...columns);
|
|
1076
|
+
return this;
|
|
1077
|
+
}
|
|
1078
|
+
union(query) {
|
|
1079
|
+
this.builder.union(typeof query === 'string' ? query : query.toString());
|
|
1080
|
+
return this;
|
|
1081
|
+
}
|
|
1082
|
+
unionAll(query) {
|
|
1083
|
+
this.builder.unionAll(typeof query === 'string' ? query : query.toString());
|
|
1084
|
+
return this;
|
|
1085
|
+
}
|
|
1086
|
+
intersect(query) {
|
|
1087
|
+
this.builder.intersect(typeof query === 'string' ? query : query.toString());
|
|
1088
|
+
return this;
|
|
1089
|
+
}
|
|
1090
|
+
except(query) {
|
|
1091
|
+
this.builder.except(typeof query === 'string' ? query : query.toString());
|
|
1092
|
+
return this;
|
|
1093
|
+
}
|
|
1094
|
+
forUpdate() {
|
|
1095
|
+
this.builder.forUpdate();
|
|
1096
|
+
return this;
|
|
1097
|
+
}
|
|
1098
|
+
forUpdateSkipLocked() {
|
|
1099
|
+
this.builder.forUpdateSkipLocked();
|
|
1100
|
+
return this;
|
|
1101
|
+
}
|
|
1102
|
+
forShare() {
|
|
1103
|
+
this.builder.forShare();
|
|
1104
|
+
return this;
|
|
1105
|
+
}
|
|
1106
|
+
toString() {
|
|
1107
|
+
return this.builder.toString();
|
|
1108
|
+
}
|
|
1109
|
+
async all(withMetadata, _asRequired) {
|
|
1110
|
+
const sql = this.builder.toString();
|
|
1111
|
+
const result = await this.relq[INTERNAL].executeSelect(sql, this.tableName);
|
|
1112
|
+
if (withMetadata) {
|
|
1113
|
+
return result;
|
|
1114
|
+
}
|
|
1115
|
+
return result.data;
|
|
1116
|
+
}
|
|
1117
|
+
async get(withMetadata, _asRequired) {
|
|
1118
|
+
this.builder.limit(1);
|
|
1119
|
+
const sql = this.builder.toString();
|
|
1120
|
+
const result = await this.relq[INTERNAL].executeSelectOne(sql, this.tableName);
|
|
1121
|
+
if (withMetadata) {
|
|
1122
|
+
return result;
|
|
1123
|
+
}
|
|
1124
|
+
return result.data;
|
|
1125
|
+
}
|
|
1126
|
+
async value(column) {
|
|
1127
|
+
this.builder.limit(1);
|
|
1128
|
+
const sql = this.builder.toString();
|
|
1129
|
+
const result = await this.relq[INTERNAL].executeSelectOne(sql, this.tableName);
|
|
1130
|
+
return result.data?.[column] ?? null;
|
|
1131
|
+
}
|
|
1132
|
+
async each(callback, options = {}) {
|
|
1133
|
+
const batchSize = options.batchSize ?? 100;
|
|
1134
|
+
const sql = this.builder.toString();
|
|
1135
|
+
const relqAny = this.relq;
|
|
1136
|
+
if (relqAny.usePooling === false || (!relqAny.pool && relqAny.client)) {
|
|
1137
|
+
throw new relq_errors_1.RelqConfigError('each() requires pooled connections', { field: 'pooling', value: 'false (must be true)' });
|
|
1138
|
+
}
|
|
1139
|
+
const { client, release } = await this.relq[INTERNAL].getClientForCursor();
|
|
1140
|
+
try {
|
|
1141
|
+
await client.query('BEGIN');
|
|
1142
|
+
const cursor = client.query(new pg_cursor_1.default(sql));
|
|
1143
|
+
let rows;
|
|
1144
|
+
let aborted = false;
|
|
1145
|
+
let index = 0;
|
|
1146
|
+
outer: do {
|
|
1147
|
+
rows = await new Promise((resolve, reject) => {
|
|
1148
|
+
cursor.read(batchSize, (err, result) => {
|
|
1149
|
+
if (err)
|
|
1150
|
+
reject(err);
|
|
1151
|
+
else
|
|
1152
|
+
resolve(result);
|
|
1153
|
+
});
|
|
1154
|
+
});
|
|
1155
|
+
const transformedRows = this.tableName && this.relq._transformResultsFromDb
|
|
1156
|
+
? this.relq._transformResultsFromDb(this.tableName, rows)
|
|
1157
|
+
: rows;
|
|
1158
|
+
for (const row of transformedRows) {
|
|
1159
|
+
const result = await callback(row, index++);
|
|
1160
|
+
if (result === false) {
|
|
1161
|
+
aborted = true;
|
|
1162
|
+
break outer;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
} while (rows.length > 0);
|
|
1166
|
+
await new Promise((resolve, reject) => {
|
|
1167
|
+
cursor.close((err) => {
|
|
1168
|
+
if (err)
|
|
1169
|
+
reject(err);
|
|
1170
|
+
else
|
|
1171
|
+
resolve();
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
await client.query('COMMIT');
|
|
1175
|
+
}
|
|
1176
|
+
catch (error) {
|
|
1177
|
+
await client.query('ROLLBACK');
|
|
1178
|
+
throw error;
|
|
1179
|
+
}
|
|
1180
|
+
finally {
|
|
1181
|
+
release();
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
async pagination(options) {
|
|
1185
|
+
if (!options.mode || !['paging', 'offset'].includes(options.mode)) {
|
|
1186
|
+
throw new relq_errors_1.RelqQueryError('pagination() requires "mode" to be one of: \'paging\', \'offset\'', { hint: 'Set mode: "paging" or mode: "offset"' });
|
|
1187
|
+
}
|
|
1188
|
+
const orderByArr = options.orderBy
|
|
1189
|
+
? (Array.isArray(options.orderBy[0]) ? options.orderBy : [options.orderBy])
|
|
1190
|
+
: [];
|
|
1191
|
+
for (const [column, direction] of orderByArr) {
|
|
1192
|
+
const dbColumn = this.relq[INTERNAL].hasColumnMapping()
|
|
1193
|
+
? Object.keys(this.relq[INTERNAL].transformToDbColumns(this.tableName, { [column]: true }))[0]
|
|
1194
|
+
: column;
|
|
1195
|
+
this.builder.orderBy(dbColumn, direction);
|
|
1196
|
+
}
|
|
1197
|
+
const isPaging = options.mode === 'paging';
|
|
1198
|
+
const shouldCount = options.count ?? isPaging;
|
|
1199
|
+
let total = 0;
|
|
1200
|
+
if (shouldCount) {
|
|
1201
|
+
const countSql = this.builder.toCountSQL();
|
|
1202
|
+
const countResult = await this.relq[INTERNAL].executeCount(countSql);
|
|
1203
|
+
total = countResult.count;
|
|
1204
|
+
}
|
|
1205
|
+
if (isPaging) {
|
|
1206
|
+
const { page, perPage } = options;
|
|
1207
|
+
if (typeof page !== 'number' || isNaN(page) || page < 1) {
|
|
1208
|
+
throw new relq_errors_1.RelqQueryError('pagination() paging mode requires "page" as a positive number (1-indexed)', { hint: 'page must be >= 1' });
|
|
1209
|
+
}
|
|
1210
|
+
if (typeof perPage !== 'number' || isNaN(perPage) || perPage < 1) {
|
|
1211
|
+
throw new relq_errors_1.RelqQueryError('pagination() paging mode requires "perPage" as a positive number', { hint: 'perPage must be >= 1' });
|
|
1212
|
+
}
|
|
1213
|
+
const offset = (page - 1) * perPage;
|
|
1214
|
+
this.builder.limit(perPage);
|
|
1215
|
+
this.builder.offset(offset);
|
|
1216
|
+
const sql = this.builder.toString();
|
|
1217
|
+
const result = await this.relq[INTERNAL].executeSelect(sql, this.tableName);
|
|
1218
|
+
const totalPages = Math.ceil(total / perPage);
|
|
1219
|
+
const hasNext = page < totalPages;
|
|
1220
|
+
const hasPrev = page > 1;
|
|
1221
|
+
const pagination = {
|
|
1222
|
+
page,
|
|
1223
|
+
perPage,
|
|
1224
|
+
total,
|
|
1225
|
+
totalPages,
|
|
1226
|
+
};
|
|
1227
|
+
if (hasNext) {
|
|
1228
|
+
pagination.hasNext = true;
|
|
1229
|
+
pagination.nextPage = page + 1;
|
|
1230
|
+
Object.defineProperty(pagination, 'loadNext', {
|
|
1231
|
+
value: () => this.pagination({ ...options, page: page + 1 }),
|
|
1232
|
+
enumerable: false,
|
|
1233
|
+
configurable: false
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
else {
|
|
1237
|
+
pagination.hasNext = false;
|
|
1238
|
+
}
|
|
1239
|
+
if (hasPrev) {
|
|
1240
|
+
pagination.hasPrev = true;
|
|
1241
|
+
pagination.prevPage = page - 1;
|
|
1242
|
+
Object.defineProperty(pagination, 'loadPrev', {
|
|
1243
|
+
value: () => this.pagination({ ...options, page: page - 1 }),
|
|
1244
|
+
enumerable: false,
|
|
1245
|
+
configurable: false
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
pagination.hasPrev = false;
|
|
1250
|
+
}
|
|
1251
|
+
const getVisibleProps = () => {
|
|
1252
|
+
const visible = { page, perPage, total, totalPages, hasNext };
|
|
1253
|
+
if (hasNext)
|
|
1254
|
+
visible.nextPage = page + 1;
|
|
1255
|
+
visible.hasPrev = hasPrev;
|
|
1256
|
+
if (hasPrev)
|
|
1257
|
+
visible.prevPage = page - 1;
|
|
1258
|
+
return visible;
|
|
1259
|
+
};
|
|
1260
|
+
Object.defineProperty(pagination, Symbol.for('nodejs.util.inspect.custom'), {
|
|
1261
|
+
value: () => getVisibleProps(),
|
|
1262
|
+
enumerable: false
|
|
1263
|
+
});
|
|
1264
|
+
Object.defineProperty(pagination, 'toJSON', {
|
|
1265
|
+
value: () => getVisibleProps(),
|
|
1266
|
+
enumerable: false
|
|
1267
|
+
});
|
|
1268
|
+
return { data: result.data, pagination };
|
|
1269
|
+
}
|
|
1270
|
+
if (options.mode === 'offset') {
|
|
1271
|
+
const { position, limit: limitOpt } = options;
|
|
1272
|
+
if (typeof position !== 'number' || isNaN(position)) {
|
|
1273
|
+
throw new relq_errors_1.RelqQueryError('pagination() offset mode requires "position" as a number', { hint: 'position must be >= 0' });
|
|
1274
|
+
}
|
|
1275
|
+
if (limitOpt === undefined || (typeof limitOpt !== 'number' && !Array.isArray(limitOpt))) {
|
|
1276
|
+
throw new relq_errors_1.RelqQueryError('pagination() offset mode requires "limit" as a number or [min, max] array', { hint: 'Use limit: number or limit: [min, max]' });
|
|
1277
|
+
}
|
|
1278
|
+
const limit = Array.isArray(limitOpt) ? (0, pagination_types_1.randomLimit)(limitOpt) : limitOpt;
|
|
1279
|
+
this.builder.limit(limit + 1);
|
|
1280
|
+
this.builder.offset(position);
|
|
1281
|
+
const sql = this.builder.toString();
|
|
1282
|
+
const result = await this.relq[INTERNAL].executeSelect(sql, this.tableName);
|
|
1283
|
+
const hasMore = result.data.length > limit;
|
|
1284
|
+
const hasPrev = position > 0;
|
|
1285
|
+
const data = hasMore ? result.data.slice(0, limit) : result.data;
|
|
1286
|
+
const pagination = {
|
|
1287
|
+
position,
|
|
1288
|
+
limit,
|
|
1289
|
+
...(shouldCount && { total }),
|
|
1290
|
+
};
|
|
1291
|
+
if (hasMore) {
|
|
1292
|
+
pagination.hasMore = true;
|
|
1293
|
+
pagination.nextPos = position + limit;
|
|
1294
|
+
Object.defineProperty(pagination, 'loadNext', {
|
|
1295
|
+
value: () => this.pagination({ ...options, position: position + limit }),
|
|
1296
|
+
enumerable: false,
|
|
1297
|
+
configurable: false
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
else {
|
|
1301
|
+
pagination.hasMore = false;
|
|
1302
|
+
}
|
|
1303
|
+
if (hasPrev) {
|
|
1304
|
+
pagination.hasPrev = true;
|
|
1305
|
+
pagination.prevPos = Math.max(0, position - limit);
|
|
1306
|
+
Object.defineProperty(pagination, 'loadPrev', {
|
|
1307
|
+
value: () => this.pagination({ ...options, position: Math.max(0, position - limit) }),
|
|
1308
|
+
enumerable: false,
|
|
1309
|
+
configurable: false
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
else {
|
|
1313
|
+
pagination.hasPrev = false;
|
|
1314
|
+
}
|
|
1315
|
+
const getVisibleProps = () => {
|
|
1316
|
+
const visible = { position, limit };
|
|
1317
|
+
if (shouldCount)
|
|
1318
|
+
visible.total = total;
|
|
1319
|
+
visible.hasMore = hasMore;
|
|
1320
|
+
if (hasMore)
|
|
1321
|
+
visible.nextPos = position + limit;
|
|
1322
|
+
visible.hasPrev = hasPrev;
|
|
1323
|
+
if (hasPrev)
|
|
1324
|
+
visible.prevPos = Math.max(0, position - limit);
|
|
1325
|
+
return visible;
|
|
1326
|
+
};
|
|
1327
|
+
Object.defineProperty(pagination, Symbol.for('nodejs.util.inspect.custom'), {
|
|
1328
|
+
value: () => getVisibleProps(),
|
|
1329
|
+
enumerable: false
|
|
1330
|
+
});
|
|
1331
|
+
Object.defineProperty(pagination, 'toJSON', {
|
|
1332
|
+
value: () => getVisibleProps(),
|
|
1333
|
+
enumerable: false
|
|
1334
|
+
});
|
|
1335
|
+
return { data, pagination };
|
|
1336
|
+
}
|
|
1337
|
+
throw new relq_errors_1.RelqQueryError('Invalid pagination mode');
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
class PaginateBuilder {
|
|
1341
|
+
relq;
|
|
1342
|
+
tableName;
|
|
1343
|
+
columns;
|
|
1344
|
+
whereClause;
|
|
1345
|
+
orderByClause;
|
|
1346
|
+
constructor(relq, tableName, columns, whereClause, orderByClause) {
|
|
1347
|
+
this.relq = relq;
|
|
1348
|
+
this.tableName = tableName;
|
|
1349
|
+
this.columns = columns;
|
|
1350
|
+
this.whereClause = whereClause;
|
|
1351
|
+
this.orderByClause = orderByClause;
|
|
1352
|
+
}
|
|
1353
|
+
async paging(options) {
|
|
1354
|
+
const page = options.page ?? 1;
|
|
1355
|
+
const perPage = options.perPage;
|
|
1356
|
+
const shouldCount = options.count ?? true;
|
|
1357
|
+
if (page < 1) {
|
|
1358
|
+
throw new relq_errors_1.RelqQueryError('page must be >= 1', { hint: 'Page numbers are 1-indexed' });
|
|
1359
|
+
}
|
|
1360
|
+
if (perPage < 1) {
|
|
1361
|
+
throw new relq_errors_1.RelqQueryError('perPage must be >= 1');
|
|
1362
|
+
}
|
|
1363
|
+
const columnsToSelect = this.columns && this.columns.length > 0 ? this.columns : ['*'];
|
|
1364
|
+
const orderByArr = this.orderByClause
|
|
1365
|
+
? (Array.isArray(this.orderByClause[0]) ? this.orderByClause : [this.orderByClause])
|
|
1366
|
+
: [];
|
|
1367
|
+
const selectBuilder = new select_builder_1.SelectBuilder(this.tableName, columnsToSelect);
|
|
1368
|
+
if (this.whereClause) {
|
|
1369
|
+
selectBuilder.where(this.whereClause);
|
|
1370
|
+
}
|
|
1371
|
+
for (const [column, direction] of orderByArr) {
|
|
1372
|
+
const dbColumn = this.relq[INTERNAL].transformToDbColumns(this.tableName, { [column]: true });
|
|
1373
|
+
selectBuilder.orderBy(Object.keys(dbColumn)[0] || column, direction);
|
|
1374
|
+
}
|
|
1375
|
+
let total = 0;
|
|
1376
|
+
if (shouldCount) {
|
|
1377
|
+
const countBuilder = new count_builder_1.CountBuilder(this.tableName);
|
|
1378
|
+
if (this.whereClause) {
|
|
1379
|
+
countBuilder.where(this.whereClause);
|
|
1380
|
+
}
|
|
1381
|
+
const countResult = await this.relq[INTERNAL].executeCount(countBuilder.toString());
|
|
1382
|
+
total = countResult.count;
|
|
1383
|
+
}
|
|
1384
|
+
const offset = (page - 1) * perPage;
|
|
1385
|
+
selectBuilder.limit(perPage);
|
|
1386
|
+
selectBuilder.offset(offset);
|
|
1387
|
+
const result = await this.relq[INTERNAL].executeSelect(selectBuilder.toString(), this.tableName);
|
|
1388
|
+
const totalPages = Math.ceil(total / perPage);
|
|
1389
|
+
const hasNext = page < totalPages;
|
|
1390
|
+
const hasPrev = page > 1;
|
|
1391
|
+
const pagination = {
|
|
1392
|
+
page,
|
|
1393
|
+
perPage,
|
|
1394
|
+
total,
|
|
1395
|
+
totalPages,
|
|
1396
|
+
};
|
|
1397
|
+
if (hasNext) {
|
|
1398
|
+
pagination.hasNext = true;
|
|
1399
|
+
pagination.nextPage = page + 1;
|
|
1400
|
+
Object.defineProperty(pagination, 'loadNext', {
|
|
1401
|
+
value: () => this.paging({ ...options, page: page + 1 }),
|
|
1402
|
+
enumerable: false
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
pagination.hasNext = false;
|
|
1407
|
+
}
|
|
1408
|
+
if (hasPrev) {
|
|
1409
|
+
pagination.hasPrev = true;
|
|
1410
|
+
pagination.prevPage = page - 1;
|
|
1411
|
+
Object.defineProperty(pagination, 'loadPrev', {
|
|
1412
|
+
value: () => this.paging({ ...options, page: page - 1 }),
|
|
1413
|
+
enumerable: false
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
else {
|
|
1417
|
+
pagination.hasPrev = false;
|
|
1418
|
+
}
|
|
1419
|
+
Object.defineProperty(pagination, 'toJSON', {
|
|
1420
|
+
value: () => ({ page, perPage, total, totalPages, hasNext: pagination.hasNext, hasPrev: pagination.hasPrev, nextPage: pagination.nextPage, prevPage: pagination.prevPage }),
|
|
1421
|
+
enumerable: false
|
|
1422
|
+
});
|
|
1423
|
+
return { data: result.data, pagination };
|
|
1424
|
+
}
|
|
1425
|
+
async offset(options) {
|
|
1426
|
+
const position = options.position ?? 0;
|
|
1427
|
+
const shouldCount = options.count ?? false;
|
|
1428
|
+
if (position < 0) {
|
|
1429
|
+
throw new relq_errors_1.RelqQueryError('position must be >= 0');
|
|
1430
|
+
}
|
|
1431
|
+
let limit;
|
|
1432
|
+
if (options.shuffleLimit) {
|
|
1433
|
+
const [min, max] = options.shuffleLimit;
|
|
1434
|
+
if (min < 1 || max < 1) {
|
|
1435
|
+
throw new relq_errors_1.RelqQueryError('shuffleLimit values must be >= 1', { hint: 'Use [min, max] where both are >= 1' });
|
|
1436
|
+
}
|
|
1437
|
+
if (min > max) {
|
|
1438
|
+
throw new relq_errors_1.RelqQueryError('shuffleLimit[0] must be <= shuffleLimit[1]', { hint: 'Use [min, max] format where min <= max' });
|
|
1439
|
+
}
|
|
1440
|
+
limit = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
1441
|
+
}
|
|
1442
|
+
else {
|
|
1443
|
+
const limitValue = options.limit;
|
|
1444
|
+
if (limitValue !== undefined && limitValue < 1) {
|
|
1445
|
+
throw new relq_errors_1.RelqQueryError('limit must be >= 1');
|
|
1446
|
+
}
|
|
1447
|
+
limit = limitValue ?? 50;
|
|
1448
|
+
}
|
|
1449
|
+
const columnsToSelect = this.columns && this.columns.length > 0 ? this.columns : ['*'];
|
|
1450
|
+
const orderByArr = this.orderByClause
|
|
1451
|
+
? (Array.isArray(this.orderByClause[0]) ? this.orderByClause : [this.orderByClause])
|
|
1452
|
+
: [];
|
|
1453
|
+
const selectBuilder = new select_builder_1.SelectBuilder(this.tableName, columnsToSelect);
|
|
1454
|
+
if (this.whereClause) {
|
|
1455
|
+
selectBuilder.where(this.whereClause);
|
|
1456
|
+
}
|
|
1457
|
+
for (const [column, direction] of orderByArr) {
|
|
1458
|
+
const dbColumn = this.relq[INTERNAL].transformToDbColumns(this.tableName, { [column]: true });
|
|
1459
|
+
selectBuilder.orderBy(Object.keys(dbColumn)[0] || column, direction);
|
|
1460
|
+
}
|
|
1461
|
+
let total = 0;
|
|
1462
|
+
if (shouldCount) {
|
|
1463
|
+
const countBuilder = new count_builder_1.CountBuilder(this.tableName);
|
|
1464
|
+
if (this.whereClause) {
|
|
1465
|
+
countBuilder.where(this.whereClause);
|
|
1466
|
+
}
|
|
1467
|
+
const countResult = await this.relq[INTERNAL].executeCount(countBuilder.toString());
|
|
1468
|
+
total = countResult.count;
|
|
1469
|
+
}
|
|
1470
|
+
selectBuilder.limit(limit + 1);
|
|
1471
|
+
selectBuilder.offset(position);
|
|
1472
|
+
const result = await this.relq[INTERNAL].executeSelect(selectBuilder.toString(), this.tableName);
|
|
1473
|
+
const hasMore = result.data.length > limit;
|
|
1474
|
+
const data = hasMore ? result.data.slice(0, limit) : result.data;
|
|
1475
|
+
const hasPrev = position > 0;
|
|
1476
|
+
const pagination = {
|
|
1477
|
+
position,
|
|
1478
|
+
limit,
|
|
1479
|
+
total,
|
|
1480
|
+
};
|
|
1481
|
+
if (hasMore) {
|
|
1482
|
+
pagination.hasMore = true;
|
|
1483
|
+
pagination.nextPos = position + limit;
|
|
1484
|
+
Object.defineProperty(pagination, 'loadNext', {
|
|
1485
|
+
value: () => this.offset({ ...options, position: position + limit }),
|
|
1486
|
+
enumerable: false
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
else {
|
|
1490
|
+
pagination.hasMore = false;
|
|
1491
|
+
}
|
|
1492
|
+
if (hasPrev) {
|
|
1493
|
+
pagination.hasPrev = true;
|
|
1494
|
+
pagination.prevPos = Math.max(0, position - limit);
|
|
1495
|
+
Object.defineProperty(pagination, 'loadPrev', {
|
|
1496
|
+
value: () => this.offset({ ...options, position: Math.max(0, position - limit) }),
|
|
1497
|
+
enumerable: false
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
else {
|
|
1501
|
+
pagination.hasPrev = false;
|
|
1502
|
+
}
|
|
1503
|
+
Object.defineProperty(pagination, 'toJSON', {
|
|
1504
|
+
value: () => ({ position, limit, total, hasMore: pagination.hasMore, hasPrev: pagination.hasPrev, nextPos: pagination.nextPos, prevPos: pagination.prevPos }),
|
|
1505
|
+
enumerable: false
|
|
1506
|
+
});
|
|
1507
|
+
return { data, pagination };
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
class ReturningExecutor {
|
|
1511
|
+
builder;
|
|
1512
|
+
relq;
|
|
1513
|
+
constructor(builder, relq) {
|
|
1514
|
+
this.builder = builder;
|
|
1515
|
+
this.relq = relq;
|
|
1516
|
+
}
|
|
1517
|
+
toString() {
|
|
1518
|
+
return this.builder.toString();
|
|
1519
|
+
}
|
|
1520
|
+
async run(withMetadata) {
|
|
1521
|
+
if (this.builder instanceof insert_builder_1.InsertBuilder) {
|
|
1522
|
+
for (const row of this.builder.insertData) {
|
|
1523
|
+
this.relq[INTERNAL].validateData(this.builder.tableName, row, 'insert');
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
else if (this.builder instanceof update_builder_1.UpdateBuilder) {
|
|
1527
|
+
this.relq[INTERNAL].validateData(this.builder.tableName, this.builder.updateData, 'update');
|
|
1528
|
+
}
|
|
1529
|
+
const sql = this.builder.toString();
|
|
1530
|
+
const result = await this.relq[INTERNAL].executeSelect(sql);
|
|
1531
|
+
if (withMetadata) {
|
|
1532
|
+
return result;
|
|
1533
|
+
}
|
|
1534
|
+
return result.data;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
class ConnectedInsertBuilder {
|
|
1538
|
+
builder;
|
|
1539
|
+
relq;
|
|
1540
|
+
constructor(builder, relq) {
|
|
1541
|
+
this.builder = builder;
|
|
1542
|
+
this.relq = relq;
|
|
1543
|
+
}
|
|
1544
|
+
addRow(row) {
|
|
1545
|
+
this.builder.addRow(row);
|
|
1546
|
+
return this;
|
|
1547
|
+
}
|
|
1548
|
+
addRows(rows) {
|
|
1549
|
+
this.builder.addRows(rows);
|
|
1550
|
+
return this;
|
|
1551
|
+
}
|
|
1552
|
+
clear() {
|
|
1553
|
+
this.builder.clear();
|
|
1554
|
+
return this;
|
|
1555
|
+
}
|
|
1556
|
+
get total() {
|
|
1557
|
+
return this.builder.total;
|
|
1558
|
+
}
|
|
1559
|
+
onConflict(columns, callback) {
|
|
1560
|
+
const cols = Array.isArray(columns) ? columns : [columns];
|
|
1561
|
+
this.builder.onConflict(cols, (conflictBuilder) => {
|
|
1562
|
+
callback(conflictBuilder);
|
|
1563
|
+
});
|
|
1564
|
+
return this;
|
|
1565
|
+
}
|
|
1566
|
+
toString() {
|
|
1567
|
+
return this.builder.toString();
|
|
1568
|
+
}
|
|
1569
|
+
async run(withMetadata) {
|
|
1570
|
+
debugLog(this.relq[INTERNAL]?.config, `ConnectedInsertBuilder.run called for table: ${this.builder.tableName}`);
|
|
1571
|
+
const internalRelq = this.relq[INTERNAL];
|
|
1572
|
+
for (const row of this.builder.insertData) {
|
|
1573
|
+
internalRelq.validateData(this.builder.tableName, row, 'insert');
|
|
1574
|
+
}
|
|
1575
|
+
const sql = this.builder.toString();
|
|
1576
|
+
const result = await internalRelq.executeRun(sql);
|
|
1577
|
+
if (withMetadata) {
|
|
1578
|
+
return result;
|
|
1579
|
+
}
|
|
1580
|
+
return result.metadata.rowCount ?? 0;
|
|
1581
|
+
}
|
|
1582
|
+
returning(columns) {
|
|
1583
|
+
this.builder.returning(columns);
|
|
1584
|
+
return new ReturningExecutor(this.builder, this.relq);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
class ConnectedUpdateBuilder {
|
|
1588
|
+
builder;
|
|
1589
|
+
relq;
|
|
1590
|
+
constructor(builder, relq) {
|
|
1591
|
+
this.builder = builder;
|
|
1592
|
+
this.relq = relq;
|
|
1593
|
+
}
|
|
1594
|
+
where(callback) {
|
|
1595
|
+
this.builder.where(callback);
|
|
1596
|
+
return this;
|
|
1597
|
+
}
|
|
1598
|
+
toString() {
|
|
1599
|
+
return this.builder.toString();
|
|
1600
|
+
}
|
|
1601
|
+
async run(withMetadata) {
|
|
1602
|
+
this.relq[INTERNAL].validateData(this.builder.tableName, this.builder.updateData, 'update');
|
|
1603
|
+
const sql = this.builder.toString();
|
|
1604
|
+
const result = await this.relq[INTERNAL].executeRun(sql);
|
|
1605
|
+
if (withMetadata) {
|
|
1606
|
+
return result;
|
|
1607
|
+
}
|
|
1608
|
+
return result.metadata.rowCount ?? 0;
|
|
1609
|
+
}
|
|
1610
|
+
returning(columns) {
|
|
1611
|
+
this.builder.returning(columns);
|
|
1612
|
+
return new ReturningExecutor(this.builder, this.relq);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
class ConnectedDeleteBuilder {
|
|
1616
|
+
builder;
|
|
1617
|
+
relq;
|
|
1618
|
+
constructor(builder, relq) {
|
|
1619
|
+
this.builder = builder;
|
|
1620
|
+
this.relq = relq;
|
|
1621
|
+
}
|
|
1622
|
+
where(callback) {
|
|
1623
|
+
this.builder.where(callback);
|
|
1624
|
+
return this;
|
|
1625
|
+
}
|
|
1626
|
+
toString() {
|
|
1627
|
+
return this.builder.toString();
|
|
1628
|
+
}
|
|
1629
|
+
async run(withMetadata) {
|
|
1630
|
+
const sql = this.builder.toString();
|
|
1631
|
+
const result = await this.relq[INTERNAL].executeRun(sql);
|
|
1632
|
+
if (withMetadata) {
|
|
1633
|
+
return result;
|
|
1634
|
+
}
|
|
1635
|
+
return result.metadata.rowCount ?? 0;
|
|
1636
|
+
}
|
|
1637
|
+
returning(columns) {
|
|
1638
|
+
this.builder.returning(columns);
|
|
1639
|
+
return new ReturningExecutor(this.builder, this.relq);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
class ConnectedCountBuilder {
|
|
1643
|
+
builder;
|
|
1644
|
+
relq;
|
|
1645
|
+
tableName;
|
|
1646
|
+
groupNames = [];
|
|
1647
|
+
constructor(builder, relq, tableName) {
|
|
1648
|
+
this.builder = builder;
|
|
1649
|
+
this.relq = relq;
|
|
1650
|
+
this.tableName = tableName;
|
|
1651
|
+
}
|
|
1652
|
+
group(name, callback, options) {
|
|
1653
|
+
this.groupNames.push(name);
|
|
1654
|
+
this.builder.group(name, (q) => {
|
|
1655
|
+
const wrapped = this.wrapConditionBuilder(q);
|
|
1656
|
+
return callback(wrapped);
|
|
1657
|
+
}, options);
|
|
1658
|
+
return this;
|
|
1659
|
+
}
|
|
1660
|
+
where(callback) {
|
|
1661
|
+
this.builder.where((q) => {
|
|
1662
|
+
const wrapped = this.wrapConditionBuilder(q);
|
|
1663
|
+
return callback(wrapped);
|
|
1664
|
+
});
|
|
1665
|
+
return this;
|
|
1666
|
+
}
|
|
1667
|
+
wrapConditionBuilder(originalBuilder) {
|
|
1668
|
+
const relq = this.relq;
|
|
1669
|
+
const tableName = this.tableName;
|
|
1670
|
+
return new Proxy(originalBuilder, {
|
|
1671
|
+
get(target, prop) {
|
|
1672
|
+
const original = target[prop];
|
|
1673
|
+
if (typeof original === 'function') {
|
|
1674
|
+
return function (column, ...args) {
|
|
1675
|
+
const transformed = relq[INTERNAL].transformToDbColumns(tableName, { [column]: true });
|
|
1676
|
+
const dbColumn = Object.keys(transformed)[0] || column;
|
|
1677
|
+
return original.call(target, dbColumn, ...args);
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
return original;
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
toString() {
|
|
1685
|
+
return this.builder.toString();
|
|
1686
|
+
}
|
|
1687
|
+
async execute() {
|
|
1688
|
+
const sql = this.builder.toString();
|
|
1689
|
+
return this.relq[INTERNAL].executeCount(sql);
|
|
1690
|
+
}
|
|
1691
|
+
async get() {
|
|
1692
|
+
const sql = this.builder.toString();
|
|
1693
|
+
const result = await this.relq[INTERNAL].executeQuery(sql);
|
|
1694
|
+
const row = result.result.rows[0];
|
|
1695
|
+
if (this.groupNames.length === 0) {
|
|
1696
|
+
return (Number(row?.count) ?? 0);
|
|
1697
|
+
}
|
|
1698
|
+
const counts = {};
|
|
1699
|
+
for (const name of this.groupNames) {
|
|
1700
|
+
counts[name] = Number(row?.[name] ?? 0);
|
|
1701
|
+
}
|
|
1702
|
+
return counts;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
class ConnectedRawQueryBuilder {
|
|
1706
|
+
query;
|
|
1707
|
+
params;
|
|
1708
|
+
relq;
|
|
1709
|
+
builder;
|
|
1710
|
+
constructor(query, params, relq) {
|
|
1711
|
+
this.query = query;
|
|
1712
|
+
this.params = params;
|
|
1713
|
+
this.relq = relq;
|
|
1714
|
+
const convertedQuery = this.convertPlaceholders(query);
|
|
1715
|
+
this.builder = new raw_query_builder_1.RawQueryBuilder(convertedQuery, this.params);
|
|
1716
|
+
}
|
|
1717
|
+
convertPlaceholders(query) {
|
|
1718
|
+
let index = 0;
|
|
1719
|
+
return query.replace(/\?/g, () => `$${++index}`);
|
|
1720
|
+
}
|
|
1721
|
+
async all() {
|
|
1722
|
+
const sql = this.builder.toString();
|
|
1723
|
+
return this.relq[INTERNAL].executeSelect(sql);
|
|
1724
|
+
}
|
|
1725
|
+
async get() {
|
|
1726
|
+
const sql = this.builder.toString();
|
|
1727
|
+
return this.relq[INTERNAL].executeSelectOne(sql);
|
|
1728
|
+
}
|
|
1729
|
+
async getMany(count) {
|
|
1730
|
+
const sql = this.builder.toString();
|
|
1731
|
+
const limitedSql = sql.toUpperCase().includes('LIMIT')
|
|
1732
|
+
? sql
|
|
1733
|
+
: `${sql} LIMIT ${count}`;
|
|
1734
|
+
return this.relq[INTERNAL].executeSelect(limitedSql);
|
|
1735
|
+
}
|
|
1736
|
+
async run() {
|
|
1737
|
+
const sql = this.builder.toString();
|
|
1738
|
+
return this.relq[INTERNAL].executeRun(sql);
|
|
1739
|
+
}
|
|
1740
|
+
async count() {
|
|
1741
|
+
const sql = this.builder.toString();
|
|
1742
|
+
return this.relq[INTERNAL].executeCount(sql);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
class ConnectedTransactionBuilder {
|
|
1746
|
+
relq;
|
|
1747
|
+
constructor(relq) {
|
|
1748
|
+
this.relq = relq;
|
|
1749
|
+
}
|
|
1750
|
+
builder = new transaction_builder_1.TransactionBuilder();
|
|
1751
|
+
toString() {
|
|
1752
|
+
return this.builder.toString();
|
|
1753
|
+
}
|
|
1754
|
+
async begin() {
|
|
1755
|
+
const sql = this.builder.toString();
|
|
1756
|
+
return this.relq[INTERNAL].executeRun(sql);
|
|
1757
|
+
}
|
|
1758
|
+
async commit() {
|
|
1759
|
+
const sql = this.builder.commit();
|
|
1760
|
+
return this.relq[INTERNAL].executeRun(sql);
|
|
1761
|
+
}
|
|
1762
|
+
async rollback() {
|
|
1763
|
+
const sql = this.builder.rollback();
|
|
1764
|
+
return this.relq[INTERNAL].executeRun(sql);
|
|
1765
|
+
}
|
|
1766
|
+
isolationLevel(level) {
|
|
1767
|
+
this.builder.isolation(level);
|
|
1768
|
+
return this;
|
|
1769
|
+
}
|
|
1770
|
+
readOnly() {
|
|
1771
|
+
this.builder.readOnly();
|
|
1772
|
+
return this;
|
|
1773
|
+
}
|
|
1774
|
+
readWrite() {
|
|
1775
|
+
this.builder.readWrite();
|
|
1776
|
+
return this;
|
|
1777
|
+
}
|
|
1778
|
+
deferrable() {
|
|
1779
|
+
this.builder.deferrable();
|
|
1780
|
+
return this;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
class ConnectedCTEBuilder {
|
|
1784
|
+
relq;
|
|
1785
|
+
ctes = [];
|
|
1786
|
+
constructor(relq) {
|
|
1787
|
+
this.relq = relq;
|
|
1788
|
+
}
|
|
1789
|
+
with(name, query) {
|
|
1790
|
+
const queryStr = typeof query === 'string' ? query : query.toString();
|
|
1791
|
+
this.ctes.push({ name, query: queryStr });
|
|
1792
|
+
return this;
|
|
1793
|
+
}
|
|
1794
|
+
withMaterialized(name, query) {
|
|
1795
|
+
const queryStr = typeof query === 'string' ? query : query.toString();
|
|
1796
|
+
this.ctes.push({ name, query: `MATERIALIZED (${queryStr})` });
|
|
1797
|
+
return this;
|
|
1798
|
+
}
|
|
1799
|
+
async query(sql) {
|
|
1800
|
+
const cteClause = this.buildCTEClause();
|
|
1801
|
+
const fullSql = `${cteClause} ${sql}`;
|
|
1802
|
+
const result = await this.relq[INTERNAL].executeQuery(fullSql);
|
|
1803
|
+
return {
|
|
1804
|
+
data: result.result.rows,
|
|
1805
|
+
rowCount: result.result.rowCount ?? 0
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
async all(sql) {
|
|
1809
|
+
const result = await this.query(sql);
|
|
1810
|
+
return result.data;
|
|
1811
|
+
}
|
|
1812
|
+
async get(sql) {
|
|
1813
|
+
const result = await this.query(sql);
|
|
1814
|
+
return result.data[0] ?? null;
|
|
1815
|
+
}
|
|
1816
|
+
buildCTEClause() {
|
|
1817
|
+
if (this.ctes.length === 0)
|
|
1818
|
+
return '';
|
|
1819
|
+
const cteStrings = this.ctes.map(cte => {
|
|
1820
|
+
if (cte.query.startsWith('MATERIALIZED')) {
|
|
1821
|
+
return `"${cte.name}" AS ${cte.query}`;
|
|
1822
|
+
}
|
|
1823
|
+
return `"${cte.name}" AS (${cte.query})`;
|
|
1824
|
+
});
|
|
1825
|
+
return `WITH ${cteStrings.join(', ')}`;
|
|
1826
|
+
}
|
|
1827
|
+
toSQL(mainQuery) {
|
|
1828
|
+
return `${this.buildCTEClause()} ${mainQuery}`;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
exports.default = Relq;
|