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,470 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Amazon Neptune store.
|
|
4
|
+
*
|
|
5
|
+
* Neptune is a managed graph database: data is modeled as vertices and
|
|
6
|
+
* edges (plus their properties), queried by graph traversal, not by
|
|
7
|
+
* arbitrary `WHERE`-clause SQL. It has no joins, no relational schema, and
|
|
8
|
+
* no `query(sql)` shape, so — like the other stores in `src/nosql/` — this
|
|
9
|
+
* class does not implement the SQL-shaped `Dialect` interface
|
|
10
|
+
* (`src/dialects/dialect.ts`). Instead it implements the minimal
|
|
11
|
+
* `NoSqlStore` marker interface (`src/nosql/store.ts`) and exposes
|
|
12
|
+
* Neptune's own query model directly: Gremlin traversals.
|
|
13
|
+
*
|
|
14
|
+
* Neptune supports two graph query languages: Gremlin and openCypher.
|
|
15
|
+
* This store uses **Gremlin**, via the official `gremlin` npm package
|
|
16
|
+
* (Apache TinkerPop's JavaScript Gremlin Language Variant), because it is
|
|
17
|
+
* the more universally-supported path for Neptune from Node.js — the
|
|
18
|
+
* `gremlin` package speaks Neptune's Gremlin websocket protocol directly
|
|
19
|
+
* (`DriverRemoteConnection` for the fluent, bytecode-based traversal API;
|
|
20
|
+
* `Client` for raw Gremlin script submission).
|
|
21
|
+
*
|
|
22
|
+
* ## IAM authentication — read this before using in production
|
|
23
|
+
*
|
|
24
|
+
* Neptune clusters can require AWS IAM (SigV4) request signing on every
|
|
25
|
+
* Gremlin connection. **This store does NOT implement SigV4 signing.**
|
|
26
|
+
* Correctly signing a websocket handshake (canonical request, credential
|
|
27
|
+
* scope, signing key derivation, clock-skew-sensitive timestamps, and
|
|
28
|
+
* keeping all of that valid for the life of a persistent connection) is
|
|
29
|
+
* substantial, security-sensitive logic that is easy to get subtly wrong,
|
|
30
|
+
* and shipping a partial/approximate implementation would be worse than
|
|
31
|
+
* not shipping one — silent auth failures or, worse, requests that
|
|
32
|
+
* "happen to work" against a lenient test setup but fail against real
|
|
33
|
+
* Neptune are not an acceptable trade-off here.
|
|
34
|
+
*
|
|
35
|
+
* Instead, `NeptuneStoreOptions` supports two supported ways to connect:
|
|
36
|
+
*
|
|
37
|
+
* 1. **No auth** — Neptune clusters are frequently run inside a VPC with
|
|
38
|
+
* IAM database authentication *disabled*, relying on VPC/security-group
|
|
39
|
+
* isolation instead. Just supply `endpoint`/`url` and nothing else.
|
|
40
|
+
* 2. **Caller-supplied signed headers** — if the target cluster has IAM
|
|
41
|
+
* auth enabled, sign the request yourself (e.g. with
|
|
42
|
+
* `@aws-sdk/signature-v4` / `aws4`, or your own SigV4 helper) and pass
|
|
43
|
+
* the resulting headers via `options.headers` (static) or
|
|
44
|
+
* `options.authHeaderProvider` (called on every `connect()`, so it can
|
|
45
|
+
* mint a fresh signature — SigV4 signatures are timestamp-bound and a
|
|
46
|
+
* static one will eventually be rejected). This store passes those
|
|
47
|
+
* headers straight through to `DriverRemoteConnection`/`Client` via
|
|
48
|
+
* their `options.headers` field; it performs no signing of its own.
|
|
49
|
+
*
|
|
50
|
+
* See the README in this directory for a worked example of option 2.
|
|
51
|
+
*/
|
|
52
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
53
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
54
|
+
};
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.NeptuneStore = exports.NeptuneError = void 0;
|
|
57
|
+
// The `gremlin` package ships no TypeScript declarations, so this import
|
|
58
|
+
// is implicitly `any` (the repo's tsconfig has `noImplicitAny: false`,
|
|
59
|
+
// which is exactly the escape hatch intended for cases like this). All
|
|
60
|
+
// gremlin types used below (`GraphTraversalSource`, `Traversal`, ...) are
|
|
61
|
+
// therefore documented in comments rather than enforced by the compiler.
|
|
62
|
+
const gremlin_1 = __importDefault(require("gremlin"));
|
|
63
|
+
const errors_1 = require("../../errors");
|
|
64
|
+
/** `DatabaseError` subclass used for all Neptune/Gremlin failures wrapped by this store. */
|
|
65
|
+
class NeptuneError extends errors_1.DatabaseError {
|
|
66
|
+
constructor(message, options = {}) {
|
|
67
|
+
super(message, options);
|
|
68
|
+
this.name = 'NeptuneError';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.NeptuneError = NeptuneError;
|
|
72
|
+
/** Recursively converts Gremlin's `Map`/`Array` result shapes (from `valueMap()`/`elementMap()`) into plain JS objects/arrays. */
|
|
73
|
+
function normalizeValue(value) {
|
|
74
|
+
if (value instanceof Map) {
|
|
75
|
+
const obj = {};
|
|
76
|
+
for (const [key, val] of value.entries()) {
|
|
77
|
+
obj[String(key)] = normalizeValue(val);
|
|
78
|
+
}
|
|
79
|
+
return obj;
|
|
80
|
+
}
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
return value.map(normalizeValue);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
/** Splits a normalized `elementMap()` result into a `NeptuneVertex`. */
|
|
87
|
+
function toVertex(normalized) {
|
|
88
|
+
const { id, label, ...properties } = normalized;
|
|
89
|
+
return { id: id, label: String(label), properties };
|
|
90
|
+
}
|
|
91
|
+
/** Splits a normalized `elementMap()` result into a `NeptuneEdge`. `IN`/`OUT` are the endpoint vertex IDs Gremlin's edge `elementMap()` reports them under. */
|
|
92
|
+
function toEdge(normalized) {
|
|
93
|
+
const { id, label, IN, OUT, ...properties } = normalized;
|
|
94
|
+
return {
|
|
95
|
+
id: id,
|
|
96
|
+
label: String(label),
|
|
97
|
+
outV: OUT,
|
|
98
|
+
inV: IN,
|
|
99
|
+
properties,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function wrapConnectionError(err, endpoint) {
|
|
103
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
104
|
+
return new errors_1.ConnectionError(`Unable to connect to Neptune: ${parent.message}`, {
|
|
105
|
+
parent,
|
|
106
|
+
database: 'neptune',
|
|
107
|
+
host: endpoint,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function wrapNeptuneError(err, action) {
|
|
111
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
112
|
+
return new NeptuneError(`Neptune ${action} failed: ${parent.message}`, {
|
|
113
|
+
parent,
|
|
114
|
+
original: parent.message,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* `NeptuneStore` wraps the `gremlin` package's `DriverRemoteConnection`
|
|
119
|
+
* (fluent, bytecode-based traversals via a `GraphTraversalSource`, i.e.
|
|
120
|
+
* `g`) and `Client` (raw Gremlin script submission) to talk to Amazon
|
|
121
|
+
* Neptune, implementing the minimal `NoSqlStore` marker interface. Vertex/
|
|
122
|
+
* edge CRUD helpers below are convenience wrappers built on top of real
|
|
123
|
+
* Gremlin steps (`addV`/`addE`/`has`/`hasLabel`/`drop`/...) — for anything
|
|
124
|
+
* they don't cover, use `getClient()` (the raw `GraphTraversalSource`),
|
|
125
|
+
* `traversal()` (build an arbitrary traversal against `g`), or `submit()`
|
|
126
|
+
* (raw parameterized Gremlin script text).
|
|
127
|
+
*/
|
|
128
|
+
class NeptuneStore {
|
|
129
|
+
constructor(options = {}) {
|
|
130
|
+
this.name = 'neptune';
|
|
131
|
+
this.library = 'gremlin';
|
|
132
|
+
this.connection = null;
|
|
133
|
+
this.g = null;
|
|
134
|
+
this.client = null;
|
|
135
|
+
this.connected = false;
|
|
136
|
+
this.options = options;
|
|
137
|
+
}
|
|
138
|
+
// ---------------------------------------------------------------------
|
|
139
|
+
// Connection lifecycle
|
|
140
|
+
// ---------------------------------------------------------------------
|
|
141
|
+
resolveUrl() {
|
|
142
|
+
if (this.options.url) {
|
|
143
|
+
return this.options.url;
|
|
144
|
+
}
|
|
145
|
+
if (!this.options.endpoint) {
|
|
146
|
+
throw new errors_1.ConnectionError('NeptuneStore requires either `url` or `endpoint` to connect', {
|
|
147
|
+
database: 'neptune',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const scheme = this.options.useSsl === false ? 'ws' : 'wss';
|
|
151
|
+
const port = this.options.port ?? 8182;
|
|
152
|
+
return `${scheme}://${this.options.endpoint}:${port}/gremlin`;
|
|
153
|
+
}
|
|
154
|
+
async resolveHeaders() {
|
|
155
|
+
const provided = this.options.authHeaderProvider ? await this.options.authHeaderProvider() : undefined;
|
|
156
|
+
const merged = { ...this.options.headers, ...provided };
|
|
157
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
158
|
+
}
|
|
159
|
+
async connect() {
|
|
160
|
+
const url = this.resolveUrl();
|
|
161
|
+
try {
|
|
162
|
+
const headers = await this.resolveHeaders();
|
|
163
|
+
const connectionOptions = {
|
|
164
|
+
traversalSource: this.options.traversalSource ?? 'g',
|
|
165
|
+
rejectUnauthorized: this.options.rejectUnauthorized,
|
|
166
|
+
headers,
|
|
167
|
+
...this.options.connectionOptions,
|
|
168
|
+
};
|
|
169
|
+
this.connection = new gremlin_1.default.driver.DriverRemoteConnection(url, connectionOptions);
|
|
170
|
+
await this.connection.open();
|
|
171
|
+
this.g = gremlin_1.default.process.AnonymousTraversalSource.traversal().with_(this.connection);
|
|
172
|
+
this.client = new gremlin_1.default.driver.Client(url, connectionOptions);
|
|
173
|
+
await this.client.open();
|
|
174
|
+
this.connected = true;
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
await this.safeCloseAll();
|
|
178
|
+
this.connected = false;
|
|
179
|
+
throw wrapConnectionError(err, this.options.endpoint ?? url);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async safeCloseAll() {
|
|
183
|
+
try {
|
|
184
|
+
await this.client?.close();
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// ignore - we're already tearing down after a failure
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
await this.connection?.close();
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// ignore - we're already tearing down after a failure
|
|
194
|
+
}
|
|
195
|
+
this.client = null;
|
|
196
|
+
this.connection = null;
|
|
197
|
+
this.g = null;
|
|
198
|
+
}
|
|
199
|
+
async disconnect() {
|
|
200
|
+
await this.safeCloseAll();
|
|
201
|
+
this.connected = false;
|
|
202
|
+
}
|
|
203
|
+
isConnected() {
|
|
204
|
+
return this.connected && this.connection !== null && this.connection.isOpen === true;
|
|
205
|
+
}
|
|
206
|
+
/** Returns the underlying `GraphTraversalSource` (`g`) for building traversals not wrapped below. */
|
|
207
|
+
getClient() {
|
|
208
|
+
return this.requireG();
|
|
209
|
+
}
|
|
210
|
+
/** Access to the raw `DriverRemoteConnection`, e.g. to inspect `isOpen` directly. */
|
|
211
|
+
getConnection() {
|
|
212
|
+
if (!this.connection) {
|
|
213
|
+
throw new errors_1.ConnectionError('Neptune client is not connected. Call connect() first.', {
|
|
214
|
+
database: 'neptune',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return this.connection;
|
|
218
|
+
}
|
|
219
|
+
requireG() {
|
|
220
|
+
if (!this.g || !this.connected) {
|
|
221
|
+
throw new errors_1.ConnectionError('Neptune client is not connected. Call connect() first.', {
|
|
222
|
+
database: 'neptune',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return this.g;
|
|
226
|
+
}
|
|
227
|
+
requireClient() {
|
|
228
|
+
if (!this.client || !this.connected) {
|
|
229
|
+
throw new errors_1.ConnectionError('Neptune client is not connected. Call connect() first.', {
|
|
230
|
+
database: 'neptune',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return this.client;
|
|
234
|
+
}
|
|
235
|
+
// ---------------------------------------------------------------------
|
|
236
|
+
// Raw Gremlin
|
|
237
|
+
// ---------------------------------------------------------------------
|
|
238
|
+
/**
|
|
239
|
+
* Runs an arbitrary traversal built against `g` and Gremlin's anonymous
|
|
240
|
+
* traversal statics (`gremlin.process.statics`, e.g. `statics.V(id)` for
|
|
241
|
+
* nested traversals like `.to(statics.V(id))`), returning the fully
|
|
242
|
+
* materialized, normalized result list. This is the escape hatch for any
|
|
243
|
+
* Gremlin step this store doesn't wrap in a dedicated helper.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* const names = await store.traversal((g) => g.V().hasLabel('person').values('name'));
|
|
247
|
+
*/
|
|
248
|
+
async traversal(build) {
|
|
249
|
+
const g = this.requireG();
|
|
250
|
+
try {
|
|
251
|
+
const t = build(g, gremlin_1.default.process.statics);
|
|
252
|
+
const list = await t.toList();
|
|
253
|
+
return list.map(normalizeValue);
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
throw wrapNeptuneError(err, 'traversal');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Submits a raw Gremlin script string via the driver's script-eval
|
|
261
|
+
* `Client` (as opposed to the fluent bytecode traversal `g` used
|
|
262
|
+
* elsewhere in this class), with `bindings` substituted server-side for
|
|
263
|
+
* named parameters in the script (e.g. `'g.V(id)'` with
|
|
264
|
+
* `{ id: '1' }`) — the standard way to parameterize Gremlin script text
|
|
265
|
+
* without string-concatenating untrusted values into it.
|
|
266
|
+
*/
|
|
267
|
+
async submit(script, bindings = {}) {
|
|
268
|
+
const client = this.requireClient();
|
|
269
|
+
try {
|
|
270
|
+
const resultSet = await client.submit(script, bindings);
|
|
271
|
+
const list = typeof resultSet.toArray === 'function' ? resultSet.toArray() : resultSet;
|
|
272
|
+
return list.map(normalizeValue);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
throw wrapNeptuneError(err, `script submit ('${script}')`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// ---------------------------------------------------------------------
|
|
279
|
+
// Vertex CRUD
|
|
280
|
+
// ---------------------------------------------------------------------
|
|
281
|
+
/** `addV(label)` + a `property(key, value)` step per entry in `properties`, returning the created vertex via `elementMap()`. */
|
|
282
|
+
async addVertex(label, properties = {}) {
|
|
283
|
+
const g = this.requireG();
|
|
284
|
+
try {
|
|
285
|
+
let t = g.addV(label);
|
|
286
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
287
|
+
t = t.property(key, value);
|
|
288
|
+
}
|
|
289
|
+
const { value } = await t.elementMap().next();
|
|
290
|
+
return toVertex(normalizeValue(value));
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
throw wrapNeptuneError(err, `addVertex('${label}')`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/** `g.V(id).elementMap().next()` — returns `undefined` if no vertex with that ID exists. */
|
|
297
|
+
async getVertex(id) {
|
|
298
|
+
const g = this.requireG();
|
|
299
|
+
try {
|
|
300
|
+
const { value, done } = await g.V(id).elementMap().next();
|
|
301
|
+
if (done || value === undefined || value === null) {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
return toVertex(normalizeValue(value));
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
throw wrapNeptuneError(err, `getVertex('${String(id)}')`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/** `g.V()` filtered by `options.label`/`options.has`/`options.limit`, projected with `elementMap()`. */
|
|
311
|
+
async findVertices(options = {}) {
|
|
312
|
+
const g = this.requireG();
|
|
313
|
+
try {
|
|
314
|
+
let t = g.V();
|
|
315
|
+
t = this.applyElementFilters(t, options);
|
|
316
|
+
const list = await t.elementMap().toList();
|
|
317
|
+
return list.map((v) => toVertex(normalizeValue(v)));
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
throw wrapNeptuneError(err, 'findVertices');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/** Same filters as `findVertices()`, terminated with `count()` instead of materializing elements. */
|
|
324
|
+
async countVertices(options = {}) {
|
|
325
|
+
const g = this.requireG();
|
|
326
|
+
try {
|
|
327
|
+
let t = g.V();
|
|
328
|
+
t = this.applyElementFilters(t, options);
|
|
329
|
+
const { value } = await t.count().next();
|
|
330
|
+
return Number(value);
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
throw wrapNeptuneError(err, 'countVertices');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/** `g.V(id)` + a `property(key, value)` step per entry in `properties` (existing properties keep their value unless overwritten here), returning the updated vertex. */
|
|
337
|
+
async updateVertex(id, properties) {
|
|
338
|
+
const g = this.requireG();
|
|
339
|
+
try {
|
|
340
|
+
let t = g.V(id);
|
|
341
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
342
|
+
t = t.property(key, value);
|
|
343
|
+
}
|
|
344
|
+
const { value, done } = await t.elementMap().next();
|
|
345
|
+
if (done || value === undefined || value === null) {
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
return toVertex(normalizeValue(value));
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
throw wrapNeptuneError(err, `updateVertex('${String(id)}')`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/** `g.V(id).drop().iterate()` — also drops any incident edges (Neptune cascades edge drops when their vertex is dropped). No-op (does not throw) if the vertex doesn't exist. */
|
|
355
|
+
async dropVertex(id) {
|
|
356
|
+
const g = this.requireG();
|
|
357
|
+
try {
|
|
358
|
+
await g.V(id).drop().iterate();
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
throw wrapNeptuneError(err, `dropVertex('${String(id)}')`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// ---------------------------------------------------------------------
|
|
365
|
+
// Edge CRUD
|
|
366
|
+
// ---------------------------------------------------------------------
|
|
367
|
+
/**
|
|
368
|
+
* `g.V(fromId).addE(label).to(statics.V(toId))` + a `property(key, value)`
|
|
369
|
+
* step per entry in `properties`, returning the created edge via
|
|
370
|
+
* `elementMap()`. Throws a `NeptuneError` if `fromId`/`toId` don't
|
|
371
|
+
* resolve to existing vertices (same as real Gremlin `addE`/`to`).
|
|
372
|
+
*/
|
|
373
|
+
async addEdge(fromId, label, toId, properties = {}) {
|
|
374
|
+
const g = this.requireG();
|
|
375
|
+
try {
|
|
376
|
+
let t = g.V(fromId).addE(label).to(gremlin_1.default.process.statics.V(toId));
|
|
377
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
378
|
+
t = t.property(key, value);
|
|
379
|
+
}
|
|
380
|
+
const { value } = await t.elementMap().next();
|
|
381
|
+
return toEdge(normalizeValue(value));
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
throw wrapNeptuneError(err, `addEdge('${label}')`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/** `g.E(id).elementMap().next()` — returns `undefined` if no edge with that ID exists. */
|
|
388
|
+
async getEdge(id) {
|
|
389
|
+
const g = this.requireG();
|
|
390
|
+
try {
|
|
391
|
+
const { value, done } = await g.E(id).elementMap().next();
|
|
392
|
+
if (done || value === undefined || value === null) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
return toEdge(normalizeValue(value));
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
throw wrapNeptuneError(err, `getEdge('${String(id)}')`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/** `g.E()` filtered by `options.label`/`options.has`/`options.limit`, projected with `elementMap()`. */
|
|
402
|
+
async findEdges(options = {}) {
|
|
403
|
+
const g = this.requireG();
|
|
404
|
+
try {
|
|
405
|
+
let t = g.E();
|
|
406
|
+
t = this.applyElementFilters(t, options);
|
|
407
|
+
const list = await t.elementMap().toList();
|
|
408
|
+
return list.map((v) => toEdge(normalizeValue(v)));
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
throw wrapNeptuneError(err, 'findEdges');
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/** `g.E(id).drop().iterate()`. No-op (does not throw) if the edge doesn't exist. */
|
|
415
|
+
async dropEdge(id) {
|
|
416
|
+
const g = this.requireG();
|
|
417
|
+
try {
|
|
418
|
+
await g.E(id).drop().iterate();
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
throw wrapNeptuneError(err, `dropEdge('${String(id)}')`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// ---------------------------------------------------------------------
|
|
425
|
+
// Traversal convenience
|
|
426
|
+
// ---------------------------------------------------------------------
|
|
427
|
+
/**
|
|
428
|
+
* `g.V(id).out(edgeLabel)` (or `.in_()`/`.both()` per `options.direction`,
|
|
429
|
+
* default `'out'`), projected with `elementMap()`. The common
|
|
430
|
+
* "one-hop neighbors" query — for multi-hop traversals, path-finding, or
|
|
431
|
+
* anything else Gremlin's step library covers beyond one hop, use
|
|
432
|
+
* `traversal()` directly.
|
|
433
|
+
*/
|
|
434
|
+
async neighbors(id, options = {}) {
|
|
435
|
+
const g = this.requireG();
|
|
436
|
+
const direction = options.direction ?? 'out';
|
|
437
|
+
try {
|
|
438
|
+
let t = g.V(id);
|
|
439
|
+
const step = direction === 'in' ? 'in_' : direction === 'both' ? 'both' : 'out';
|
|
440
|
+
t = options.edgeLabel ? t[step](options.edgeLabel) : t[step]();
|
|
441
|
+
if (options.limit !== undefined) {
|
|
442
|
+
t = t.limit(options.limit);
|
|
443
|
+
}
|
|
444
|
+
const list = await t.elementMap().toList();
|
|
445
|
+
return list.map((v) => toVertex(normalizeValue(v)));
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
throw wrapNeptuneError(err, `neighbors('${String(id)}')`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// ---------------------------------------------------------------------
|
|
452
|
+
// Internal helpers
|
|
453
|
+
// ---------------------------------------------------------------------
|
|
454
|
+
applyElementFilters(t, options) {
|
|
455
|
+
if (options.label) {
|
|
456
|
+
t = t.hasLabel(options.label);
|
|
457
|
+
}
|
|
458
|
+
if (options.has) {
|
|
459
|
+
for (const [key, value] of Object.entries(options.has)) {
|
|
460
|
+
t = t.has(key, value);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (options.limit !== undefined) {
|
|
464
|
+
t = t.limit(options.limit);
|
|
465
|
+
}
|
|
466
|
+
return t;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
exports.NeptuneStore = NeptuneStore;
|
|
470
|
+
exports.default = NeptuneStore;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* NSQ topic/channel queue store.
|
|
4
|
+
*
|
|
5
|
+
* NSQ is a realtime distributed messaging platform. Producers `publish`
|
|
6
|
+
* messages to a *topic* via a `Writer`; consumers create a `Reader` bound to
|
|
7
|
+
* a topic + *channel* and handle each delivered message (calling
|
|
8
|
+
* `msg.finish()`/`msg.requeue()`). NSQ distributes a topic's messages across
|
|
9
|
+
* all channels, and load-balances within a channel. It is not a database and
|
|
10
|
+
* does not fit the SQL-shaped `Dialect` interface, so this class implements
|
|
11
|
+
* the minimal `NoSqlStore` marker interface and exposes NSQ's real
|
|
12
|
+
* publish/subscribe operations instead.
|
|
13
|
+
*
|
|
14
|
+
* Uses the `nsqjs` driver. It is an optional peer loaded lazily (via
|
|
15
|
+
* `require`) inside `connect()`, so importing this module does not require it
|
|
16
|
+
* to be installed unless you actually connect. A pre-built driver module
|
|
17
|
+
* (providing `Writer` and `Reader` constructors) can be injected via
|
|
18
|
+
* `NsqStoreOptions.client`, in which case `connect()` adopts it and skips the
|
|
19
|
+
* `require` entirely (used by the unit tests to inject mocks).
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.NsqStore = void 0;
|
|
23
|
+
const errors_1 = require("../../errors");
|
|
24
|
+
function wrapConnectionError(err) {
|
|
25
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
26
|
+
const message = /econnrefused/i.test(parent.message)
|
|
27
|
+
? 'Connection refused'
|
|
28
|
+
: `Unable to connect to NSQ: ${parent.message}`;
|
|
29
|
+
return new errors_1.ConnectionError(message, { parent, database: 'nsq' });
|
|
30
|
+
}
|
|
31
|
+
function wrapDatabaseError(err, action) {
|
|
32
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
33
|
+
return errors_1.DatabaseError.from(parent, { message: `NSQ ${action} failed: ${parent.message}` });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `NsqStore` wraps a single producer `Writer` plus a pool of consumer
|
|
37
|
+
* `Reader`s (keyed by `topic/channel`) and exposes the common queue surface.
|
|
38
|
+
* NSQ has no server-side job registry, so `getJob()`/`remove()` (which imply
|
|
39
|
+
* addressing a job by id) are not supported and throw.
|
|
40
|
+
*/
|
|
41
|
+
class NsqStore {
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
this.name = 'nsq';
|
|
44
|
+
this.library = 'nsqjs';
|
|
45
|
+
this.driver = null;
|
|
46
|
+
this.writer = null;
|
|
47
|
+
this.connected = false;
|
|
48
|
+
this.readers = new Map();
|
|
49
|
+
this.options = options;
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------
|
|
52
|
+
// Connection lifecycle
|
|
53
|
+
// ---------------------------------------------------------------------
|
|
54
|
+
async connect() {
|
|
55
|
+
if (this.connected && this.writer) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
this.driver = this.options.client != null ? this.options.client : require('nsqjs');
|
|
60
|
+
const host = this.options.nsqdHost || '127.0.0.1';
|
|
61
|
+
const port = this.options.nsqdPort || 4150;
|
|
62
|
+
this.writer = new this.driver.Writer(host, port);
|
|
63
|
+
// A real nsqjs Writer connects asynchronously and emits 'ready'; a mock
|
|
64
|
+
// Writer may expose neither, so only wait when both are present.
|
|
65
|
+
if (typeof this.writer.connect === 'function' &&
|
|
66
|
+
typeof this.writer.on === 'function') {
|
|
67
|
+
await new Promise((resolve, reject) => {
|
|
68
|
+
this.writer.on('ready', () => resolve());
|
|
69
|
+
this.writer.on('error', (err) => reject(err));
|
|
70
|
+
this.writer.connect();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
this.connected = true;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
this.writer = null;
|
|
77
|
+
this.driver = null;
|
|
78
|
+
this.connected = false;
|
|
79
|
+
throw wrapConnectionError(err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async disconnect() {
|
|
83
|
+
for (const reader of this.readers.values()) {
|
|
84
|
+
try {
|
|
85
|
+
if (reader && typeof reader.close === 'function')
|
|
86
|
+
reader.close();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Ignore teardown errors.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
this.readers.clear();
|
|
93
|
+
try {
|
|
94
|
+
if (this.writer && typeof this.writer.close === 'function')
|
|
95
|
+
this.writer.close();
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Ignore teardown errors.
|
|
99
|
+
}
|
|
100
|
+
this.writer = null;
|
|
101
|
+
this.driver = null;
|
|
102
|
+
this.connected = false;
|
|
103
|
+
}
|
|
104
|
+
isConnected() {
|
|
105
|
+
return this.connected && this.writer !== null;
|
|
106
|
+
}
|
|
107
|
+
/** Returns the underlying producer `Writer` for operations not wrapped here. */
|
|
108
|
+
getClient() {
|
|
109
|
+
return this.requireWriter();
|
|
110
|
+
}
|
|
111
|
+
requireWriter() {
|
|
112
|
+
if (!this.writer) {
|
|
113
|
+
throw new errors_1.ConnectionError('Not connected to NSQ', { database: 'nsq' });
|
|
114
|
+
}
|
|
115
|
+
return this.writer;
|
|
116
|
+
}
|
|
117
|
+
// ---------------------------------------------------------------------
|
|
118
|
+
// Queue operations
|
|
119
|
+
// ---------------------------------------------------------------------
|
|
120
|
+
/**
|
|
121
|
+
* Publish a message to a topic via the producer `Writer`. The payload is
|
|
122
|
+
* JSON-serialized (strings/Buffers pass through). NSQ does not return a
|
|
123
|
+
* client-visible job id, so this resolves to `null`.
|
|
124
|
+
*/
|
|
125
|
+
async enqueue(queue, jobData) {
|
|
126
|
+
const writer = this.requireWriter();
|
|
127
|
+
const message = typeof jobData === 'string' || Buffer.isBuffer(jobData) ? jobData : JSON.stringify(jobData);
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
try {
|
|
130
|
+
writer.publish(queue, message, (err) => {
|
|
131
|
+
if (err)
|
|
132
|
+
reject(wrapDatabaseError(err, 'enqueue'));
|
|
133
|
+
else
|
|
134
|
+
resolve(null);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
reject(wrapDatabaseError(err, 'enqueue'));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Subscribe a `Reader` to `queue` (topic) on a channel and invoke
|
|
144
|
+
* `handler(msg)` for each delivered message. One reader is cached per
|
|
145
|
+
* `topic/channel`; a repeat `process()` on the same pair returns it.
|
|
146
|
+
*/
|
|
147
|
+
async process(queue, handler, opts = {}) {
|
|
148
|
+
try {
|
|
149
|
+
const { channel = 'default', ...readerOpts } = opts;
|
|
150
|
+
const key = `${queue}/${channel}`;
|
|
151
|
+
const existing = this.readers.get(key);
|
|
152
|
+
if (existing)
|
|
153
|
+
return existing;
|
|
154
|
+
const driver = this.driver;
|
|
155
|
+
if (!driver) {
|
|
156
|
+
throw new errors_1.ConnectionError('Not connected to NSQ', { database: 'nsq' });
|
|
157
|
+
}
|
|
158
|
+
const reader = new driver.Reader(queue, channel, {
|
|
159
|
+
...(this.options.readerOptions || {}),
|
|
160
|
+
...readerOpts,
|
|
161
|
+
});
|
|
162
|
+
reader.on('message', (msg) => handler(msg));
|
|
163
|
+
reader.connect();
|
|
164
|
+
this.readers.set(key, reader);
|
|
165
|
+
return reader;
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
if (err instanceof errors_1.ConnectionError)
|
|
169
|
+
throw err;
|
|
170
|
+
throw wrapDatabaseError(err, 'process');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Not supported: NSQ exposes no server-side lookup of a message by id. */
|
|
174
|
+
async getJob(_queue, _id) {
|
|
175
|
+
throw new errors_1.DatabaseError('NSQ does not support getJob (no addressable job ids)', {
|
|
176
|
+
code: undefined,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** Not supported: NSQ messages are acknowledged via `finish()`, not removed by id. */
|
|
180
|
+
async remove(_queue, _id) {
|
|
181
|
+
throw new errors_1.DatabaseError('NSQ does not support remove (use msg.finish() in the handler)', {
|
|
182
|
+
code: undefined,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Return a lightweight local view of consumers for a topic: the number of
|
|
187
|
+
* active `Reader`s this store has bound to it. (NSQ's authoritative stats
|
|
188
|
+
* live in nsqd's HTTP `/stats` endpoint, not the client library.)
|
|
189
|
+
*/
|
|
190
|
+
async stats(queue) {
|
|
191
|
+
let count = 0;
|
|
192
|
+
for (const key of this.readers.keys()) {
|
|
193
|
+
if (key.startsWith(`${queue}/`))
|
|
194
|
+
count++;
|
|
195
|
+
}
|
|
196
|
+
return { topic: queue, readers: count };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.NsqStore = NsqStore;
|
|
200
|
+
exports.default = NsqStore;
|