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,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.R2Store = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Cloudflare R2 object-storage store.
|
|
6
|
+
*
|
|
7
|
+
* R2 is Cloudflare's S3-compatible object store: like Amazon S3 it has no
|
|
8
|
+
* query language, no rows, and no SQL-shaped access pattern — data is opaque
|
|
9
|
+
* objects (byte bodies plus metadata) addressed by a `(bucket, key)` pair,
|
|
10
|
+
* where the "key" is a flat string that only *looks* like a path
|
|
11
|
+
* (`foo/bar/baz.json`); R2 has no real directories, just an optional key
|
|
12
|
+
* `prefix` you can filter listings by. None of that fits the SQL-shaped
|
|
13
|
+
* `Dialect` interface, so this store implements the minimal `NoSqlStore`
|
|
14
|
+
* marker interface (`src/nosql/store.ts`) and exposes object-level operations
|
|
15
|
+
* directly.
|
|
16
|
+
*
|
|
17
|
+
* Because R2 speaks the S3 wire protocol, this store is built on AWS SDK v3
|
|
18
|
+
* (`@aws-sdk/client-s3`) exactly like `S3Store`: an `S3Client` uses the
|
|
19
|
+
* command pattern — you construct a command object (`new PutObjectCommand(
|
|
20
|
+
* input)`) and hand it to `client.send(command)`. The only differences from a
|
|
21
|
+
* plain S3 connection are R2-specific client settings: R2 has no regions, so
|
|
22
|
+
* `region` is the sentinel `'auto'`; requests go to an account-specific
|
|
23
|
+
* `endpoint` (e.g. `https://<account_id>.r2.cloudflarestorage.com`); and
|
|
24
|
+
* path-style addressing is required (`forcePathStyle: true`).
|
|
25
|
+
*
|
|
26
|
+
* The SDK is loaded lazily inside `connect()` (via `require`) rather than a
|
|
27
|
+
* top-level import so that importing this module doesn't hard-require the
|
|
28
|
+
* `@aws-sdk/client-s3` package unless an R2 store is actually connected — and
|
|
29
|
+
* so tests can inject a pre-built mock `client` and never load the SDK at all.
|
|
30
|
+
*/
|
|
31
|
+
const errors_1 = require("../../errors");
|
|
32
|
+
/**
|
|
33
|
+
* Cloudflare R2 store, implementing the minimal `NoSqlStore` marker interface.
|
|
34
|
+
* Unlike the SQL dialects there is no `query(sql)` — R2's operations are
|
|
35
|
+
* object-shaped (Put/Get/Delete/List/Head) and that shape is preserved here
|
|
36
|
+
* rather than forced into a SQL string.
|
|
37
|
+
*/
|
|
38
|
+
class R2Store {
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.name = 'r2';
|
|
41
|
+
this.library = '@aws-sdk/client-s3';
|
|
42
|
+
this.client = null;
|
|
43
|
+
this.aws = null;
|
|
44
|
+
this.connected = false;
|
|
45
|
+
this.options = options;
|
|
46
|
+
}
|
|
47
|
+
// ---------------------------------------------------------------------
|
|
48
|
+
// NoSqlStore lifecycle
|
|
49
|
+
// ---------------------------------------------------------------------
|
|
50
|
+
async connect() {
|
|
51
|
+
try {
|
|
52
|
+
if (this.options.client) {
|
|
53
|
+
// A pre-built client was injected (mock or custom). Use it directly
|
|
54
|
+
// and don't require the SDK at all. We still best-effort lazy-load the
|
|
55
|
+
// SDK for the command constructors if it happens to be installed; if
|
|
56
|
+
// not, the command classes are loaded on demand in each method.
|
|
57
|
+
this.client = this.options.client;
|
|
58
|
+
this.tryLoadSdk();
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const aws = require('@aws-sdk/client-s3');
|
|
62
|
+
this.aws = aws;
|
|
63
|
+
this.client = new aws.S3Client({
|
|
64
|
+
// R2 has no regions; the SDK still requires a region, and R2
|
|
65
|
+
// expects the sentinel 'auto'.
|
|
66
|
+
region: this.options.region ?? 'auto',
|
|
67
|
+
// R2 uses an account-specific endpoint rather than the AWS default.
|
|
68
|
+
endpoint: this.options.endpoint,
|
|
69
|
+
credentials: this.options.credentials,
|
|
70
|
+
// R2 requires path-style addressing (endpoint/bucket/key).
|
|
71
|
+
forcePathStyle: true,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
this.connected = true;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
this.connected = false;
|
|
78
|
+
this.client = null;
|
|
79
|
+
this.aws = null;
|
|
80
|
+
throw new errors_1.ConnectionError(`Failed to connect to R2: ${err instanceof Error ? err.message : String(err)}`, { parent: err instanceof Error ? err : undefined });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Attempt to lazy-load `@aws-sdk/client-s3` for its command constructors.
|
|
85
|
+
* Best-effort: when a client was injected (e.g. a test mock) the SDK may
|
|
86
|
+
* not be installed, so a failure here is swallowed and the command classes
|
|
87
|
+
* are loaded on demand instead (see `command()`).
|
|
88
|
+
*/
|
|
89
|
+
tryLoadSdk() {
|
|
90
|
+
if (this.aws)
|
|
91
|
+
return;
|
|
92
|
+
try {
|
|
93
|
+
this.aws = require('@aws-sdk/client-s3');
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
this.aws = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async disconnect() {
|
|
100
|
+
try {
|
|
101
|
+
this.client?.destroy?.();
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
this.client = null;
|
|
105
|
+
this.aws = null;
|
|
106
|
+
this.connected = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
isConnected() {
|
|
110
|
+
return this.connected && this.client !== null;
|
|
111
|
+
}
|
|
112
|
+
/** Returns the underlying `S3Client` (loosely typed) for anything not wrapped here. */
|
|
113
|
+
getClient() {
|
|
114
|
+
return this.requireClient();
|
|
115
|
+
}
|
|
116
|
+
requireClient() {
|
|
117
|
+
if (!this.client || !this.connected) {
|
|
118
|
+
throw new errors_1.ConnectionError('R2 client is not connected. Call connect() first.');
|
|
119
|
+
}
|
|
120
|
+
return this.client;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Resolve an S3 command constructor by name from the (lazily loaded) SDK,
|
|
124
|
+
* so that with an injected mock client we only load `@aws-sdk/client-s3`
|
|
125
|
+
* the first time a command is actually built. Throws a `ConnectionError`
|
|
126
|
+
* if the SDK genuinely can't be resolved.
|
|
127
|
+
*/
|
|
128
|
+
command(name, input) {
|
|
129
|
+
if (!this.aws) {
|
|
130
|
+
this.tryLoadSdk();
|
|
131
|
+
}
|
|
132
|
+
const Ctor = this.aws?.[name];
|
|
133
|
+
if (typeof Ctor !== 'function') {
|
|
134
|
+
throw new errors_1.ConnectionError(`R2 command '${name}' is unavailable: '@aws-sdk/client-s3' could not be loaded.`);
|
|
135
|
+
}
|
|
136
|
+
return new Ctor(input);
|
|
137
|
+
}
|
|
138
|
+
/** Wraps an AWS SDK failure in a `DatabaseError`, preserving the original error. */
|
|
139
|
+
wrapError(action, err) {
|
|
140
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
141
|
+
const name = parent.name ? ` (${parent.name})` : '';
|
|
142
|
+
return new errors_1.DatabaseError(`R2 ${action} failed${name}: ${parent.message}`, {
|
|
143
|
+
parent,
|
|
144
|
+
original: parent.message,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// ---------------------------------------------------------------------
|
|
148
|
+
// Object operations (AWS v3 command pattern via client.send)
|
|
149
|
+
// ---------------------------------------------------------------------
|
|
150
|
+
/** `PutObject` — store an object's body (and optional metadata) under `bucket`/`key`. */
|
|
151
|
+
async putObject(bucket, key, body, options = {}) {
|
|
152
|
+
const client = this.requireClient();
|
|
153
|
+
try {
|
|
154
|
+
const result = await client.send(this.command('PutObjectCommand', {
|
|
155
|
+
Bucket: bucket,
|
|
156
|
+
Key: key,
|
|
157
|
+
Body: body,
|
|
158
|
+
ContentType: options.contentType,
|
|
159
|
+
Metadata: options.metadata,
|
|
160
|
+
CacheControl: options.cacheControl,
|
|
161
|
+
ContentEncoding: options.contentEncoding,
|
|
162
|
+
}));
|
|
163
|
+
return { etag: result?.ETag, versionId: result?.VersionId };
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
throw this.wrapError(`PutObject on '${bucket}/${key}'`, err);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** `GetObject` — fetch an object's body and metadata. */
|
|
170
|
+
async getObject(bucket, key) {
|
|
171
|
+
const client = this.requireClient();
|
|
172
|
+
try {
|
|
173
|
+
const result = await client.send(this.command('GetObjectCommand', { Bucket: bucket, Key: key }));
|
|
174
|
+
return {
|
|
175
|
+
body: result?.Body,
|
|
176
|
+
contentType: result?.ContentType,
|
|
177
|
+
contentLength: result?.ContentLength,
|
|
178
|
+
etag: result?.ETag,
|
|
179
|
+
lastModified: result?.LastModified,
|
|
180
|
+
metadata: result?.Metadata,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
throw this.wrapError(`GetObject on '${bucket}/${key}'`, err);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** `DeleteObject` — remove an object. Succeeds even if the key doesn't exist. */
|
|
188
|
+
async deleteObject(bucket, key) {
|
|
189
|
+
const client = this.requireClient();
|
|
190
|
+
try {
|
|
191
|
+
await client.send(this.command('DeleteObjectCommand', { Bucket: bucket, Key: key }));
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
throw this.wrapError(`DeleteObject on '${bucket}/${key}'`, err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* `ListObjectsV2` — list objects in `bucket`, optionally restricted to
|
|
199
|
+
* those whose key starts with `prefix`. A single call returns at most
|
|
200
|
+
* 1000 objects; use `result.nextContinuationToken` (via the raw client)
|
|
201
|
+
* to page beyond that.
|
|
202
|
+
*/
|
|
203
|
+
async listObjects(bucket, prefix) {
|
|
204
|
+
const client = this.requireClient();
|
|
205
|
+
try {
|
|
206
|
+
const result = await client.send(this.command('ListObjectsV2Command', { Bucket: bucket, Prefix: prefix }));
|
|
207
|
+
const contents = result?.Contents ?? [];
|
|
208
|
+
return {
|
|
209
|
+
objects: contents.map((o) => ({
|
|
210
|
+
key: o?.Key,
|
|
211
|
+
size: o?.Size,
|
|
212
|
+
etag: o?.ETag,
|
|
213
|
+
lastModified: o?.LastModified,
|
|
214
|
+
})),
|
|
215
|
+
isTruncated: Boolean(result?.IsTruncated),
|
|
216
|
+
nextContinuationToken: result?.NextContinuationToken,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
throw this.wrapError(`ListObjects on '${bucket}'`, err);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/** `HeadObject` — fetch an object's metadata without its body. Throws (NotFound) if the object doesn't exist. */
|
|
224
|
+
async headObject(bucket, key) {
|
|
225
|
+
const client = this.requireClient();
|
|
226
|
+
try {
|
|
227
|
+
const result = await client.send(this.command('HeadObjectCommand', { Bucket: bucket, Key: key }));
|
|
228
|
+
return {
|
|
229
|
+
contentType: result?.ContentType,
|
|
230
|
+
contentLength: result?.ContentLength,
|
|
231
|
+
etag: result?.ETag,
|
|
232
|
+
lastModified: result?.LastModified,
|
|
233
|
+
metadata: result?.Metadata,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
throw this.wrapError(`HeadObject on '${bucket}/${key}'`, err);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Whether an object exists, implemented via `HeadObject`. Returns `false`
|
|
242
|
+
* when R2 reports the object is missing (a `NotFound` / 404 error) rather
|
|
243
|
+
* than throwing; any other error (e.g. access denied) is re-thrown.
|
|
244
|
+
*/
|
|
245
|
+
async objectExists(bucket, key) {
|
|
246
|
+
try {
|
|
247
|
+
await this.headObject(bucket, key);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
if (this.isNotFound(err)) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
throw err;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Recognize "object does not exist" errors across the shapes the SDK (and
|
|
259
|
+
* its wrapped form here) can surface them: the raw error's `.name`
|
|
260
|
+
* (`NotFound`, `NoSuchKey`), its HTTP status (404), or the wrapped
|
|
261
|
+
* `DatabaseError`'s `.parent`.
|
|
262
|
+
*/
|
|
263
|
+
isNotFound(err) {
|
|
264
|
+
const candidates = [err];
|
|
265
|
+
const parent = err?.parent;
|
|
266
|
+
if (parent)
|
|
267
|
+
candidates.push(parent);
|
|
268
|
+
for (const c of candidates) {
|
|
269
|
+
const name = c?.name;
|
|
270
|
+
if (name === 'NotFound' || name === 'NoSuchKey')
|
|
271
|
+
return true;
|
|
272
|
+
const status = c?.$metadata?.httpStatusCode ??
|
|
273
|
+
c?.statusCode;
|
|
274
|
+
if (status === 404)
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
exports.R2Store = R2Store;
|
|
281
|
+
exports.default = R2Store;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RabbitMQ (AMQP 0-9-1) store.
|
|
4
|
+
*
|
|
5
|
+
* RabbitMQ is a message broker, not a database: there is no query language,
|
|
6
|
+
* no relational schema, and no `query(sql)` shape. Producers publish
|
|
7
|
+
* messages to *exchanges*, which route them (by binding key/pattern) into
|
|
8
|
+
* *queues*, from which *consumers* read. Like the other stores in
|
|
9
|
+
* `src/nosql/`, this class therefore does not implement the SQL-shaped
|
|
10
|
+
* `Dialect` interface (`src/dialects/dialect.ts`). Instead it implements the
|
|
11
|
+
* minimal `NoSqlStore` marker interface (`src/nosql/store.ts`) and exposes
|
|
12
|
+
* RabbitMQ's real operations — assert/bind topology, publish, and consume —
|
|
13
|
+
* directly, mapped onto a single AMQP channel.
|
|
14
|
+
*
|
|
15
|
+
* Uses the official `amqplib` driver. `amqplib` is an optional peer of this
|
|
16
|
+
* ORM and is loaded lazily (via `require`) inside `connect()`, so importing
|
|
17
|
+
* this module does not require `amqplib` to be installed unless you actually
|
|
18
|
+
* connect. For tests (and for advanced setups that manage the AMQP
|
|
19
|
+
* connection themselves), a pre-built connection can be injected via
|
|
20
|
+
* `RabbitMQStoreOptions.connection`, in which case `connect()` uses it and
|
|
21
|
+
* skips the `require` entirely.
|
|
22
|
+
*
|
|
23
|
+
* ## Message serialization
|
|
24
|
+
*
|
|
25
|
+
* AMQP message bodies are raw bytes (`Buffer`). For convenience, both
|
|
26
|
+
* `sendToQueue()` and `publish()` accept any value: a `Buffer` (or
|
|
27
|
+
* `Uint8Array`) is sent as-is, a `string` is UTF-8 encoded, and anything
|
|
28
|
+
* else is serialized with `JSON.stringify` before being wrapped in a
|
|
29
|
+
* `Buffer`. Consumers receive the raw `amqplib` message object; decode
|
|
30
|
+
* `msg.content` (a `Buffer`) yourself (e.g. `JSON.parse(msg.content.toString())`).
|
|
31
|
+
*/
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.RabbitMQStore = void 0;
|
|
34
|
+
const errors_1 = require("../../errors");
|
|
35
|
+
/**
|
|
36
|
+
* Normalize an arbitrary message value into a `Buffer` suitable for the
|
|
37
|
+
* AMQP wire: `Buffer`/`Uint8Array` pass through, strings are UTF-8 encoded,
|
|
38
|
+
* everything else is `JSON.stringify`'d.
|
|
39
|
+
*/
|
|
40
|
+
function toBuffer(message) {
|
|
41
|
+
if (Buffer.isBuffer(message)) {
|
|
42
|
+
return message;
|
|
43
|
+
}
|
|
44
|
+
if (message instanceof Uint8Array) {
|
|
45
|
+
return Buffer.from(message);
|
|
46
|
+
}
|
|
47
|
+
if (typeof message === 'string') {
|
|
48
|
+
return Buffer.from(message);
|
|
49
|
+
}
|
|
50
|
+
return Buffer.from(JSON.stringify(message));
|
|
51
|
+
}
|
|
52
|
+
function wrapConnectionError(err) {
|
|
53
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
54
|
+
const message = /econnrefused/i.test(parent.message)
|
|
55
|
+
? 'Connection refused'
|
|
56
|
+
: `Unable to connect to RabbitMQ: ${parent.message}`;
|
|
57
|
+
return new errors_1.ConnectionError(message, { parent, database: 'rabbitmq' });
|
|
58
|
+
}
|
|
59
|
+
function wrapDatabaseError(err, action) {
|
|
60
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
61
|
+
return errors_1.DatabaseError.from(parent, { message: `RabbitMQ ${action} failed: ${parent.message}` });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* `RabbitMQStore` wraps a single `amqplib` channel and exposes RabbitMQ's
|
|
65
|
+
* core AMQP operations: asserting queues/exchanges, binding them, publishing
|
|
66
|
+
* (to a queue directly or via an exchange), consuming with ack/nack, and
|
|
67
|
+
* consumer prefetch (QoS).
|
|
68
|
+
*/
|
|
69
|
+
class RabbitMQStore {
|
|
70
|
+
constructor(options = {}) {
|
|
71
|
+
this.name = 'rabbitmq';
|
|
72
|
+
this.library = 'amqplib';
|
|
73
|
+
this.connection = null;
|
|
74
|
+
this.channel = null;
|
|
75
|
+
this.connected = false;
|
|
76
|
+
this.options = options;
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------
|
|
79
|
+
// Connection lifecycle
|
|
80
|
+
// ---------------------------------------------------------------------
|
|
81
|
+
async connect() {
|
|
82
|
+
if (this.connected && this.channel) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
if (this.options.connection != null) {
|
|
87
|
+
// Caller-supplied connection: adopt it, don't require the driver.
|
|
88
|
+
this.connection = this.options.connection;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Lazy-load amqplib only when actually connecting, so importing this
|
|
92
|
+
// module doesn't hard-require the optional driver to be installed.
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
94
|
+
const amqp = require('amqplib');
|
|
95
|
+
this.connection = await amqp.connect(this.options.url || 'amqp://localhost');
|
|
96
|
+
}
|
|
97
|
+
this.channel = await this.connection.createChannel();
|
|
98
|
+
this.connected = true;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
this.channel = null;
|
|
102
|
+
this.connection = null;
|
|
103
|
+
this.connected = false;
|
|
104
|
+
throw wrapConnectionError(err);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async disconnect() {
|
|
108
|
+
try {
|
|
109
|
+
if (this.channel) {
|
|
110
|
+
await this.channel.close();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Ignore channel-close errors; we're tearing down regardless.
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
if (this.connection) {
|
|
118
|
+
await this.connection.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Ignore connection-close errors; we're tearing down regardless.
|
|
123
|
+
}
|
|
124
|
+
this.channel = null;
|
|
125
|
+
this.connection = null;
|
|
126
|
+
this.connected = false;
|
|
127
|
+
}
|
|
128
|
+
isConnected() {
|
|
129
|
+
return this.connected && this.channel !== null;
|
|
130
|
+
}
|
|
131
|
+
/** Returns the underlying `amqplib` channel for operations not wrapped here. */
|
|
132
|
+
getClient() {
|
|
133
|
+
return this.requireChannel();
|
|
134
|
+
}
|
|
135
|
+
/** Returns the underlying `amqplib` connection. */
|
|
136
|
+
getConnection() {
|
|
137
|
+
if (!this.connection) {
|
|
138
|
+
throw new errors_1.ConnectionError('Not connected to RabbitMQ', { database: 'rabbitmq' });
|
|
139
|
+
}
|
|
140
|
+
return this.connection;
|
|
141
|
+
}
|
|
142
|
+
requireChannel() {
|
|
143
|
+
if (!this.channel) {
|
|
144
|
+
throw new errors_1.ConnectionError('Not connected to RabbitMQ', { database: 'rabbitmq' });
|
|
145
|
+
}
|
|
146
|
+
return this.channel;
|
|
147
|
+
}
|
|
148
|
+
async exec(fn, action) {
|
|
149
|
+
const channel = this.requireChannel();
|
|
150
|
+
try {
|
|
151
|
+
return await fn(channel);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
throw wrapDatabaseError(err, action);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ---------------------------------------------------------------------
|
|
158
|
+
// Topology (queues, exchanges, bindings)
|
|
159
|
+
// ---------------------------------------------------------------------
|
|
160
|
+
/**
|
|
161
|
+
* Declare a queue, creating it if it doesn't exist (idempotent). Returns
|
|
162
|
+
* amqplib's assertion reply (`{ queue, messageCount, consumerCount }`).
|
|
163
|
+
* Pass `''` as `queue` to have the broker generate a unique name.
|
|
164
|
+
*/
|
|
165
|
+
async assertQueue(queue, options) {
|
|
166
|
+
return this.exec((c) => c.assertQueue(queue, options), 'assertQueue');
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Declare an exchange of the given `type` (`'direct'`, `'fanout'`,
|
|
170
|
+
* `'topic'`, `'headers'`), creating it if it doesn't exist (idempotent).
|
|
171
|
+
*/
|
|
172
|
+
async assertExchange(exchange, type, options) {
|
|
173
|
+
return this.exec((c) => c.assertExchange(exchange, type, options), 'assertExchange');
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Bind `queue` to `exchange` so messages published to `exchange` matching
|
|
177
|
+
* `pattern` (the routing/binding key) are routed into `queue`.
|
|
178
|
+
*/
|
|
179
|
+
async bindQueue(queue, exchange, pattern) {
|
|
180
|
+
return this.exec((c) => c.bindQueue(queue, exchange, pattern), 'bindQueue');
|
|
181
|
+
}
|
|
182
|
+
// ---------------------------------------------------------------------
|
|
183
|
+
// Producing
|
|
184
|
+
// ---------------------------------------------------------------------
|
|
185
|
+
/**
|
|
186
|
+
* Send a message straight to a named `queue` (via the default exchange).
|
|
187
|
+
* Non-`Buffer` messages are serialized — strings UTF-8 encoded, other
|
|
188
|
+
* values `JSON.stringify`'d (see the module docs). Returns amqplib's
|
|
189
|
+
* boolean write result (`false` when the channel's write buffer is full
|
|
190
|
+
* and you should wait for a `'drain'` event before writing more).
|
|
191
|
+
*/
|
|
192
|
+
async sendToQueue(queue, message, options) {
|
|
193
|
+
return this.exec((c) => c.sendToQueue(queue, toBuffer(message), options), 'sendToQueue');
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Publish a message to an `exchange` with a `routingKey`; the exchange
|
|
197
|
+
* routes it to bound queues. Non-`Buffer` messages are serialized like
|
|
198
|
+
* `sendToQueue()`. Returns amqplib's boolean write result.
|
|
199
|
+
*/
|
|
200
|
+
async publish(exchange, routingKey, message, options) {
|
|
201
|
+
return this.exec((c) => c.publish(exchange, routingKey, toBuffer(message), options), 'publish');
|
|
202
|
+
}
|
|
203
|
+
// ---------------------------------------------------------------------
|
|
204
|
+
// Consuming
|
|
205
|
+
// ---------------------------------------------------------------------
|
|
206
|
+
/**
|
|
207
|
+
* Register `handler` to receive each message delivered from `queue`. The
|
|
208
|
+
* handler is called with the raw amqplib message object (or `null` if the
|
|
209
|
+
* consumer is cancelled by the broker); decode `msg.content` yourself.
|
|
210
|
+
* Unless `options.noAck` is set, call `ack(msg)`/`nack(msg)` when done.
|
|
211
|
+
* Returns amqplib's `{ consumerTag }` reply.
|
|
212
|
+
*/
|
|
213
|
+
async consume(queue, handler, options) {
|
|
214
|
+
return this.exec((c) => c.consume(queue, (msg) => handler(msg), options), 'consume');
|
|
215
|
+
}
|
|
216
|
+
/** Acknowledge a delivered message so the broker can drop it. */
|
|
217
|
+
ack(message, allUpTo) {
|
|
218
|
+
this.requireChannel().ack(message, allUpTo);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Reject a delivered message. By default RabbitMQ requeues it (`requeue`
|
|
222
|
+
* defaults to `true` in amqplib); pass `requeue = false` to drop or
|
|
223
|
+
* dead-letter it.
|
|
224
|
+
*/
|
|
225
|
+
nack(message, allUpTo, requeue) {
|
|
226
|
+
this.requireChannel().nack(message, allUpTo, requeue);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Set the consumer prefetch (QoS) — the max number of unacknowledged
|
|
230
|
+
* messages the broker will deliver to consumers on this channel at once.
|
|
231
|
+
*/
|
|
232
|
+
async prefetch(count, global) {
|
|
233
|
+
return this.exec((c) => c.prefetch(count, global), 'prefetch');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
exports.RabbitMQStore = RabbitMQStore;
|
|
237
|
+
exports.default = RabbitMQStore;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RavenDB store implementation for the Prorm NoSQL layer.
|
|
4
|
+
*
|
|
5
|
+
* RavenDB is a transactional, multi-model document database with a
|
|
6
|
+
* unit-of-work session API (store/load/delete + saveChanges) and a fluent,
|
|
7
|
+
* indexed query builder. It does not fit the SQL-shaped `Dialect` interface,
|
|
8
|
+
* so this class implements the minimal `NoSqlStore` marker interface for
|
|
9
|
+
* connection lifecycle and exposes RavenDB's real session CRUD plus `query`
|
|
10
|
+
* API through a small document-CRUD convenience layer. Each operation opens
|
|
11
|
+
* its own short-lived session and commits with `saveChanges()`.
|
|
12
|
+
*
|
|
13
|
+
* Uses the official `ravendb` driver. That driver is an optional peer
|
|
14
|
+
* dependency and is loaded lazily inside `connect()` so importing this module
|
|
15
|
+
* never requires it to be installed. Tests inject a pre-built mock
|
|
16
|
+
* `DocumentStore` via `options.client`, which bypasses the lazy `require`
|
|
17
|
+
* entirely - no real driver and no network are needed.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.RavenDbStore = void 0;
|
|
21
|
+
const errors_1 = require("../../errors");
|
|
22
|
+
class RavenDbStore {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.name = 'ravendb';
|
|
25
|
+
this.library = 'ravendb';
|
|
26
|
+
this.store = null;
|
|
27
|
+
this.connected = false;
|
|
28
|
+
this.options = options;
|
|
29
|
+
}
|
|
30
|
+
// ---------------------------------------------------------------------
|
|
31
|
+
// Connection lifecycle
|
|
32
|
+
// ---------------------------------------------------------------------
|
|
33
|
+
async connect() {
|
|
34
|
+
if (this.connected && this.store) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
if (this.options.client) {
|
|
39
|
+
this.store = this.options.client;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
43
|
+
const mod = require('ravendb');
|
|
44
|
+
const DocumentStoreCtor = mod.DocumentStore;
|
|
45
|
+
this.store = new DocumentStoreCtor(this.options.urls, this.options.database);
|
|
46
|
+
}
|
|
47
|
+
if (typeof this.store.initialize === 'function') {
|
|
48
|
+
this.store.initialize();
|
|
49
|
+
}
|
|
50
|
+
this.connected = true;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
this.store = null;
|
|
54
|
+
this.connected = false;
|
|
55
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
56
|
+
throw new errors_1.ConnectionError(`Unable to connect to RavenDB: ${parent.message}`, {
|
|
57
|
+
parent,
|
|
58
|
+
database: 'ravendb',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async disconnect() {
|
|
63
|
+
if (this.store && typeof this.store.dispose === 'function') {
|
|
64
|
+
try {
|
|
65
|
+
await this.store.dispose();
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
throw wrapDatabaseError(err, 'RavenDB disconnect failed');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this.store = null;
|
|
72
|
+
this.connected = false;
|
|
73
|
+
}
|
|
74
|
+
isConnected() {
|
|
75
|
+
return this.connected && this.store !== null;
|
|
76
|
+
}
|
|
77
|
+
getClient() {
|
|
78
|
+
return this.requireStore();
|
|
79
|
+
}
|
|
80
|
+
requireStore() {
|
|
81
|
+
if (!this.store || !this.connected) {
|
|
82
|
+
throw new errors_1.ConnectionError('Not connected to RavenDB. Call connect() first.', {
|
|
83
|
+
database: 'ravendb',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return this.store;
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------
|
|
89
|
+
// Document CRUD (unit-of-work sessions)
|
|
90
|
+
// ---------------------------------------------------------------------
|
|
91
|
+
/** Store a document in `collection`; returns the id used. */
|
|
92
|
+
async insert(collection, doc) {
|
|
93
|
+
const store = this.requireStore();
|
|
94
|
+
try {
|
|
95
|
+
const id = (doc.id ?? `${collection}/${genId()}`);
|
|
96
|
+
const session = store.openSession();
|
|
97
|
+
await session.store(doc, id);
|
|
98
|
+
await session.saveChanges();
|
|
99
|
+
return id;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
throw wrapDatabaseError(err, 'RavenDB insert failed');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Load a document by id. */
|
|
106
|
+
async get(_collection, id) {
|
|
107
|
+
const store = this.requireStore();
|
|
108
|
+
try {
|
|
109
|
+
const session = store.openSession();
|
|
110
|
+
return await session.load(id);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
throw wrapDatabaseError(err, 'RavenDB get failed');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Load, merge `patch`, and save; returns the updated entity. */
|
|
117
|
+
async update(_collection, id, patch) {
|
|
118
|
+
const store = this.requireStore();
|
|
119
|
+
try {
|
|
120
|
+
const session = store.openSession();
|
|
121
|
+
const entity = await session.load(id);
|
|
122
|
+
const merged = Object.assign(entity ?? {}, patch);
|
|
123
|
+
await session.saveChanges();
|
|
124
|
+
return merged;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
throw wrapDatabaseError(err, 'RavenDB update failed');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Delete a document by id. */
|
|
131
|
+
async delete(_collection, id) {
|
|
132
|
+
const store = this.requireStore();
|
|
133
|
+
try {
|
|
134
|
+
const session = store.openSession();
|
|
135
|
+
await session.delete(id);
|
|
136
|
+
await session.saveChanges();
|
|
137
|
+
return { id };
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
throw wrapDatabaseError(err, 'RavenDB delete failed');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Query `collection`. `filter` is a plain object whose entries become
|
|
145
|
+
* `whereEquals(field, value)` conditions on the session query. Returns all
|
|
146
|
+
* matched documents.
|
|
147
|
+
*/
|
|
148
|
+
async query(collection, filter = {}) {
|
|
149
|
+
const store = this.requireStore();
|
|
150
|
+
try {
|
|
151
|
+
const session = store.openSession();
|
|
152
|
+
let q = session.query({ collection });
|
|
153
|
+
for (const [field, value] of Object.entries(filter)) {
|
|
154
|
+
q = q.whereEquals(field, value);
|
|
155
|
+
}
|
|
156
|
+
const results = await q.all();
|
|
157
|
+
return results ?? [];
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
throw wrapDatabaseError(err, 'RavenDB query failed');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.RavenDbStore = RavenDbStore;
|
|
165
|
+
function genId() {
|
|
166
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
167
|
+
}
|
|
168
|
+
function wrapDatabaseError(err, message) {
|
|
169
|
+
if (err instanceof errors_1.ConnectionError) {
|
|
170
|
+
return err;
|
|
171
|
+
}
|
|
172
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
173
|
+
return errors_1.DatabaseError.from(parent, { message: `${message}: ${parent.message}` });
|
|
174
|
+
}
|
|
175
|
+
exports.default = RavenDbStore;
|