ts-prorm-orm 1.1.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/CHANGELOG.md +1111 -0
- package/LICENSE +21 -0
- package/README.md +573 -0
- package/dist/audit/history-query.js +180 -0
- package/dist/audit/index.js +23 -0
- package/dist/audit/logger.js +236 -0
- package/dist/cache/cache-manager.js +84 -0
- package/dist/cache/index.js +16 -0
- package/dist/cache/redis-cluster-cache.js +557 -0
- package/dist/cli.js +2200 -0
- package/dist/compliance/audit-trail.js +68 -0
- package/dist/compliance/backup-verification.js +685 -0
- package/dist/compliance/breach-detector.js +505 -0
- package/dist/compliance/consent-record.js +227 -0
- package/dist/compliance/consent-versioning.js +331 -0
- package/dist/compliance/cross-border-log.js +530 -0
- package/dist/compliance/data-classifier.js +258 -0
- package/dist/compliance/data-lineage.js +565 -0
- package/dist/compliance/data-masker.js +303 -0
- package/dist/compliance/data-portability.js +265 -0
- package/dist/compliance/data-retention.js +195 -0
- package/dist/compliance/dsar-workflow.js +342 -0
- package/dist/compliance/field-encryption.js +223 -0
- package/dist/compliance/immutable-record.js +148 -0
- package/dist/compliance/index.js +260 -0
- package/dist/compliance/privacy-impact-assessment.js +364 -0
- package/dist/compliance/pseudonymization.js +269 -0
- package/dist/compliance/query-firewall.js +1182 -0
- package/dist/compliance/rate-limiter.js +580 -0
- package/dist/compliance/right-to-erasure.js +117 -0
- package/dist/compliance/row-level-security.js +246 -0
- package/dist/compliance/security-decorator.js +574 -0
- package/dist/compliance/security-monitor.js +525 -0
- package/dist/compliance/sensitive-data-discovery.js +476 -0
- package/dist/compliance/session-isolation.js +472 -0
- package/dist/compliance/tls-enforcer.js +108 -0
- package/dist/compliance/worm-storage.js +712 -0
- package/dist/connection-manager.js +198 -0
- package/dist/connection-pool.js +519 -0
- package/dist/decorators/audit.js +136 -0
- package/dist/decorators/belongs-to-many.js +115 -0
- package/dist/decorators/belongs-to.js +115 -0
- package/dist/decorators/check.js +435 -0
- package/dist/decorators/collate.js +329 -0
- package/dist/decorators/comment.js +205 -0
- package/dist/decorators/database-settings.js +236 -0
- package/dist/decorators/default.js +244 -0
- package/dist/decorators/encryption.js +235 -0
- package/dist/decorators/engine.js +97 -0
- package/dist/decorators/fk-constraints.js +594 -0
- package/dist/decorators/foreign-table.js +136 -0
- package/dist/decorators/generated.js +274 -0
- package/dist/decorators/has-many.js +127 -0
- package/dist/decorators/has-one.js +116 -0
- package/dist/decorators/hstore.js +129 -0
- package/dist/decorators/index.js +355 -0
- package/dist/decorators/json-column.js +83 -0
- package/dist/decorators/jsonb.js +101 -0
- package/dist/decorators/orm-decorators.js +425 -0
- package/dist/decorators/permissions.js +294 -0
- package/dist/decorators/procedure.js +210 -0
- package/dist/decorators/query-options.js +556 -0
- package/dist/decorators/range.js +167 -0
- package/dist/decorators/set-column.js +83 -0
- package/dist/decorators/spatial.js +114 -0
- package/dist/decorators/storage.js +580 -0
- package/dist/decorators/timezone.js +512 -0
- package/dist/decorators/trigger.js +90 -0
- package/dist/decorators/uuid.js +135 -0
- package/dist/decorators/view.js +258 -0
- package/dist/diagrams/chen-diagram.js +354 -0
- package/dist/diagrams/class-diagram.js +384 -0
- package/dist/diagrams/dependency-diagram.js +432 -0
- package/dist/diagrams/er-diagram.js +605 -0
- package/dist/diagrams/flow-diagram.js +394 -0
- package/dist/diagrams/gantt-diagram.js +411 -0
- package/dist/diagrams/index-diagram.js +353 -0
- package/dist/diagrams/index.js +184 -0
- package/dist/diagrams/migration-diagram.js +316 -0
- package/dist/diagrams/model-diagram.js +616 -0
- package/dist/diagrams/package-diagram.js +376 -0
- package/dist/diagrams/palette.js +85 -0
- package/dist/diagrams/relational-diagram.js +455 -0
- package/dist/diagrams/schemadoc-diagram.js +309 -0
- package/dist/diagrams/sequence-diagram.js +307 -0
- package/dist/diagrams/state-diagram.js +344 -0
- package/dist/diagrams/svg-dom.js +111 -0
- package/dist/diagrams/tree-diagram.js +250 -0
- package/dist/dialects/clickhouse/index.js +1742 -0
- package/dist/dialects/cockroachdb/index.js +4677 -0
- package/dist/dialects/cratedb/index.js +441 -0
- package/dist/dialects/databricks/index.js +517 -0
- package/dist/dialects/db2/index.js +2225 -0
- package/dist/dialects/dialect.js +720 -0
- package/dist/dialects/duckdb/index.js +2014 -0
- package/dist/dialects/exasol/index.js +357 -0
- package/dist/dialects/firebird/index.js +495 -0
- package/dist/dialects/greenplum/index.js +457 -0
- package/dist/dialects/hana/index.js +1741 -0
- package/dist/dialects/mariadb/index.js +3590 -0
- package/dist/dialects/mssql/index.js +2296 -0
- package/dist/dialects/mysql/index.js +4058 -0
- package/dist/dialects/oracle/index.js +2999 -0
- package/dist/dialects/postgres/index.js +5319 -0
- package/dist/dialects/query-stream-helper.js +152 -0
- package/dist/dialects/questdb/index.js +433 -0
- package/dist/dialects/redshift/index.js +2277 -0
- package/dist/dialects/singlestore/index.js +354 -0
- package/dist/dialects/snowflake/index.js +2082 -0
- package/dist/dialects/spanner/index.js +1768 -0
- package/dist/dialects/sqlite/index.js +3382 -0
- package/dist/dialects/tidb/index.js +377 -0
- package/dist/dialects/timescaledb/index.js +164 -0
- package/dist/dialects/trino/index.js +420 -0
- package/dist/dialects/turso/index.js +227 -0
- package/dist/dialects/vertica/index.js +317 -0
- package/dist/dialects/yugabytedb/index.js +424 -0
- package/dist/errors/index.js +472 -0
- package/dist/errors/utils.js +340 -0
- package/dist/errors.js +21 -0
- package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
- package/dist/extensions/catalog/mssql-features.js +147 -0
- package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
- package/dist/extensions/catalog/oracle-db2-features.js +196 -0
- package/dist/extensions/catalog/postgres-extensions.js +516 -0
- package/dist/extensions/index.js +71 -0
- package/dist/extensions/types.js +13 -0
- package/dist/foreign-data.js +173 -0
- package/dist/hooks/hooks-manager.js +350 -0
- package/dist/hooks/index.js +37 -0
- package/dist/index.js +463 -0
- package/dist/logging.js +335 -0
- package/dist/migrations/index.js +40 -0
- package/dist/migrations/migration.js +196 -0
- package/dist/migrations/migrator.js +411 -0
- package/dist/migrations/prormmigration.js +275 -0
- package/dist/migrations/query-interface.js +435 -0
- package/dist/migrations/seeder.js +353 -0
- package/dist/models/associations.js +852 -0
- package/dist/models/constraints.js +288 -0
- package/dist/models/data-types.js +2518 -0
- package/dist/models/decorators.js +445 -0
- package/dist/models/index.js +33 -0
- package/dist/models/indexes.js +531 -0
- package/dist/models/methods.js +382 -0
- package/dist/models/model-manager.js +103 -0
- package/dist/models/model.js +5349 -0
- package/dist/models/operators.js +67 -0
- package/dist/models/scopes.js +189 -0
- package/dist/models/typescript-types.js +26 -0
- package/dist/nosql/aerospike/index.js +205 -0
- package/dist/nosql/allegrograph/index.js +169 -0
- package/dist/nosql/arangodb/index.js +364 -0
- package/dist/nosql/azure-blob/index.js +206 -0
- package/dist/nosql/beanstalkd/index.js +231 -0
- package/dist/nosql/beequeue/index.js +210 -0
- package/dist/nosql/bigchaindb/index.js +195 -0
- package/dist/nosql/bigtable/index.js +224 -0
- package/dist/nosql/blazegraph/index.js +173 -0
- package/dist/nosql/bullmq/index.js +191 -0
- package/dist/nosql/cassandra/index.js +174 -0
- package/dist/nosql/chroma/index.js +190 -0
- package/dist/nosql/cloudflare-kv/index.js +220 -0
- package/dist/nosql/coherence/index.js +200 -0
- package/dist/nosql/cosmosdb/index.js +157 -0
- package/dist/nosql/couchbase/index.js +213 -0
- package/dist/nosql/dax/index.js +212 -0
- package/dist/nosql/deno-kv/index.js +206 -0
- package/dist/nosql/dgraph/index.js +171 -0
- package/dist/nosql/doris/index.js +169 -0
- package/dist/nosql/druid/index.js +162 -0
- package/dist/nosql/dynamodb/index.js +937 -0
- package/dist/nosql/elasticsearch/index.js +377 -0
- package/dist/nosql/etcd/index.js +502 -0
- package/dist/nosql/eventhubs/index.js +213 -0
- package/dist/nosql/eventstore/index.js +254 -0
- package/dist/nosql/faunadb/index.js +188 -0
- package/dist/nosql/firestore/index.js +177 -0
- package/dist/nosql/fluree/index.js +148 -0
- package/dist/nosql/fuseki/index.js +170 -0
- package/dist/nosql/gcs/index.js +172 -0
- package/dist/nosql/gearman/index.js +160 -0
- package/dist/nosql/geode/index.js +196 -0
- package/dist/nosql/graphdb/index.js +169 -0
- package/dist/nosql/graylog/index.js +188 -0
- package/dist/nosql/gridgain/index.js +171 -0
- package/dist/nosql/hazelcast/index.js +162 -0
- package/dist/nosql/hbase/index.js +230 -0
- package/dist/nosql/ignite/index.js +173 -0
- package/dist/nosql/immudb/index.js +184 -0
- package/dist/nosql/index.js +232 -0
- package/dist/nosql/infinispan/index.js +200 -0
- package/dist/nosql/influxdb/index.js +0 -0
- package/dist/nosql/kafka/index.js +234 -0
- package/dist/nosql/keyspaces/index.js +189 -0
- package/dist/nosql/kinesis/index.js +253 -0
- package/dist/nosql/leveldb/index.js +153 -0
- package/dist/nosql/lmdb/index.js +160 -0
- package/dist/nosql/loki/index.js +201 -0
- package/dist/nosql/marklogic/index.js +204 -0
- package/dist/nosql/materialize/index.js +145 -0
- package/dist/nosql/meilisearch/index.js +154 -0
- package/dist/nosql/memcached/index.js +223 -0
- package/dist/nosql/milvus/index.js +410 -0
- package/dist/nosql/minio/index.js +265 -0
- package/dist/nosql/momento/index.js +179 -0
- package/dist/nosql/mongodb/index.js +461 -0
- package/dist/nosql/nats/index.js +247 -0
- package/dist/nosql/nedb/index.js +164 -0
- package/dist/nosql/neo4j/index.js +450 -0
- package/dist/nosql/neptune/index.js +470 -0
- package/dist/nosql/nsq/index.js +200 -0
- package/dist/nosql/opensearch/index.js +186 -0
- package/dist/nosql/orientdb/index.js +175 -0
- package/dist/nosql/papertrail/index.js +200 -0
- package/dist/nosql/pinecone/index.js +0 -0
- package/dist/nosql/pinot/index.js +133 -0
- package/dist/nosql/pouchdb/index.js +172 -0
- package/dist/nosql/prometheus/index.js +174 -0
- package/dist/nosql/provendb/index.js +147 -0
- package/dist/nosql/pubsub/index.js +187 -0
- package/dist/nosql/pulsar/index.js +232 -0
- package/dist/nosql/qdrant/index.js +295 -0
- package/dist/nosql/qldb/index.js +192 -0
- package/dist/nosql/r2/index.js +281 -0
- package/dist/nosql/rabbitmq/index.js +237 -0
- package/dist/nosql/ravendb/index.js +175 -0
- package/dist/nosql/redis/index.js +607 -0
- package/dist/nosql/redpanda/index.js +237 -0
- package/dist/nosql/resque/index.js +203 -0
- package/dist/nosql/rethinkdb/index.js +232 -0
- package/dist/nosql/rocksdb/index.js +152 -0
- package/dist/nosql/rockset/index.js +126 -0
- package/dist/nosql/s3/index.js +298 -0
- package/dist/nosql/scylladb/index.js +178 -0
- package/dist/nosql/signoz/index.js +227 -0
- package/dist/nosql/sns/index.js +201 -0
- package/dist/nosql/solr/index.js +231 -0
- package/dist/nosql/splunk/index.js +227 -0
- package/dist/nosql/sqs/index.js +246 -0
- package/dist/nosql/stardog/index.js +169 -0
- package/dist/nosql/starrocks/index.js +170 -0
- package/dist/nosql/store.js +2 -0
- package/dist/nosql/sumologic/index.js +213 -0
- package/dist/nosql/surrealdb/index.js +179 -0
- package/dist/nosql/terminusdb/index.js +151 -0
- package/dist/nosql/tigergraph/index.js +469 -0
- package/dist/nosql/typesense/index.js +148 -0
- package/dist/nosql/unqlite/index.js +157 -0
- package/dist/nosql/upstash/index.js +167 -0
- package/dist/nosql/vercel-kv/index.js +166 -0
- package/dist/nosql/victoriametrics/index.js +213 -0
- package/dist/nosql/virtuoso/index.js +169 -0
- package/dist/nosql/weaviate/index.js +276 -0
- package/dist/operators/index.js +108 -0
- package/dist/operators.js +2690 -0
- package/dist/prisma-migrate/index.js +38 -0
- package/dist/prisma-migrate/migration-generator.js +125 -0
- package/dist/prisma-migrate/model-generator.js +172 -0
- package/dist/prisma-migrate/relations.js +100 -0
- package/dist/prisma-migrate/schema-parser.js +167 -0
- package/dist/prisma-migrate/type-mapper.js +40 -0
- package/dist/prorm.js +6832 -0
- package/dist/query-builders/cte-builder.js +80 -0
- package/dist/query-builders/functions/aggregate.js +390 -0
- package/dist/query-builders/functions/conditional.js +503 -0
- package/dist/query-builders/functions/datetime.js +695 -0
- package/dist/query-builders/functions/fulltext.js +439 -0
- package/dist/query-builders/functions/index.js +93 -0
- package/dist/query-builders/functions/json.js +427 -0
- package/dist/query-builders/functions/math.js +399 -0
- package/dist/query-builders/functions/string.js +518 -0
- package/dist/query-builders/functions/window.js +328 -0
- package/dist/query-builders/include-builder.js +323 -0
- package/dist/query-builders/index-expression-builder.js +242 -0
- package/dist/query-builders/index.js +161 -0
- package/dist/query-builders/insert-builder.js +164 -0
- package/dist/query-builders/model-helpers.js +38 -0
- package/dist/query-builders/order-limit-builder.js +239 -0
- package/dist/query-builders/sql-compiler.js +2040 -0
- package/dist/query-builders/subquery-builder.js +152 -0
- package/dist/query-builders/update-builder.js +182 -0
- package/dist/query-builders/view-builder.js +218 -0
- package/dist/query-builders/where-builder.js +1532 -0
- package/dist/query-interface.js +622 -0
- package/dist/query-optimizers/batch-optimizer.js +426 -0
- package/dist/query-optimizers/explain-plans.js +415 -0
- package/dist/query-optimizers/index.js +51 -0
- package/dist/query-optimizers/prepared-statement-cache.js +423 -0
- package/dist/query-optimizers/query-hints.js +438 -0
- package/dist/query-optimizers/query-optimizer.js +278 -0
- package/dist/query-optimizers/slow-query-logger.js +271 -0
- package/dist/replica-manager.js +507 -0
- package/dist/schema/index.js +15 -0
- package/dist/schema/migration-generator.js +374 -0
- package/dist/schema/schema-differ.js +549 -0
- package/dist/schema/types.js +6 -0
- package/dist/sql-constants.js +300 -0
- package/dist/sqlite-advanced.js +1047 -0
- package/dist/streams/index.js +12 -0
- package/dist/streams/transforms.js +178 -0
- package/dist/transaction.js +456 -0
- package/dist/types/index.js +180 -0
- package/dist/user-management.js +127 -0
- package/dist/utils/date.js +300 -0
- package/dist/utils/index.js +1079 -0
- package/dist/utils/string.js +161 -0
- package/dist/validators/index.js +19 -0
- package/dist/validators/validator.js +911 -0
- package/package.json +190 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Neo4j store implementation.
|
|
4
|
+
*
|
|
5
|
+
* Neo4j is a graph database: data is modeled as labeled nodes and typed
|
|
6
|
+
* relationships (both carrying arbitrary properties) instead of rows in
|
|
7
|
+
* tables, and queried with Cypher instead of SQL. It has no `Dialect`-shaped
|
|
8
|
+
* `query(sql)`/identifier-escaping/DDL surface, so this class implements the
|
|
9
|
+
* minimal `NoSqlStore` marker interface and exposes Neo4j's real primitives
|
|
10
|
+
* — sessions, (managed and explicit) transactions, and raw parameterized
|
|
11
|
+
* Cypher — plus a thin layer of Cypher-generating convenience helpers for
|
|
12
|
+
* common node/relationship CRUD, rather than forcing a graph database into
|
|
13
|
+
* a `query(sql)` shape.
|
|
14
|
+
*
|
|
15
|
+
* Uses the official `neo4j-driver` (Bolt protocol) driver.
|
|
16
|
+
*/
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.Neo4jStore = void 0;
|
|
22
|
+
exports.intToNumber = intToNumber;
|
|
23
|
+
const neo4j_driver_1 = __importDefault(require("neo4j-driver"));
|
|
24
|
+
const errors_1 = require("../../errors");
|
|
25
|
+
function wrapConnectionError(err, host) {
|
|
26
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
27
|
+
return new errors_1.ConnectionError(`Unable to connect to Neo4j: ${parent.message}`, {
|
|
28
|
+
parent,
|
|
29
|
+
database: 'neo4j',
|
|
30
|
+
host,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function wrapDatabaseError(err, message) {
|
|
34
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
35
|
+
// Keep the `action` context but don't discard the real Neo4j error text
|
|
36
|
+
// (e.g. a Cypher syntax error, or a constraint violation) - append it so
|
|
37
|
+
// callers inspecting `err.message` still see what actually went wrong.
|
|
38
|
+
const combined = message ? `${message}: ${parent.message}` : parent.message;
|
|
39
|
+
return errors_1.DatabaseError.from(parent, { message: combined });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Unwrap a single Neo4j `Integer` (its 64-bit integer type, used for
|
|
43
|
+
* identities, counts, and any Cypher `INTEGER` value) into a JS `number`.
|
|
44
|
+
* Values outside `Number.MAX_SAFE_INTEGER` lose precision on this
|
|
45
|
+
* conversion - see "Known limitations" in the README for the lossless
|
|
46
|
+
* alternative (`neo4j-driver`'s `disableLosslessIntegers`/`useBigInt`
|
|
47
|
+
* config, passed through via `Neo4jStoreOptions.config`).
|
|
48
|
+
*/
|
|
49
|
+
function intToNumber(value) {
|
|
50
|
+
return value.toNumber();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Recursively convert a raw value coming back from the driver (an
|
|
54
|
+
* `Integer`, a `Node`, a `Relationship`, an array/map containing any of
|
|
55
|
+
* those, or a plain scalar) into plain JS values/objects. This is what
|
|
56
|
+
* turns `Record.toObject()` output into the `GraphNode`/`GraphRelationship`
|
|
57
|
+
* shapes used throughout this store.
|
|
58
|
+
*/
|
|
59
|
+
function mapValue(value) {
|
|
60
|
+
if (value === null || value === undefined) {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
if (neo4j_driver_1.default.isInt(value)) {
|
|
64
|
+
return intToNumber(value);
|
|
65
|
+
}
|
|
66
|
+
if (neo4j_driver_1.default.isNode(value)) {
|
|
67
|
+
const node = value;
|
|
68
|
+
const mapped = {
|
|
69
|
+
elementId: node.elementId,
|
|
70
|
+
identity: intToNumber(node.identity),
|
|
71
|
+
labels: [...node.labels],
|
|
72
|
+
properties: mapValue(node.properties),
|
|
73
|
+
};
|
|
74
|
+
return mapped;
|
|
75
|
+
}
|
|
76
|
+
if (neo4j_driver_1.default.isRelationship(value)) {
|
|
77
|
+
const rel = value;
|
|
78
|
+
const mapped = {
|
|
79
|
+
elementId: rel.elementId,
|
|
80
|
+
identity: intToNumber(rel.identity),
|
|
81
|
+
type: rel.type,
|
|
82
|
+
startElementId: rel.startNodeElementId,
|
|
83
|
+
endElementId: rel.endNodeElementId,
|
|
84
|
+
start: intToNumber(rel.start),
|
|
85
|
+
end: intToNumber(rel.end),
|
|
86
|
+
properties: mapValue(rel.properties),
|
|
87
|
+
};
|
|
88
|
+
return mapped;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
return value.map(mapValue);
|
|
92
|
+
}
|
|
93
|
+
if (typeof value === 'object') {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const [key, v] of Object.entries(value)) {
|
|
96
|
+
out[key] = mapValue(v);
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
/** Convert a single `Record` into a plain object, mapping every field's value (see `mapValue`). */
|
|
103
|
+
function mapRecord(record) {
|
|
104
|
+
return mapValue(record.toObject());
|
|
105
|
+
}
|
|
106
|
+
/** Convert a `ResultSummary` into the plain-number `CypherSummary` shape. */
|
|
107
|
+
function mapSummary(summary) {
|
|
108
|
+
const counters = summary.counters.updates();
|
|
109
|
+
return {
|
|
110
|
+
queryType: summary.queryType,
|
|
111
|
+
nodesCreated: counters.nodesCreated,
|
|
112
|
+
nodesDeleted: counters.nodesDeleted,
|
|
113
|
+
relationshipsCreated: counters.relationshipsCreated,
|
|
114
|
+
relationshipsDeleted: counters.relationshipsDeleted,
|
|
115
|
+
propertiesSet: counters.propertiesSet,
|
|
116
|
+
labelsAdded: counters.labelsAdded,
|
|
117
|
+
labelsRemoved: counters.labelsRemoved,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Build a Cypher label clause (`:Label1:Label2`) from a label or list of labels. */
|
|
121
|
+
function labelClause(labels) {
|
|
122
|
+
if (!labels)
|
|
123
|
+
return '';
|
|
124
|
+
const list = Array.isArray(labels) ? labels : [labels];
|
|
125
|
+
return list.map((label) => `:${label}`).join('');
|
|
126
|
+
}
|
|
127
|
+
/** Build an `AND`-joined `WHERE` clause (`alias.k1 = $k1 AND alias.k2 = $k2`) from an equality-match object. */
|
|
128
|
+
function whereClause(alias, where) {
|
|
129
|
+
const keys = Object.keys(where);
|
|
130
|
+
if (keys.length === 0)
|
|
131
|
+
return '';
|
|
132
|
+
return ` WHERE ${keys.map((key) => `${alias}.${key} = $${key}`).join(' AND ')}`;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* `Neo4jStore` wraps a `neo4j-driver` `Driver` and exposes:
|
|
136
|
+
* - raw parameterized Cypher (`run()`) - the general escape hatch,
|
|
137
|
+
* - session management (`session()`),
|
|
138
|
+
* - transaction functions with automatic retry (`executeRead()`/`executeWrite()`),
|
|
139
|
+
* - explicit begin/commit/rollback transactions (`beginTransaction()`),
|
|
140
|
+
* - and Cypher-generating node/relationship CRUD convenience helpers
|
|
141
|
+
* (`createNode()`, `findNodes()`, `createRelationship()`, etc) built on
|
|
142
|
+
* top of `run()` - these are thin Cypher generators, not a separate
|
|
143
|
+
* query engine, so anything they don't cover is one `run()` call away.
|
|
144
|
+
*/
|
|
145
|
+
class Neo4jStore {
|
|
146
|
+
constructor(options) {
|
|
147
|
+
this.name = 'neo4j';
|
|
148
|
+
this.library = 'neo4j-driver';
|
|
149
|
+
this.driverInstance = null;
|
|
150
|
+
this.connected = false;
|
|
151
|
+
this.options = options;
|
|
152
|
+
}
|
|
153
|
+
// ---------------------------------------------------------------------
|
|
154
|
+
// Connection lifecycle
|
|
155
|
+
// ---------------------------------------------------------------------
|
|
156
|
+
resolveAuth() {
|
|
157
|
+
if (this.options.auth) {
|
|
158
|
+
return this.options.auth;
|
|
159
|
+
}
|
|
160
|
+
if (this.options.username !== undefined || this.options.password !== undefined) {
|
|
161
|
+
return neo4j_driver_1.default.auth.basic(this.options.username ?? '', this.options.password ?? '');
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
async connect() {
|
|
166
|
+
if (this.connected && this.driverInstance) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
let candidate = null;
|
|
170
|
+
try {
|
|
171
|
+
candidate = neo4j_driver_1.default.driver(this.options.uri, this.resolveAuth(), this.options.config);
|
|
172
|
+
await candidate.verifyConnectivity(this.options.database ? { database: this.options.database } : undefined);
|
|
173
|
+
this.driverInstance = candidate;
|
|
174
|
+
this.connected = true;
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
// Don't leak the failed driver's connection pool - we're about to
|
|
178
|
+
// drop our only reference to it.
|
|
179
|
+
if (candidate) {
|
|
180
|
+
await candidate.close().catch(() => undefined);
|
|
181
|
+
}
|
|
182
|
+
this.driverInstance = null;
|
|
183
|
+
this.connected = false;
|
|
184
|
+
throw wrapConnectionError(err, this.options.uri);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async disconnect() {
|
|
188
|
+
if (this.driverInstance) {
|
|
189
|
+
await this.driverInstance.close();
|
|
190
|
+
this.driverInstance = null;
|
|
191
|
+
}
|
|
192
|
+
this.connected = false;
|
|
193
|
+
}
|
|
194
|
+
isConnected() {
|
|
195
|
+
return this.connected && this.driverInstance !== null;
|
|
196
|
+
}
|
|
197
|
+
/** Returns the underlying `neo4j-driver` `Driver` for anything not wrapped here. */
|
|
198
|
+
getClient() {
|
|
199
|
+
return this.requireDriver();
|
|
200
|
+
}
|
|
201
|
+
requireDriver() {
|
|
202
|
+
if (!this.driverInstance) {
|
|
203
|
+
throw new errors_1.ConnectionError('Not connected to Neo4j', { database: 'neo4j' });
|
|
204
|
+
}
|
|
205
|
+
return this.driverInstance;
|
|
206
|
+
}
|
|
207
|
+
// ---------------------------------------------------------------------
|
|
208
|
+
// Sessions
|
|
209
|
+
// ---------------------------------------------------------------------
|
|
210
|
+
/**
|
|
211
|
+
* Open a new `Session`. Sessions are lightweight and disposable - open
|
|
212
|
+
* one per logical unit of work and close it when done (`run()`,
|
|
213
|
+
* `executeRead()`/`executeWrite()`, and `beginTransaction()` all do this
|
|
214
|
+
* for you; call this directly only if you need the raw `Session`, e.g.
|
|
215
|
+
* for `session.run()`'s streaming `subscribe()` API).
|
|
216
|
+
*/
|
|
217
|
+
session(options = {}) {
|
|
218
|
+
const driver = this.requireDriver();
|
|
219
|
+
return driver.session({
|
|
220
|
+
database: options.database ?? this.options.database,
|
|
221
|
+
defaultAccessMode: options.defaultAccessMode === 'READ' ? neo4j_driver_1.default.session.READ : neo4j_driver_1.default.session.WRITE,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// ---------------------------------------------------------------------
|
|
225
|
+
// Raw Cypher (the general escape hatch)
|
|
226
|
+
// ---------------------------------------------------------------------
|
|
227
|
+
/**
|
|
228
|
+
* Run a single parameterized Cypher statement as an auto-commit query,
|
|
229
|
+
* on a session opened and closed for just this call. Always parameterize
|
|
230
|
+
* user-supplied values (`$name`, not string concatenation) - the driver
|
|
231
|
+
* sends `params` separately from the query text, the same protection
|
|
232
|
+
* bind parameters give you against SQL injection.
|
|
233
|
+
*/
|
|
234
|
+
async run(cypher, params = {}, options = {}) {
|
|
235
|
+
const session = this.session(options);
|
|
236
|
+
try {
|
|
237
|
+
const result = await session.run(cypher, params);
|
|
238
|
+
return {
|
|
239
|
+
records: result.records.map((record) => mapRecord(record)),
|
|
240
|
+
keys: result.records.length > 0 ? result.records[0].keys.map(String) : [],
|
|
241
|
+
summary: mapSummary(result.summary),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
throw wrapDatabaseError(err, 'Neo4j query failed');
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
await session.close();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// ---------------------------------------------------------------------
|
|
252
|
+
// Transaction functions (automatic retry on transient errors)
|
|
253
|
+
// ---------------------------------------------------------------------
|
|
254
|
+
/**
|
|
255
|
+
* Run `work` inside a managed read transaction. The driver automatically
|
|
256
|
+
* retries `work` on transient errors (e.g. deadlocks, leader switches in
|
|
257
|
+
* a cluster) - `work` may run more than once, so it must be idempotent
|
|
258
|
+
* and side-effect-free outside of the transaction it's given.
|
|
259
|
+
*/
|
|
260
|
+
async executeRead(work, options = {}) {
|
|
261
|
+
const session = this.session({ ...options, defaultAccessMode: 'READ' });
|
|
262
|
+
try {
|
|
263
|
+
return await session.executeRead(work);
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
throw wrapDatabaseError(err, 'Neo4j executeRead failed');
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
await session.close();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/** Like `executeRead()`, but for a managed write transaction (`defaultAccessMode: 'WRITE'`). */
|
|
273
|
+
async executeWrite(work, options = {}) {
|
|
274
|
+
const session = this.session({ ...options, defaultAccessMode: 'WRITE' });
|
|
275
|
+
try {
|
|
276
|
+
return await session.executeWrite(work);
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
throw wrapDatabaseError(err, 'Neo4j executeWrite failed');
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
await session.close();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// ---------------------------------------------------------------------
|
|
286
|
+
// Explicit transactions (caller-driven begin/commit/rollback)
|
|
287
|
+
// ---------------------------------------------------------------------
|
|
288
|
+
/**
|
|
289
|
+
* Begin an explicit transaction spanning multiple `run()` calls, on a
|
|
290
|
+
* session held open until `commit()`/`rollback()`. Unlike
|
|
291
|
+
* `executeRead()`/`executeWrite()`, this is never retried automatically
|
|
292
|
+
* - use it when you need multiple round trips with logic in between, or
|
|
293
|
+
* full manual control over commit/rollback.
|
|
294
|
+
*/
|
|
295
|
+
async beginTransaction(options = {}) {
|
|
296
|
+
const session = this.session(options);
|
|
297
|
+
let tx;
|
|
298
|
+
try {
|
|
299
|
+
tx = await session.beginTransaction();
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
await session.close();
|
|
303
|
+
throw wrapConnectionError(err, this.options.uri);
|
|
304
|
+
}
|
|
305
|
+
let closed = false;
|
|
306
|
+
const finish = async (op) => {
|
|
307
|
+
try {
|
|
308
|
+
await tx[op]();
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
throw wrapDatabaseError(err, `Neo4j transaction ${op} failed`);
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
closed = true;
|
|
315
|
+
await session.close();
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
return {
|
|
319
|
+
run: async (cypher, params = {}) => {
|
|
320
|
+
try {
|
|
321
|
+
const result = await tx.run(cypher, params);
|
|
322
|
+
return {
|
|
323
|
+
records: result.records.map((record) => mapRecord(record)),
|
|
324
|
+
keys: result.records.length > 0 ? result.records[0].keys.map(String) : [],
|
|
325
|
+
summary: mapSummary(result.summary),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
throw wrapDatabaseError(err, 'Neo4j transaction run failed');
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
commit: () => finish('commit'),
|
|
333
|
+
rollback: () => finish('rollback'),
|
|
334
|
+
isOpen: () => !closed && tx.isOpen(),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
// ---------------------------------------------------------------------
|
|
338
|
+
// Node CRUD (Cypher generated under the hood - see README)
|
|
339
|
+
// ---------------------------------------------------------------------
|
|
340
|
+
/** `CREATE (n:Label1:Label2) SET n = $properties RETURN n` */
|
|
341
|
+
async createNode(labels, properties = {}) {
|
|
342
|
+
const cypher = `CREATE (n${labelClause(labels)}) SET n = $properties RETURN n`;
|
|
343
|
+
const result = await this.run(cypher, { properties });
|
|
344
|
+
return result.records[0].n;
|
|
345
|
+
}
|
|
346
|
+
/** `MATCH (n) WHERE elementId(n) = $elementId RETURN n` */
|
|
347
|
+
async getNodeByElementId(elementId) {
|
|
348
|
+
const cypher = 'MATCH (n) WHERE elementId(n) = $elementId RETURN n';
|
|
349
|
+
const result = await this.run(cypher, { elementId });
|
|
350
|
+
return result.records[0]?.n ?? null;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* `MATCH (n:Label) WHERE n.k1 = $k1 AND ... RETURN n [SKIP $skip] [LIMIT $limit]`
|
|
354
|
+
*
|
|
355
|
+
* `where` is an equality-only match on node properties (no operators,
|
|
356
|
+
* ranges, or `OR`) - use `run()` directly for anything more expressive.
|
|
357
|
+
*/
|
|
358
|
+
async findNodes(labels = [], where = {}, options = {}) {
|
|
359
|
+
let cypher = `MATCH (n${labelClause(labels)})${whereClause('n', where)} RETURN n`;
|
|
360
|
+
const params = { ...where };
|
|
361
|
+
if (options.skip !== undefined) {
|
|
362
|
+
cypher += ' SKIP $skip';
|
|
363
|
+
params.skip = neo4j_driver_1.default.int(options.skip);
|
|
364
|
+
}
|
|
365
|
+
if (options.limit !== undefined) {
|
|
366
|
+
cypher += ' LIMIT $limit';
|
|
367
|
+
params.limit = neo4j_driver_1.default.int(options.limit);
|
|
368
|
+
}
|
|
369
|
+
const result = await this.run(cypher, params);
|
|
370
|
+
return result.records.map((record) => record.n);
|
|
371
|
+
}
|
|
372
|
+
/** `MATCH (n) WHERE elementId(n) = $elementId SET n += $properties RETURN n` (merges, does not replace, existing properties) */
|
|
373
|
+
async updateNode(elementId, properties) {
|
|
374
|
+
const cypher = 'MATCH (n) WHERE elementId(n) = $elementId SET n += $properties RETURN n';
|
|
375
|
+
const result = await this.run(cypher, { elementId, properties });
|
|
376
|
+
return result.records[0]?.n ?? null;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* `MATCH (n) WHERE elementId(n) = $elementId [DETACH] DELETE n`
|
|
380
|
+
*
|
|
381
|
+
* Without `options.detach`, deleting a node that still has relationships
|
|
382
|
+
* fails (Neo4j refuses to leave dangling relationships) - the underlying
|
|
383
|
+
* error surfaces as a `DatabaseError`. Returns whether a node was
|
|
384
|
+
* actually deleted (based on the query's `nodesDeleted` counter).
|
|
385
|
+
*/
|
|
386
|
+
async deleteNode(elementId, options = {}) {
|
|
387
|
+
const cypher = `MATCH (n) WHERE elementId(n) = $elementId ${options.detach ? 'DETACH DELETE n' : 'DELETE n'}`;
|
|
388
|
+
const result = await this.run(cypher, { elementId });
|
|
389
|
+
return result.summary.nodesDeleted > 0;
|
|
390
|
+
}
|
|
391
|
+
/** `MATCH (n:Label) WHERE ... RETURN count(n) AS count` */
|
|
392
|
+
async countNodes(labels = [], where = {}) {
|
|
393
|
+
const cypher = `MATCH (n${labelClause(labels)})${whereClause('n', where)} RETURN count(n) AS count`;
|
|
394
|
+
const result = await this.run(cypher, { ...where });
|
|
395
|
+
return result.records[0]?.count ?? 0;
|
|
396
|
+
}
|
|
397
|
+
// ---------------------------------------------------------------------
|
|
398
|
+
// Relationship CRUD
|
|
399
|
+
// ---------------------------------------------------------------------
|
|
400
|
+
/**
|
|
401
|
+
* `MATCH (a), (b) WHERE elementId(a) = $fromElementId AND elementId(b) = $toElementId`
|
|
402
|
+
* `CREATE (a)-[r:TYPE]->(b) SET r = $properties RETURN r`
|
|
403
|
+
*/
|
|
404
|
+
async createRelationship(fromElementId, toElementId, type, properties = {}) {
|
|
405
|
+
const cypher = 'MATCH (a), (b) WHERE elementId(a) = $fromElementId AND elementId(b) = $toElementId ' +
|
|
406
|
+
`CREATE (a)-[r:${type}]->(b) SET r = $properties RETURN r`;
|
|
407
|
+
const result = await this.run(cypher, {
|
|
408
|
+
fromElementId,
|
|
409
|
+
toElementId,
|
|
410
|
+
properties,
|
|
411
|
+
});
|
|
412
|
+
if (result.records.length === 0) {
|
|
413
|
+
throw wrapDatabaseError(new Error(`fromElementId "${fromElementId}" or toElementId "${toElementId}" not found`), 'Neo4j createRelationship failed');
|
|
414
|
+
}
|
|
415
|
+
return result.records[0].r;
|
|
416
|
+
}
|
|
417
|
+
/** `MATCH ()-[r]->() WHERE elementId(r) = $elementId RETURN r` */
|
|
418
|
+
async getRelationshipByElementId(elementId) {
|
|
419
|
+
const cypher = 'MATCH (a)-[r]->(b) WHERE elementId(r) = $elementId RETURN r';
|
|
420
|
+
const result = await this.run(cypher, { elementId });
|
|
421
|
+
return result.records[0]?.r ?? null;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* `MATCH (a)-[r:TYPE]->(b) WHERE r.k1 = $k1 AND ... RETURN r [SKIP $skip] [LIMIT $limit]`
|
|
425
|
+
*
|
|
426
|
+
* Omit `type` to match relationships of any type.
|
|
427
|
+
*/
|
|
428
|
+
async findRelationships(type, where = {}, options = {}) {
|
|
429
|
+
let cypher = `MATCH (a)-[r${type ? `:${type}` : ''}]->(b)${whereClause('r', where)} RETURN r`;
|
|
430
|
+
const params = { ...where };
|
|
431
|
+
if (options.skip !== undefined) {
|
|
432
|
+
cypher += ' SKIP $skip';
|
|
433
|
+
params.skip = neo4j_driver_1.default.int(options.skip);
|
|
434
|
+
}
|
|
435
|
+
if (options.limit !== undefined) {
|
|
436
|
+
cypher += ' LIMIT $limit';
|
|
437
|
+
params.limit = neo4j_driver_1.default.int(options.limit);
|
|
438
|
+
}
|
|
439
|
+
const result = await this.run(cypher, params);
|
|
440
|
+
return result.records.map((record) => record.r);
|
|
441
|
+
}
|
|
442
|
+
/** `MATCH (a)-[r]->(b) WHERE elementId(r) = $elementId DELETE r` - returns whether a relationship was actually deleted. */
|
|
443
|
+
async deleteRelationship(elementId) {
|
|
444
|
+
const cypher = 'MATCH (a)-[r]->(b) WHERE elementId(r) = $elementId DELETE r';
|
|
445
|
+
const result = await this.run(cypher, { elementId });
|
|
446
|
+
return result.summary.relationshipsDeleted > 0;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
exports.Neo4jStore = Neo4jStore;
|
|
450
|
+
exports.default = Neo4jStore;
|