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,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Google Cloud Firestore store implementation for the Prorm NoSQL layer.
|
|
4
|
+
*
|
|
5
|
+
* Firestore is a document database organized as collections of documents,
|
|
6
|
+
* each addressed by an id, with rich structured querying. It does not fit the
|
|
7
|
+
* SQL-shaped `Dialect` interface, so this class implements the minimal
|
|
8
|
+
* `NoSqlStore` marker interface for connection lifecycle and exposes
|
|
9
|
+
* Firestore's real `collection().doc()` CRUD plus `where()` query API through
|
|
10
|
+
* a small convenience layer.
|
|
11
|
+
*
|
|
12
|
+
* Uses the official `@google-cloud/firestore` driver. That driver is an
|
|
13
|
+
* optional peer dependency and is loaded lazily inside `connect()` so
|
|
14
|
+
* importing this module never requires it to be installed. Tests inject a
|
|
15
|
+
* pre-built mock `client` via `options.client`, which bypasses the lazy
|
|
16
|
+
* `require` entirely - no real driver and no network are needed.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.FirestoreStore = void 0;
|
|
20
|
+
const errors_1 = require("../../errors");
|
|
21
|
+
class FirestoreStore {
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.name = 'firestore';
|
|
24
|
+
this.library = '@google-cloud/firestore';
|
|
25
|
+
this.client = null;
|
|
26
|
+
this.connected = false;
|
|
27
|
+
this.options = options;
|
|
28
|
+
}
|
|
29
|
+
// ---------------------------------------------------------------------
|
|
30
|
+
// Connection lifecycle
|
|
31
|
+
// ---------------------------------------------------------------------
|
|
32
|
+
async connect() {
|
|
33
|
+
if (this.connected && this.client) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
if (this.options.client) {
|
|
38
|
+
this.client = this.options.client;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
42
|
+
const mod = require('@google-cloud/firestore');
|
|
43
|
+
const FirestoreCtor = mod.Firestore ?? mod;
|
|
44
|
+
this.client = new FirestoreCtor({
|
|
45
|
+
projectId: this.options.projectId,
|
|
46
|
+
keyFilename: this.options.keyFilename,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
this.connected = true;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
this.client = null;
|
|
53
|
+
this.connected = false;
|
|
54
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
55
|
+
throw new errors_1.ConnectionError(`Unable to connect to Firestore: ${parent.message}`, {
|
|
56
|
+
parent,
|
|
57
|
+
database: 'firestore',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async disconnect() {
|
|
62
|
+
if (this.client && typeof this.client.terminate === 'function') {
|
|
63
|
+
try {
|
|
64
|
+
await this.client.terminate();
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
throw wrapDatabaseError(err, 'Firestore disconnect failed');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.client = null;
|
|
71
|
+
this.connected = false;
|
|
72
|
+
}
|
|
73
|
+
isConnected() {
|
|
74
|
+
return this.connected && this.client !== null;
|
|
75
|
+
}
|
|
76
|
+
getClient() {
|
|
77
|
+
return this.requireClient();
|
|
78
|
+
}
|
|
79
|
+
requireClient() {
|
|
80
|
+
if (!this.client || !this.connected) {
|
|
81
|
+
throw new errors_1.ConnectionError('Not connected to Firestore. Call connect() first.', {
|
|
82
|
+
database: 'firestore',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return this.client;
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------
|
|
88
|
+
// Document CRUD
|
|
89
|
+
// ---------------------------------------------------------------------
|
|
90
|
+
/**
|
|
91
|
+
* Add a document to `collection`. If `doc.id` is set the document is written
|
|
92
|
+
* to that id via `doc(id).set(doc)`; otherwise Firestore auto-assigns one
|
|
93
|
+
* via `collection.add(doc)`. Returns the resulting document id.
|
|
94
|
+
*/
|
|
95
|
+
async insert(collection, doc) {
|
|
96
|
+
const client = this.requireClient();
|
|
97
|
+
try {
|
|
98
|
+
const explicitId = doc.id;
|
|
99
|
+
if (explicitId !== undefined && explicitId !== null) {
|
|
100
|
+
await client.collection(collection).doc(String(explicitId)).set(doc);
|
|
101
|
+
return String(explicitId);
|
|
102
|
+
}
|
|
103
|
+
const ref = await client.collection(collection).add(doc);
|
|
104
|
+
return ref?.id;
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
throw wrapDatabaseError(err, 'Firestore insert failed');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Fetch a document by id, returning its data (or `undefined` if missing). */
|
|
111
|
+
async get(collection, id) {
|
|
112
|
+
const client = this.requireClient();
|
|
113
|
+
try {
|
|
114
|
+
const snap = await client.collection(collection).doc(id).get();
|
|
115
|
+
return snap?.exists ? snap.data() : undefined;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
throw wrapDatabaseError(err, 'Firestore get failed');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Partially update a document by id, returning the applied `patch`. */
|
|
122
|
+
async update(collection, id, patch) {
|
|
123
|
+
const client = this.requireClient();
|
|
124
|
+
try {
|
|
125
|
+
await client.collection(collection).doc(id).update(patch);
|
|
126
|
+
return patch;
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
throw wrapDatabaseError(err, 'Firestore update failed');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** Delete a document by id. */
|
|
133
|
+
async delete(collection, id) {
|
|
134
|
+
const client = this.requireClient();
|
|
135
|
+
try {
|
|
136
|
+
return await client.collection(collection).doc(id).delete();
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
throw wrapDatabaseError(err, 'Firestore delete failed');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Query `collection`. `filter` may be an array of `[field, op, value]`
|
|
144
|
+
* conditions or a plain object (treated as equality on each key). Returns
|
|
145
|
+
* the matched documents, each shaped as `{ id, ...data }`.
|
|
146
|
+
*/
|
|
147
|
+
async query(collection, filter = {}) {
|
|
148
|
+
const client = this.requireClient();
|
|
149
|
+
try {
|
|
150
|
+
let ref = client.collection(collection);
|
|
151
|
+
for (const [field, op, value] of normalizeConditions(filter)) {
|
|
152
|
+
ref = ref.where(field, op, value);
|
|
153
|
+
}
|
|
154
|
+
const snapshot = await ref.get();
|
|
155
|
+
const docs = snapshot?.docs ?? [];
|
|
156
|
+
return docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
throw wrapDatabaseError(err, 'Firestore query failed');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.FirestoreStore = FirestoreStore;
|
|
164
|
+
function normalizeConditions(filter) {
|
|
165
|
+
if (Array.isArray(filter)) {
|
|
166
|
+
return filter;
|
|
167
|
+
}
|
|
168
|
+
return Object.entries(filter).map(([field, value]) => [field, '==', value]);
|
|
169
|
+
}
|
|
170
|
+
function wrapDatabaseError(err, message) {
|
|
171
|
+
if (err instanceof errors_1.ConnectionError) {
|
|
172
|
+
return err;
|
|
173
|
+
}
|
|
174
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
175
|
+
return errors_1.DatabaseError.from(parent, { message: `${message}: ${parent.message}` });
|
|
176
|
+
}
|
|
177
|
+
exports.default = FirestoreStore;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Fluree store implementation.
|
|
4
|
+
*
|
|
5
|
+
* Fluree is an immutable, time-travel ledger database: every transaction is
|
|
6
|
+
* appended as a set of cryptographically-hashed "flakes" into a blockchain-
|
|
7
|
+
* like log, so no data is ever overwritten and any past state (by block or
|
|
8
|
+
* time) is queryable. It speaks a JSON query/transaction API (FlureeQL) over
|
|
9
|
+
* HTTP, not SQL.
|
|
10
|
+
*
|
|
11
|
+
* ## No canonical driver - HTTP over `fetch`
|
|
12
|
+
*
|
|
13
|
+
* Fluree has no single canonical npm client; it is an HTTP/JSON API. Like the
|
|
14
|
+
* Prometheus store, this class talks to the server with the global `fetch`
|
|
15
|
+
* (so `library === 'fetch'`) and accepts an injected HTTP client via
|
|
16
|
+
* `FlureeStoreOptions.client` for tests / custom transports. It implements the
|
|
17
|
+
* minimal `NoSqlStore` marker interface plus the shared ledger surface:
|
|
18
|
+
* - `append(collection, data)` -> `POST /fdb/<ledger>/transact`.
|
|
19
|
+
* - `get(collection, id)` -> `POST /fdb/<ledger>/query`.
|
|
20
|
+
* - `history(collection, id)` -> `POST /fdb/<ledger>/history`.
|
|
21
|
+
* - `verify(collection, id)` -> `POST /fdb/<ledger>/history` with block
|
|
22
|
+
* metadata (the flake's block hash is its tamper-evidence proof).
|
|
23
|
+
* - `query(flureeql)` -> raw `POST /fdb/<ledger>/query`.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.FlureeStore = exports.DEFAULT_FLUREE_BASE_URL = void 0;
|
|
27
|
+
exports.createFlureeFetchClient = createFlureeFetchClient;
|
|
28
|
+
const errors_1 = require("../../errors");
|
|
29
|
+
/** Default Fluree HTTP API base URL. */
|
|
30
|
+
exports.DEFAULT_FLUREE_BASE_URL = 'http://localhost:8090';
|
|
31
|
+
/** Build a `fetch`-backed JSON client for the given base URL. */
|
|
32
|
+
function createFlureeFetchClient(baseURL, headers) {
|
|
33
|
+
const root = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
|
|
34
|
+
return {
|
|
35
|
+
async post(path, body) {
|
|
36
|
+
const res = await fetch(`${root}${path}`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json', ...(headers ?? {}) },
|
|
39
|
+
body: JSON.stringify(body),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
43
|
+
}
|
|
44
|
+
return res.json();
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* `FlureeStore` wraps Fluree's HTTP query/transact API behind the shared
|
|
50
|
+
* ledger surface.
|
|
51
|
+
*/
|
|
52
|
+
class FlureeStore {
|
|
53
|
+
constructor(options) {
|
|
54
|
+
this.name = 'fluree';
|
|
55
|
+
this.library = 'fetch';
|
|
56
|
+
this.client = null;
|
|
57
|
+
this.connected = false;
|
|
58
|
+
if (!options || !options.ledger) {
|
|
59
|
+
throw new errors_1.DatabaseError('FlureeStore requires a `ledger` name', {
|
|
60
|
+
code: 'INVALID_CONNECTION',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
this.baseURL = options.baseURL ?? exports.DEFAULT_FLUREE_BASE_URL;
|
|
64
|
+
this.ledger = options.ledger;
|
|
65
|
+
this.injectedClient = options.client;
|
|
66
|
+
this.headers = options.headers;
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------
|
|
69
|
+
// Connection lifecycle
|
|
70
|
+
// ---------------------------------------------------------------------
|
|
71
|
+
async connect() {
|
|
72
|
+
if (this.connected && this.client) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.client = this.injectedClient ?? createFlureeFetchClient(this.baseURL, this.headers);
|
|
76
|
+
this.connected = true;
|
|
77
|
+
}
|
|
78
|
+
async disconnect() {
|
|
79
|
+
this.client = null;
|
|
80
|
+
this.connected = false;
|
|
81
|
+
}
|
|
82
|
+
isConnected() {
|
|
83
|
+
return this.connected && this.client !== null;
|
|
84
|
+
}
|
|
85
|
+
/** Returns the underlying (internal or injected) HTTP client. */
|
|
86
|
+
getClient() {
|
|
87
|
+
return this.requireClient();
|
|
88
|
+
}
|
|
89
|
+
requireClient() {
|
|
90
|
+
if (!this.client) {
|
|
91
|
+
throw new errors_1.ConnectionError('Not connected to Fluree', {
|
|
92
|
+
database: 'fluree',
|
|
93
|
+
host: this.baseURL,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return this.client;
|
|
97
|
+
}
|
|
98
|
+
endpoint(op) {
|
|
99
|
+
return `/fdb/${this.ledger}/${op}`;
|
|
100
|
+
}
|
|
101
|
+
async post(op, body, action) {
|
|
102
|
+
const client = this.requireClient();
|
|
103
|
+
try {
|
|
104
|
+
return await client.post(this.endpoint(op), body);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
108
|
+
throw errors_1.DatabaseError.from(parent, { message: `Fluree ${action} failed: ${parent.message}` });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------
|
|
112
|
+
// Ledger surface
|
|
113
|
+
// ---------------------------------------------------------------------
|
|
114
|
+
/**
|
|
115
|
+
* Append `data` to `collection` as a transaction (`POST .../transact`).
|
|
116
|
+
* Returns the transaction response (block, flakes, temp-id map, and the
|
|
117
|
+
* block hash that anchors the write into the immutable log).
|
|
118
|
+
*/
|
|
119
|
+
async append(collection, data) {
|
|
120
|
+
return this.post('transact', [{ _id: collection, ...data }], 'append');
|
|
121
|
+
}
|
|
122
|
+
/** Read subject `id` from `collection` (`POST .../query`). */
|
|
123
|
+
async get(collection, id) {
|
|
124
|
+
return this.post('query', { select: ['*'], from: [[`${collection}/id`, id]] }, 'get');
|
|
125
|
+
}
|
|
126
|
+
/** Return the flake-level history of subject `id` (`POST .../history`). */
|
|
127
|
+
async history(collection, id) {
|
|
128
|
+
return this.post('history', { history: [[`${collection}/id`, id]] }, 'history');
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Return the provenance/proof for subject `id`: its history with block
|
|
132
|
+
* metadata (`meta: true`), so callers see the block hash and instant that
|
|
133
|
+
* cryptographically anchor each revision into Fluree's tamper-evident log.
|
|
134
|
+
*/
|
|
135
|
+
async verify(collection, id) {
|
|
136
|
+
return this.post('history', { history: [[`${collection}/id`, id]], meta: true }, 'verify');
|
|
137
|
+
}
|
|
138
|
+
/** Run a raw FlureeQL query object (`POST .../query`). */
|
|
139
|
+
async query(flureeql) {
|
|
140
|
+
return this.post('query', flureeql, 'query');
|
|
141
|
+
}
|
|
142
|
+
/** Run a raw FlureeQL transaction (`POST .../transact`). */
|
|
143
|
+
async transact(transaction) {
|
|
144
|
+
return this.post('transact', transaction, 'transact');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.FlureeStore = FlureeStore;
|
|
148
|
+
exports.default = FlureeStore;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Apache Jena Fuseki store implementation.
|
|
4
|
+
*
|
|
5
|
+
* Fuseki is a SPARQL server over the Jena RDF triplestore. It is queried and
|
|
6
|
+
* updated entirely over HTTP using the SPARQL 1.1 Protocol: a dataset exposes
|
|
7
|
+
* a query endpoint (`/<dataset>/query`) and an update endpoint
|
|
8
|
+
* (`/<dataset>/update`). There is no SQL-shaped surface (no rows, no DDL, no
|
|
9
|
+
* identifier escaping), so it implements the minimal `NoSqlStore` marker
|
|
10
|
+
* interface (`src/nosql/store.ts`) rather than the SQL `Dialect` interface.
|
|
11
|
+
*
|
|
12
|
+
* ## No canonical driver - HTTP over `fetch`
|
|
13
|
+
*
|
|
14
|
+
* Fuseki has no dedicated npm client; it is just a SPARQL/HTTP endpoint.
|
|
15
|
+
* Rather than depend on any package, this store talks to the server with the
|
|
16
|
+
* global `fetch`. For tests (and for callers who want their own transport) an
|
|
17
|
+
* alternative client can be injected via `FusekiStoreOptions.client`; when
|
|
18
|
+
* omitted, a tiny internal client over `fetch` + `endpoint` is built at
|
|
19
|
+
* connect time.
|
|
20
|
+
*
|
|
21
|
+
* ## Surface
|
|
22
|
+
*
|
|
23
|
+
* - `query(sparql)` -> POST to `/<dataset>/query`, returns result bindings
|
|
24
|
+
* - `update(sparql)` -> POST to `/<dataset>/update`
|
|
25
|
+
* - `ask(sparql)` -> POST an ASK query, returns the boolean
|
|
26
|
+
* - `add(triples)` -> `INSERT DATA { ... }` update
|
|
27
|
+
* - `drop(graph?)` -> `DROP GRAPH <g>` / `DROP ALL` update
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.FusekiStore = exports.DEFAULT_FUSEKI_DATASET = exports.DEFAULT_FUSEKI_ENDPOINT = void 0;
|
|
31
|
+
exports.createFetchClient = createFetchClient;
|
|
32
|
+
const errors_1 = require("../../errors");
|
|
33
|
+
/** Default Fuseki base URL. */
|
|
34
|
+
exports.DEFAULT_FUSEKI_ENDPOINT = 'http://localhost:3030';
|
|
35
|
+
/** Default dataset name. */
|
|
36
|
+
exports.DEFAULT_FUSEKI_DATASET = 'ds';
|
|
37
|
+
/** Build a `fetch`-backed SPARQL client for the given endpoint. */
|
|
38
|
+
function createFetchClient(endpoint, headers) {
|
|
39
|
+
const base = endpoint.endsWith('/') ? endpoint : `${endpoint}/`;
|
|
40
|
+
const buildUrl = (path) => new URL(path.replace(/^\//, ''), base).toString();
|
|
41
|
+
return {
|
|
42
|
+
async query(path, sparql) {
|
|
43
|
+
const res = await fetch(buildUrl(path), {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/sparql-query',
|
|
47
|
+
Accept: 'application/sparql-results+json',
|
|
48
|
+
...(headers ?? {}),
|
|
49
|
+
},
|
|
50
|
+
body: sparql,
|
|
51
|
+
});
|
|
52
|
+
if (!res.ok)
|
|
53
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
54
|
+
return res.json();
|
|
55
|
+
},
|
|
56
|
+
async update(path, sparql) {
|
|
57
|
+
const res = await fetch(buildUrl(path), {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/sparql-update', ...(headers ?? {}) },
|
|
60
|
+
body: sparql,
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok)
|
|
63
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
64
|
+
const text = await res.text();
|
|
65
|
+
return text || undefined;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `FusekiStore` wraps a Fuseki dataset's SPARQL query/update endpoints behind
|
|
71
|
+
* the `NoSqlStore` lifecycle.
|
|
72
|
+
*/
|
|
73
|
+
class FusekiStore {
|
|
74
|
+
constructor(options = {}) {
|
|
75
|
+
this.name = 'fuseki';
|
|
76
|
+
this.library = 'fetch';
|
|
77
|
+
this.client = null;
|
|
78
|
+
this.connected = false;
|
|
79
|
+
this.endpoint = options.endpoint ?? exports.DEFAULT_FUSEKI_ENDPOINT;
|
|
80
|
+
this.dataset = options.dataset ?? exports.DEFAULT_FUSEKI_DATASET;
|
|
81
|
+
this.injectedClient = options.client;
|
|
82
|
+
this.headers = options.headers;
|
|
83
|
+
}
|
|
84
|
+
/** SPARQL query endpoint path for the configured dataset. */
|
|
85
|
+
get queryPath() {
|
|
86
|
+
return `/${this.dataset}/query`;
|
|
87
|
+
}
|
|
88
|
+
/** SPARQL update endpoint path for the configured dataset. */
|
|
89
|
+
get updatePath() {
|
|
90
|
+
return `/${this.dataset}/update`;
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------
|
|
93
|
+
// Connection lifecycle
|
|
94
|
+
// ---------------------------------------------------------------------
|
|
95
|
+
async connect() {
|
|
96
|
+
if (this.connected && this.client)
|
|
97
|
+
return;
|
|
98
|
+
this.client = this.injectedClient ?? createFetchClient(this.endpoint, this.headers);
|
|
99
|
+
this.connected = true;
|
|
100
|
+
}
|
|
101
|
+
async disconnect() {
|
|
102
|
+
this.client = null;
|
|
103
|
+
this.connected = false;
|
|
104
|
+
}
|
|
105
|
+
isConnected() {
|
|
106
|
+
return this.connected && this.client !== null;
|
|
107
|
+
}
|
|
108
|
+
/** Returns the underlying (internal or injected) SPARQL HTTP client. */
|
|
109
|
+
getClient() {
|
|
110
|
+
return this.requireClient();
|
|
111
|
+
}
|
|
112
|
+
requireClient() {
|
|
113
|
+
if (!this.client) {
|
|
114
|
+
throw new errors_1.ConnectionError('Not connected to Fuseki', {
|
|
115
|
+
database: 'fuseki',
|
|
116
|
+
host: this.endpoint,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return this.client;
|
|
120
|
+
}
|
|
121
|
+
// ---------------------------------------------------------------------
|
|
122
|
+
// SPARQL surface
|
|
123
|
+
// ---------------------------------------------------------------------
|
|
124
|
+
/** Runs a SPARQL SELECT/CONSTRUCT query; returns the result bindings. */
|
|
125
|
+
async query(sparql) {
|
|
126
|
+
const client = this.requireClient();
|
|
127
|
+
try {
|
|
128
|
+
const body = await client.query(this.queryPath, sparql);
|
|
129
|
+
return body?.results?.bindings ?? [];
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
133
|
+
throw errors_1.DatabaseError.from(parent, { message: `Fuseki query failed: ${parent.message}`, sql: sparql });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Runs a SPARQL ASK query; returns the boolean answer. */
|
|
137
|
+
async ask(sparql) {
|
|
138
|
+
const client = this.requireClient();
|
|
139
|
+
try {
|
|
140
|
+
const body = await client.query(this.queryPath, sparql);
|
|
141
|
+
return Boolean(body?.boolean);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
145
|
+
throw errors_1.DatabaseError.from(parent, { message: `Fuseki ask failed: ${parent.message}`, sql: sparql });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** Runs a SPARQL UPDATE (INSERT/DELETE/DROP/...). */
|
|
149
|
+
async update(sparql) {
|
|
150
|
+
const client = this.requireClient();
|
|
151
|
+
try {
|
|
152
|
+
return await client.update(this.updatePath, sparql);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
156
|
+
throw errors_1.DatabaseError.from(parent, { message: `Fuseki update failed: ${parent.message}`, sql: sparql });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Inserts triples via `INSERT DATA { ... }`. Accepts a triple string or array. */
|
|
160
|
+
async add(triples, graph) {
|
|
161
|
+
const body = Array.isArray(triples) ? triples.join('\n') : triples;
|
|
162
|
+
const inner = graph ? `GRAPH <${graph}> { ${body} }` : body;
|
|
163
|
+
return this.update(`INSERT DATA { ${inner} }`);
|
|
164
|
+
}
|
|
165
|
+
/** Drops a named graph, or all graphs when no graph is given. */
|
|
166
|
+
async drop(graph) {
|
|
167
|
+
return this.update(graph ? `DROP GRAPH <${graph}>` : 'DROP ALL');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.FusekiStore = FusekiStore;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GCSStore = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Google Cloud Storage (GCS) store.
|
|
6
|
+
*
|
|
7
|
+
* GCS is an object store, not a SQL database - it has no query language,
|
|
8
|
+
* no joins, and no ad hoc WHERE-clause filtering. Data lives as immutable
|
|
9
|
+
* objects (blobs) addressed by `(bucket, key)`, so it does not fit the
|
|
10
|
+
* SQL-shaped `Dialect` interface (`src/dialects/dialect.ts`). This store
|
|
11
|
+
* instead implements the minimal `NoSqlStore` marker interface
|
|
12
|
+
* (`src/nosql/store.ts`) and exposes GCS's own object-level operations
|
|
13
|
+
* (upload/download/delete/exists/list/metadata) directly.
|
|
14
|
+
*
|
|
15
|
+
* Built on the `@google-cloud/storage` driver. That package is loaded
|
|
16
|
+
* lazily inside `connect()` (via `require`) rather than imported at module
|
|
17
|
+
* top level, so importing this file never pulls in the (optional) SDK.
|
|
18
|
+
* Tests inject a pre-built `Storage`-shaped mock via the `client` option,
|
|
19
|
+
* which skips the `require` entirely.
|
|
20
|
+
*/
|
|
21
|
+
const errors_1 = require("../../errors");
|
|
22
|
+
/**
|
|
23
|
+
* Google Cloud Storage store, implementing the minimal `NoSqlStore` marker
|
|
24
|
+
* interface. Unlike the SQL dialects there is no `query(sql)` - GCS's
|
|
25
|
+
* operations are object-shaped (`bucket(name).file(key).<op>()`) and that
|
|
26
|
+
* shape is preserved here rather than forced into a SQL string.
|
|
27
|
+
*/
|
|
28
|
+
class GCSStore {
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.name = 'gcs';
|
|
31
|
+
this.library = '@google-cloud/storage';
|
|
32
|
+
this.client = null;
|
|
33
|
+
this.connected = false;
|
|
34
|
+
this.options = options;
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------------
|
|
37
|
+
// NoSqlStore lifecycle
|
|
38
|
+
// ---------------------------------------------------------------------
|
|
39
|
+
async connect() {
|
|
40
|
+
if (this.connected && this.client) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
if (this.options.client) {
|
|
45
|
+
// A pre-built Storage instance was injected - use it as-is and skip
|
|
46
|
+
// loading the (optional) SDK entirely.
|
|
47
|
+
this.client = this.options.client;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Lazy-load the driver so importing this module never requires the
|
|
51
|
+
// optional `@google-cloud/storage` package to be installed.
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
53
|
+
const gcs = require('@google-cloud/storage');
|
|
54
|
+
this.client = new gcs.Storage({
|
|
55
|
+
projectId: this.options.projectId,
|
|
56
|
+
keyFilename: this.options.keyFilename,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
this.connected = true;
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
this.client = null;
|
|
63
|
+
this.connected = false;
|
|
64
|
+
throw new errors_1.ConnectionError(`Failed to connect to Google Cloud Storage: ${err instanceof Error ? err.message : String(err)}`, { parent: err instanceof Error ? err : undefined, database: 'gcs' });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async disconnect() {
|
|
68
|
+
// The GCS client is stateless (HTTP-based) with nothing to close, so
|
|
69
|
+
// disconnecting just drops the reference.
|
|
70
|
+
this.client = null;
|
|
71
|
+
this.connected = false;
|
|
72
|
+
}
|
|
73
|
+
isConnected() {
|
|
74
|
+
return this.connected && this.client !== null;
|
|
75
|
+
}
|
|
76
|
+
/** Returns the underlying `@google-cloud/storage` `Storage` client for anything not wrapped here. */
|
|
77
|
+
getClient() {
|
|
78
|
+
return this.requireClient();
|
|
79
|
+
}
|
|
80
|
+
requireClient() {
|
|
81
|
+
if (!this.client || !this.connected) {
|
|
82
|
+
throw new errors_1.ConnectionError('Not connected to Google Cloud Storage. Call connect() first.', {
|
|
83
|
+
database: 'gcs',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return this.client;
|
|
87
|
+
}
|
|
88
|
+
/** Resolve a `File` handle for `(bucket, key)` off the live client. */
|
|
89
|
+
file(bucket, key) {
|
|
90
|
+
return this.requireClient().bucket(bucket).file(key);
|
|
91
|
+
}
|
|
92
|
+
wrapError(action, err) {
|
|
93
|
+
const parent = err instanceof Error ? err : new Error(String(err));
|
|
94
|
+
return errors_1.DatabaseError.from(parent, { message: `GCS ${action} failed: ${parent.message}` });
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------
|
|
97
|
+
// Object operations
|
|
98
|
+
// ---------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Upload (or overwrite) an object. `data` may be a `Buffer` or string;
|
|
101
|
+
* `options` is passed straight through to `file.save()` (e.g.
|
|
102
|
+
* `{ contentType, metadata, resumable }`).
|
|
103
|
+
*/
|
|
104
|
+
async uploadObject(bucket, key, data, options = {}) {
|
|
105
|
+
const file = this.file(bucket, key);
|
|
106
|
+
try {
|
|
107
|
+
await file.save(data, options);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
throw this.wrapError(`upload of '${key}' to '${bucket}'`, err);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Download an object's contents as a `Buffer`. Throws if the object does not exist. */
|
|
114
|
+
async downloadObject(bucket, key) {
|
|
115
|
+
const file = this.file(bucket, key);
|
|
116
|
+
try {
|
|
117
|
+
const [contents] = await file.download();
|
|
118
|
+
return contents;
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
throw this.wrapError(`download of '${key}' from '${bucket}'`, err);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Delete an object. Throws if the object does not exist. */
|
|
125
|
+
async deleteObject(bucket, key) {
|
|
126
|
+
const file = this.file(bucket, key);
|
|
127
|
+
try {
|
|
128
|
+
await file.delete();
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
throw this.wrapError(`delete of '${key}' from '${bucket}'`, err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Returns whether an object exists. */
|
|
135
|
+
async objectExists(bucket, key) {
|
|
136
|
+
const file = this.file(bucket, key);
|
|
137
|
+
try {
|
|
138
|
+
const [exists] = await file.exists();
|
|
139
|
+
return exists;
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
throw this.wrapError(`exists check of '${key}' in '${bucket}'`, err);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* List objects in a bucket, optionally restricted to those whose name
|
|
147
|
+
* starts with `prefix`. Returns the raw `File` objects from the driver.
|
|
148
|
+
*/
|
|
149
|
+
async listObjects(bucket, prefix) {
|
|
150
|
+
const handle = this.requireClient().bucket(bucket);
|
|
151
|
+
try {
|
|
152
|
+
const [files] = await handle.getFiles({ prefix });
|
|
153
|
+
return (files ?? []);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
throw this.wrapError(`list of '${bucket}'`, err);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Fetch an object's metadata (size, contentType, updated, custom metadata, etc). */
|
|
160
|
+
async getMetadata(bucket, key) {
|
|
161
|
+
const file = this.file(bucket, key);
|
|
162
|
+
try {
|
|
163
|
+
const [metadata] = await file.getMetadata();
|
|
164
|
+
return metadata;
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
throw this.wrapError(`getMetadata of '${key}' in '${bucket}'`, err);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.GCSStore = GCSStore;
|
|
172
|
+
exports.default = GCSStore;
|