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,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Apache Kafka store implementation.
|
|
4
|
+
*
|
|
5
|
+
* Kafka is a distributed, partitioned, append-only commit log used as a
|
|
6
|
+
* message broker / event-streaming platform. It has no query language and
|
|
7
|
+
* does not fit the SQL-shaped `Dialect` interface, so this class implements
|
|
8
|
+
* the minimal `NoSqlStore` marker interface and exposes Kafka's real
|
|
9
|
+
* operations - producing to topics, creating/listing topics via the admin
|
|
10
|
+
* client, and subscribing consumers to topics - instead of forcing
|
|
11
|
+
* everything into a `query(sql)` shape.
|
|
12
|
+
*
|
|
13
|
+
* Uses the `kafkajs` driver. `kafkajs` is loaded lazily inside `connect()`
|
|
14
|
+
* (or supplied pre-built via `options.client`) so importing this module does
|
|
15
|
+
* not require the driver to be installed.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.KafkaStore = void 0;
|
|
19
|
+
const errors_1 = require("../../errors");
|
|
20
|
+
function wrapConnectionError(err, brokers) {
|
|
21
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
22
|
+
const message = /econnrefused/i.test(parent.message)
|
|
23
|
+
? 'Connection refused'
|
|
24
|
+
: `Unable to connect to Kafka: ${parent.message}`;
|
|
25
|
+
return new errors_1.ConnectionError(message, {
|
|
26
|
+
parent,
|
|
27
|
+
database: 'kafka',
|
|
28
|
+
host: brokers?.join(','),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function wrapDatabaseError(err, message) {
|
|
32
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
33
|
+
const combined = message ? `${message}: ${parent.message}` : parent.message;
|
|
34
|
+
return errors_1.DatabaseError.from(parent, { message: combined });
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* `KafkaStore` wraps a `kafkajs` client and exposes Kafka's core operations:
|
|
38
|
+
* producing messages, topic administration, and consumer subscriptions.
|
|
39
|
+
*/
|
|
40
|
+
class KafkaStore {
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.name = 'kafka';
|
|
43
|
+
this.library = 'kafkajs';
|
|
44
|
+
this.client = null;
|
|
45
|
+
this.producer = null;
|
|
46
|
+
/** Consumers created via `subscribe()`, tracked so `disconnect()` can stop them all. */
|
|
47
|
+
this.consumers = [];
|
|
48
|
+
this.connected = false;
|
|
49
|
+
this.options = options;
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------
|
|
52
|
+
// Connection lifecycle
|
|
53
|
+
// ---------------------------------------------------------------------
|
|
54
|
+
async connect() {
|
|
55
|
+
if (this.connected && this.client) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
if (this.options.client) {
|
|
60
|
+
this.client = this.options.client;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Lazy-load the driver so importing this module doesn't require
|
|
64
|
+
// `kafkajs` to be installed unless a real connection is made.
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
66
|
+
const { Kafka } = require('kafkajs');
|
|
67
|
+
this.client = new Kafka({
|
|
68
|
+
clientId: this.options.clientId,
|
|
69
|
+
brokers: this.options.brokers ?? [],
|
|
70
|
+
ssl: this.options.ssl,
|
|
71
|
+
sasl: this.options.sasl,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
this.producer = this.client.producer();
|
|
75
|
+
await this.producer.connect();
|
|
76
|
+
this.connected = true;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
this.client = null;
|
|
80
|
+
this.producer = null;
|
|
81
|
+
this.connected = false;
|
|
82
|
+
throw wrapConnectionError(err, this.options.brokers);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async disconnect() {
|
|
86
|
+
// Stop consumers first so they stop pulling before the producer/client go.
|
|
87
|
+
for (const consumer of this.consumers.splice(0)) {
|
|
88
|
+
try {
|
|
89
|
+
await consumer.disconnect();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Best-effort teardown - keep disconnecting the rest.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (this.producer) {
|
|
96
|
+
try {
|
|
97
|
+
await this.producer.disconnect();
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Best-effort teardown.
|
|
101
|
+
}
|
|
102
|
+
this.producer = null;
|
|
103
|
+
}
|
|
104
|
+
this.client = null;
|
|
105
|
+
this.connected = false;
|
|
106
|
+
}
|
|
107
|
+
isConnected() {
|
|
108
|
+
return this.connected && this.client !== null;
|
|
109
|
+
}
|
|
110
|
+
/** Returns the underlying `kafkajs` `Kafka` client for anything not wrapped here. */
|
|
111
|
+
getClient() {
|
|
112
|
+
return this.requireClient();
|
|
113
|
+
}
|
|
114
|
+
requireClient() {
|
|
115
|
+
if (!this.client) {
|
|
116
|
+
throw new errors_1.ConnectionError('Not connected to Kafka', { database: 'kafka' });
|
|
117
|
+
}
|
|
118
|
+
return this.client;
|
|
119
|
+
}
|
|
120
|
+
requireProducer() {
|
|
121
|
+
if (!this.producer) {
|
|
122
|
+
throw new errors_1.ConnectionError('Not connected to Kafka', { database: 'kafka' });
|
|
123
|
+
}
|
|
124
|
+
return this.producer;
|
|
125
|
+
}
|
|
126
|
+
// ---------------------------------------------------------------------
|
|
127
|
+
// Producing
|
|
128
|
+
// ---------------------------------------------------------------------
|
|
129
|
+
/**
|
|
130
|
+
* Produce one or more messages to `topic`. Returns kafkajs's record
|
|
131
|
+
* metadata (per-partition base offsets).
|
|
132
|
+
*/
|
|
133
|
+
async produce(topic, messages) {
|
|
134
|
+
const producer = this.requireProducer();
|
|
135
|
+
try {
|
|
136
|
+
return await producer.send({ topic, messages });
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
throw wrapDatabaseError(err, `Kafka produce to "${topic}" failed`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// ---------------------------------------------------------------------
|
|
143
|
+
// Topic administration
|
|
144
|
+
// ---------------------------------------------------------------------
|
|
145
|
+
/**
|
|
146
|
+
* Create `topic` via a short-lived admin connection. Returns kafkajs's
|
|
147
|
+
* boolean indicating whether the topic was newly created (`false` if it
|
|
148
|
+
* already existed).
|
|
149
|
+
*/
|
|
150
|
+
async createTopic(topic, opts = {}) {
|
|
151
|
+
const client = this.requireClient();
|
|
152
|
+
const admin = client.admin();
|
|
153
|
+
try {
|
|
154
|
+
await admin.connect();
|
|
155
|
+
return await admin.createTopics({
|
|
156
|
+
topics: [
|
|
157
|
+
{
|
|
158
|
+
topic,
|
|
159
|
+
numPartitions: opts.numPartitions,
|
|
160
|
+
replicationFactor: opts.replicationFactor,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
throw wrapDatabaseError(err, `Kafka createTopic "${topic}" failed`);
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
try {
|
|
170
|
+
await admin.disconnect();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Best-effort teardown of the admin connection.
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** List all topic names known to the cluster via a short-lived admin connection. */
|
|
178
|
+
async listTopics() {
|
|
179
|
+
const client = this.requireClient();
|
|
180
|
+
const admin = client.admin();
|
|
181
|
+
try {
|
|
182
|
+
await admin.connect();
|
|
183
|
+
return await admin.listTopics();
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
throw wrapDatabaseError(err, 'Kafka listTopics failed');
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
try {
|
|
190
|
+
await admin.disconnect();
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Best-effort teardown of the admin connection.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// ---------------------------------------------------------------------
|
|
198
|
+
// Consuming
|
|
199
|
+
// ---------------------------------------------------------------------
|
|
200
|
+
/**
|
|
201
|
+
* Subscribe a new consumer to `topics` and invoke `handler` for each
|
|
202
|
+
* message (kafkajs's `eachMessage` payload: `{ topic, partition, message }`).
|
|
203
|
+
* The consumer joins the group `options.groupId` (a generated id is used if
|
|
204
|
+
* omitted) and is tracked so `disconnect()` stops it. Returns the created
|
|
205
|
+
* kafkajs consumer.
|
|
206
|
+
*/
|
|
207
|
+
async subscribe(topics, handler, options = {}) {
|
|
208
|
+
const client = this.requireClient();
|
|
209
|
+
const topicList = Array.isArray(topics) ? topics : [topics];
|
|
210
|
+
const groupId = options.groupId ?? `${this.options.clientId ?? 'kafka'}-group-${Date.now()}`;
|
|
211
|
+
const consumer = client.consumer({ groupId });
|
|
212
|
+
try {
|
|
213
|
+
await consumer.connect();
|
|
214
|
+
await consumer.subscribe({
|
|
215
|
+
topics: topicList,
|
|
216
|
+
fromBeginning: options.fromBeginning ?? false,
|
|
217
|
+
});
|
|
218
|
+
await consumer.run({ eachMessage: handler });
|
|
219
|
+
this.consumers.push(consumer);
|
|
220
|
+
return consumer;
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
try {
|
|
224
|
+
await consumer.disconnect();
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Don't leak the half-set-up consumer's connection.
|
|
228
|
+
}
|
|
229
|
+
throw wrapDatabaseError(err, `Kafka subscribe to "${topicList.join(',')}" failed`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
exports.KafkaStore = KafkaStore;
|
|
234
|
+
exports.default = KafkaStore;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KeyspacesStore = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Amazon Keyspaces (for Apache Cassandra) store.
|
|
6
|
+
*
|
|
7
|
+
* Amazon Keyspaces is AWS's serverless, fully managed wide-column store that is
|
|
8
|
+
* compatible with Apache Cassandra: it speaks CQL (Cassandra Query Language)
|
|
9
|
+
* over the same native protocol, so the standard `cassandra-driver` (DataStax
|
|
10
|
+
* Node.js driver) connects to it unchanged.
|
|
11
|
+
*
|
|
12
|
+
* DEPLOYMENT REQUIREMENTS (real clusters — not exercised by the mock tests):
|
|
13
|
+
* - TLS is mandatory. Keyspaces only accepts encrypted connections, so a real
|
|
14
|
+
* deployment must pass SSL options (e.g. `sslOptions` with the Starfield /
|
|
15
|
+
* Amazon root CA) through `clientOptions`.
|
|
16
|
+
* - Authentication is either service-specific credentials (username/password
|
|
17
|
+
* via `credentials`) or, preferably, SigV4 request signing using AWS IAM
|
|
18
|
+
* credentials via the `aws-sigv4-auth-cassandra-plugin` `AuthProvider`
|
|
19
|
+
* (also passed through `clientOptions`).
|
|
20
|
+
* - Keyspaces default read/write consistency is `LOCAL_QUORUM`; set it via
|
|
21
|
+
* query options / driver profiles as your workload requires.
|
|
22
|
+
* All of these are plain `cassandra-driver` `Client` options, so they flow
|
|
23
|
+
* through this store unchanged via `clientOptions` (or a pre-built `client`).
|
|
24
|
+
*
|
|
25
|
+
* Like Cassandra, Keyspaces omits joins, arbitrary WHERE-clause filtering, and
|
|
26
|
+
* subqueries: queries are partition-scoped and data is modelled query-first
|
|
27
|
+
* around partition and clustering keys. That model does not fit the SQL-shaped
|
|
28
|
+
* `Dialect` interface (`src/dialects/dialect.ts`), so this store implements the
|
|
29
|
+
* minimal `NoSqlStore` marker interface (`src/nosql/store.ts`) and exposes CQL
|
|
30
|
+
* execution directly rather than pretending to be a relational dialect.
|
|
31
|
+
*
|
|
32
|
+
* The driver is an optional peer dependency loaded lazily inside `connect()`
|
|
33
|
+
* (via `require`) so importing this module never forces the driver to be
|
|
34
|
+
* installed; tests inject a pre-built mock `Client` instead.
|
|
35
|
+
*/
|
|
36
|
+
const errors_1 = require("../../errors");
|
|
37
|
+
/**
|
|
38
|
+
* `KeyspacesStore` wraps a `cassandra-driver` `Client` pointed at an Amazon
|
|
39
|
+
* Keyspaces endpoint and exposes CQL execution (`execute`), atomic
|
|
40
|
+
* multi-statement batches (`batch`), and a few parameterized convenience
|
|
41
|
+
* helpers (`insert`, `selectAll`).
|
|
42
|
+
*
|
|
43
|
+
* Statements are prepared by default (`prepare: true`). All Keyspaces-specific
|
|
44
|
+
* connection concerns (TLS, SigV4/credentials, LOCAL_QUORUM consistency) are
|
|
45
|
+
* plain driver options and pass through via `clientOptions` or a pre-built
|
|
46
|
+
* `client`.
|
|
47
|
+
*/
|
|
48
|
+
class KeyspacesStore {
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
this.name = 'keyspaces';
|
|
51
|
+
this.library = 'cassandra-driver';
|
|
52
|
+
this.client = null;
|
|
53
|
+
this.connected = false;
|
|
54
|
+
this.options = options;
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------
|
|
57
|
+
// NoSqlStore lifecycle
|
|
58
|
+
// ---------------------------------------------------------------------
|
|
59
|
+
async connect() {
|
|
60
|
+
if (this.connected && this.client) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
let client = this.options.client;
|
|
65
|
+
if (!client) {
|
|
66
|
+
// Lazy-load the optional driver so importing this module never
|
|
67
|
+
// requires `cassandra-driver` to be installed. Tests inject a mock
|
|
68
|
+
// `client` above and never reach this branch.
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
70
|
+
const cql = require('cassandra-driver');
|
|
71
|
+
client = new cql.Client({
|
|
72
|
+
contactPoints: this.options.contactPoints,
|
|
73
|
+
localDataCenter: this.options.localDataCenter,
|
|
74
|
+
keyspace: this.options.keyspace,
|
|
75
|
+
credentials: this.options.credentials,
|
|
76
|
+
...this.options.clientOptions,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
await client.connect();
|
|
80
|
+
this.client = client;
|
|
81
|
+
this.connected = true;
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
this.client = null;
|
|
85
|
+
this.connected = false;
|
|
86
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
87
|
+
throw new errors_1.ConnectionError(`Unable to connect to Amazon Keyspaces: ${parent.message}`, {
|
|
88
|
+
parent,
|
|
89
|
+
database: 'keyspaces',
|
|
90
|
+
host: this.options.contactPoints?.[0],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async disconnect() {
|
|
95
|
+
if (this.client) {
|
|
96
|
+
try {
|
|
97
|
+
await this.client.shutdown();
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
this.client = null;
|
|
101
|
+
this.connected = false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.connected = false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
isConnected() {
|
|
109
|
+
return this.connected && this.client !== null;
|
|
110
|
+
}
|
|
111
|
+
/** Returns the underlying `cassandra-driver` `Client` for anything not wrapped here. */
|
|
112
|
+
getClient() {
|
|
113
|
+
return this.requireClient();
|
|
114
|
+
}
|
|
115
|
+
requireClient() {
|
|
116
|
+
if (!this.client || !this.connected) {
|
|
117
|
+
throw new errors_1.ConnectionError('Amazon Keyspaces client is not connected. Call connect() first.', {
|
|
118
|
+
database: 'keyspaces',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return this.client;
|
|
122
|
+
}
|
|
123
|
+
wrapError(action, err) {
|
|
124
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
125
|
+
return errors_1.DatabaseError.from(parent, {
|
|
126
|
+
message: `Amazon Keyspaces ${action} failed: ${parent.message}`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// ---------------------------------------------------------------------
|
|
130
|
+
// CQL execution
|
|
131
|
+
// ---------------------------------------------------------------------
|
|
132
|
+
/**
|
|
133
|
+
* Execute a single CQL statement with optional bound `params`. Statements
|
|
134
|
+
* are prepared by default (`prepare: true`); pass `opts.prepare = false` to
|
|
135
|
+
* run a one-off unprepared. Any other driver query options (including
|
|
136
|
+
* `consistency`, which defaults to `LOCAL_QUORUM` on Keyspaces) are forwarded
|
|
137
|
+
* via `opts`. Returns the full driver result (with `.rows`).
|
|
138
|
+
*/
|
|
139
|
+
async execute(query, params = [], opts = {}) {
|
|
140
|
+
const client = this.requireClient();
|
|
141
|
+
try {
|
|
142
|
+
return (await client.execute(query, params, { prepare: true, ...opts }));
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
throw this.wrapError(`execute (${query})`, err);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Execute multiple statements as a single logged batch. Each entry is a
|
|
150
|
+
* `{ query, params }` pair. Prepared by default; forward other driver batch
|
|
151
|
+
* options via `opts`.
|
|
152
|
+
*/
|
|
153
|
+
async batch(queries, opts = {}) {
|
|
154
|
+
const client = this.requireClient();
|
|
155
|
+
try {
|
|
156
|
+
return await client.batch(queries, { prepare: true, ...opts });
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
throw this.wrapError('batch', err);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// ---------------------------------------------------------------------
|
|
163
|
+
// Convenience helpers
|
|
164
|
+
// ---------------------------------------------------------------------
|
|
165
|
+
/**
|
|
166
|
+
* Convenience `INSERT`: builds a parameterized
|
|
167
|
+
* `INSERT INTO table (cols...) VALUES (?, ?, ...)` from `obj`'s keys and
|
|
168
|
+
* executes it (prepared). For upserts with TTL, IF NOT EXISTS, or other
|
|
169
|
+
* clauses, write the CQL yourself and call `execute()`.
|
|
170
|
+
*/
|
|
171
|
+
async insert(table, obj, opts = {}) {
|
|
172
|
+
const columns = Object.keys(obj);
|
|
173
|
+
if (columns.length === 0) {
|
|
174
|
+
throw new errors_1.DatabaseError('insert() requires at least one column', {
|
|
175
|
+
original: 'empty insert object',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const placeholders = columns.map(() => '?').join(', ');
|
|
179
|
+
const query = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders})`;
|
|
180
|
+
const params = columns.map((c) => obj[c]);
|
|
181
|
+
return this.execute(query, params, opts);
|
|
182
|
+
}
|
|
183
|
+
/** Convenience `SELECT * FROM table`. Returns the full driver result (with `.rows`). */
|
|
184
|
+
async selectAll(table, opts = {}) {
|
|
185
|
+
return this.execute(`SELECT * FROM ${table}`, [], opts);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
exports.KeyspacesStore = KeyspacesStore;
|
|
189
|
+
exports.default = KeyspacesStore;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KinesisStore = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Amazon Kinesis Data Streams store.
|
|
6
|
+
*
|
|
7
|
+
* Kinesis is a managed, sharded, append-only record-streaming service — a
|
|
8
|
+
* cloud sibling of Kafka. There is no query language and no SQL-shaped access
|
|
9
|
+
* pattern, so this store implements the minimal `NoSqlStore` marker interface
|
|
10
|
+
* (`src/nosql/store.ts`) and exposes Kinesis's real streaming operations:
|
|
11
|
+
* putting records to a stream (`publish`), a shard-iterator poll loop that
|
|
12
|
+
* delivers records to a handler (`subscribe`), and stream administration
|
|
13
|
+
* (`createStream`).
|
|
14
|
+
*
|
|
15
|
+
* Built on AWS SDK v3 (`@aws-sdk/client-kinesis`): a `KinesisClient` speaks the
|
|
16
|
+
* wire protocol via the command pattern — you build a command object
|
|
17
|
+
* (`new PutRecordCommand(input)`) and hand it to `client.send(command)`. The
|
|
18
|
+
* SDK is loaded lazily inside `connect()` (via `require`) rather than a
|
|
19
|
+
* top-level import so importing this module never hard-requires the driver,
|
|
20
|
+
* and so tests can inject a pre-built mock `client` (with a `send(command)`
|
|
21
|
+
* method) and never load the SDK or touch the network.
|
|
22
|
+
*
|
|
23
|
+
* Message encoding: `publish()` accepts a `string` (UTF-8 encoded), a `Buffer`
|
|
24
|
+
* (sent as-is), or any other JSON-serializable value (`JSON.stringify`d, then
|
|
25
|
+
* UTF-8 encoded). The bytes become the record's `Data`.
|
|
26
|
+
*/
|
|
27
|
+
const errors_1 = require("../../errors");
|
|
28
|
+
/** Encode a message into the `Uint8Array` Kinesis stores as a record `Data`. */
|
|
29
|
+
function toBytes(message) {
|
|
30
|
+
if (Buffer.isBuffer(message))
|
|
31
|
+
return message;
|
|
32
|
+
if (message instanceof Uint8Array)
|
|
33
|
+
return message;
|
|
34
|
+
if (typeof message === 'string')
|
|
35
|
+
return Buffer.from(message, 'utf8');
|
|
36
|
+
return Buffer.from(JSON.stringify(message), 'utf8');
|
|
37
|
+
}
|
|
38
|
+
function sleep(ms) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const timer = setTimeout(resolve, ms);
|
|
41
|
+
// Don't let a pending poll-interval timer keep the process alive.
|
|
42
|
+
timer.unref?.();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `KinesisStore` wraps an AWS v3 `KinesisClient` and exposes stream producing,
|
|
47
|
+
* a shard-iterator consumer loop, and stream administration.
|
|
48
|
+
*/
|
|
49
|
+
class KinesisStore {
|
|
50
|
+
constructor(options = {}) {
|
|
51
|
+
this.name = 'kinesis';
|
|
52
|
+
this.library = '@aws-sdk/client-kinesis';
|
|
53
|
+
this.client = null;
|
|
54
|
+
this.aws = null;
|
|
55
|
+
this.connected = false;
|
|
56
|
+
/** Active subscriptions, tracked so `disconnect()` can stop their poll loops. */
|
|
57
|
+
this.subscriptions = new Set();
|
|
58
|
+
this.options = options;
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------
|
|
61
|
+
// Lifecycle
|
|
62
|
+
// ---------------------------------------------------------------------
|
|
63
|
+
async connect() {
|
|
64
|
+
try {
|
|
65
|
+
if (this.options.client) {
|
|
66
|
+
this.client = this.options.client;
|
|
67
|
+
this.tryLoadSdk();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const aws = require('@aws-sdk/client-kinesis');
|
|
71
|
+
this.aws = aws;
|
|
72
|
+
this.client = new aws.KinesisClient({
|
|
73
|
+
region: this.options.region,
|
|
74
|
+
credentials: this.options.credentials,
|
|
75
|
+
endpoint: this.options.endpoint,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
this.connected = true;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
this.connected = false;
|
|
82
|
+
this.client = null;
|
|
83
|
+
this.aws = null;
|
|
84
|
+
throw new errors_1.ConnectionError(`Failed to connect to Kinesis: ${err instanceof Error ? err.message : String(err)}`, { parent: err instanceof Error ? err : undefined, database: 'kinesis' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Best-effort lazy-load of the SDK for its command constructors (see `command()`). */
|
|
88
|
+
tryLoadSdk() {
|
|
89
|
+
if (this.aws)
|
|
90
|
+
return;
|
|
91
|
+
try {
|
|
92
|
+
this.aws = require('@aws-sdk/client-kinesis');
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
this.aws = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async disconnect() {
|
|
99
|
+
for (const sub of this.subscriptions) {
|
|
100
|
+
try {
|
|
101
|
+
sub.stop();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Best-effort teardown.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.subscriptions.clear();
|
|
108
|
+
try {
|
|
109
|
+
this.client?.destroy?.();
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
this.client = null;
|
|
113
|
+
this.aws = null;
|
|
114
|
+
this.connected = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
isConnected() {
|
|
118
|
+
return this.connected && this.client !== null;
|
|
119
|
+
}
|
|
120
|
+
/** Returns the underlying `KinesisClient` (loosely typed) for anything not wrapped here. */
|
|
121
|
+
getClient() {
|
|
122
|
+
return this.requireClient();
|
|
123
|
+
}
|
|
124
|
+
requireClient() {
|
|
125
|
+
if (!this.client || !this.connected) {
|
|
126
|
+
throw new errors_1.ConnectionError('Kinesis client is not connected. Call connect() first.', {
|
|
127
|
+
database: 'kinesis',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return this.client;
|
|
131
|
+
}
|
|
132
|
+
/** Resolve a command constructor by name from the (lazily loaded) SDK. */
|
|
133
|
+
command(name, input) {
|
|
134
|
+
if (!this.aws)
|
|
135
|
+
this.tryLoadSdk();
|
|
136
|
+
const Ctor = this.aws?.[name];
|
|
137
|
+
if (typeof Ctor !== 'function') {
|
|
138
|
+
throw new errors_1.ConnectionError(`Kinesis command '${name}' is unavailable: '@aws-sdk/client-kinesis' could not be loaded.`, { database: 'kinesis' });
|
|
139
|
+
}
|
|
140
|
+
return new Ctor(input);
|
|
141
|
+
}
|
|
142
|
+
wrapError(action, err) {
|
|
143
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
144
|
+
const name = parent.name ? ` (${parent.name})` : '';
|
|
145
|
+
return new errors_1.DatabaseError(`Kinesis ${action} failed${name}: ${parent.message}`, {
|
|
146
|
+
parent,
|
|
147
|
+
original: parent.message,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------
|
|
151
|
+
// Producing
|
|
152
|
+
// ---------------------------------------------------------------------
|
|
153
|
+
/**
|
|
154
|
+
* `PutRecord` — publish one `message` to `stream`. The message is encoded to
|
|
155
|
+
* bytes (string → UTF-8, Buffer → as-is, else JSON). Returns the shard id and
|
|
156
|
+
* sequence number assigned by Kinesis.
|
|
157
|
+
*/
|
|
158
|
+
async publish(stream, message, options = {}) {
|
|
159
|
+
const client = this.requireClient();
|
|
160
|
+
try {
|
|
161
|
+
const result = await client.send(this.command('PutRecordCommand', {
|
|
162
|
+
StreamName: stream,
|
|
163
|
+
Data: toBytes(message),
|
|
164
|
+
PartitionKey: options.partitionKey ?? `pk-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
165
|
+
ExplicitHashKey: options.explicitHashKey,
|
|
166
|
+
SequenceNumberForOrdering: options.sequenceNumberForOrdering,
|
|
167
|
+
}));
|
|
168
|
+
return { shardId: result?.ShardId, sequenceNumber: result?.SequenceNumber };
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
throw this.wrapError(`PutRecord on '${stream}'`, err);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ---------------------------------------------------------------------
|
|
175
|
+
// Stream administration
|
|
176
|
+
// ---------------------------------------------------------------------
|
|
177
|
+
/** `CreateStream` — provision a new stream. */
|
|
178
|
+
async createStream(name, options = {}) {
|
|
179
|
+
const client = this.requireClient();
|
|
180
|
+
try {
|
|
181
|
+
await client.send(this.command('CreateStreamCommand', {
|
|
182
|
+
StreamName: name,
|
|
183
|
+
ShardCount: options.shardCount ?? 1,
|
|
184
|
+
StreamModeDetails: options.streamMode ? { StreamMode: options.streamMode } : undefined,
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
throw this.wrapError(`CreateStream '${name}'`, err);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** Alias for `createStream()`, matching the common store surface. */
|
|
192
|
+
createTopic(name, options = {}) {
|
|
193
|
+
return this.createStream(name, options);
|
|
194
|
+
}
|
|
195
|
+
// ---------------------------------------------------------------------
|
|
196
|
+
// Consuming
|
|
197
|
+
// ---------------------------------------------------------------------
|
|
198
|
+
/**
|
|
199
|
+
* Subscribe to `stream`: acquire a shard iterator (`GetShardIterator`) and
|
|
200
|
+
* poll `GetRecords` in a loop, invoking `handler` once per record. Returns a
|
|
201
|
+
* subscription whose `stop()` ends the loop; the subscription is also tracked
|
|
202
|
+
* so `disconnect()` stops it. The poll loop runs detached (not awaited); this
|
|
203
|
+
* method resolves once the initial iterator is acquired.
|
|
204
|
+
*/
|
|
205
|
+
async subscribe(stream, handler, options = {}) {
|
|
206
|
+
const client = this.requireClient();
|
|
207
|
+
const shardId = options.shardId ?? 'shardId-000000000000';
|
|
208
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1000;
|
|
209
|
+
let iterator;
|
|
210
|
+
try {
|
|
211
|
+
const iterResult = await client.send(this.command('GetShardIteratorCommand', {
|
|
212
|
+
StreamName: stream,
|
|
213
|
+
ShardId: shardId,
|
|
214
|
+
ShardIteratorType: options.shardIteratorType ?? 'LATEST',
|
|
215
|
+
StartingSequenceNumber: options.startingSequenceNumber,
|
|
216
|
+
}));
|
|
217
|
+
iterator = iterResult?.ShardIterator;
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
throw this.wrapError(`GetShardIterator on '${stream}'`, err);
|
|
221
|
+
}
|
|
222
|
+
let stopped = false;
|
|
223
|
+
const subscription = {
|
|
224
|
+
stop: () => {
|
|
225
|
+
stopped = true;
|
|
226
|
+
this.subscriptions.delete(subscription);
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
this.subscriptions.add(subscription);
|
|
230
|
+
const loop = async () => {
|
|
231
|
+
while (!stopped && iterator) {
|
|
232
|
+
const result = await client.send(this.command('GetRecordsCommand', { ShardIterator: iterator, Limit: options.limit }));
|
|
233
|
+
const records = result?.Records ?? [];
|
|
234
|
+
for (const record of records) {
|
|
235
|
+
if (stopped)
|
|
236
|
+
break;
|
|
237
|
+
await handler(record);
|
|
238
|
+
}
|
|
239
|
+
iterator = result?.NextShardIterator;
|
|
240
|
+
if (stopped || !iterator)
|
|
241
|
+
break;
|
|
242
|
+
await sleep(pollIntervalMs);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
// Detached: consuming is a long-lived loop, not part of subscribe()'s resolution.
|
|
246
|
+
loop().catch(() => {
|
|
247
|
+
/* swallow — the loop is best-effort background polling */
|
|
248
|
+
});
|
|
249
|
+
return subscription;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
exports.KinesisStore = KinesisStore;
|
|
253
|
+
exports.default = KinesisStore;
|