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.
Files changed (310) hide show
  1. package/CHANGELOG.md +1111 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -0
  4. package/dist/audit/history-query.js +180 -0
  5. package/dist/audit/index.js +23 -0
  6. package/dist/audit/logger.js +236 -0
  7. package/dist/cache/cache-manager.js +84 -0
  8. package/dist/cache/index.js +16 -0
  9. package/dist/cache/redis-cluster-cache.js +557 -0
  10. package/dist/cli.js +2200 -0
  11. package/dist/compliance/audit-trail.js +68 -0
  12. package/dist/compliance/backup-verification.js +685 -0
  13. package/dist/compliance/breach-detector.js +505 -0
  14. package/dist/compliance/consent-record.js +227 -0
  15. package/dist/compliance/consent-versioning.js +331 -0
  16. package/dist/compliance/cross-border-log.js +530 -0
  17. package/dist/compliance/data-classifier.js +258 -0
  18. package/dist/compliance/data-lineage.js +565 -0
  19. package/dist/compliance/data-masker.js +303 -0
  20. package/dist/compliance/data-portability.js +265 -0
  21. package/dist/compliance/data-retention.js +195 -0
  22. package/dist/compliance/dsar-workflow.js +342 -0
  23. package/dist/compliance/field-encryption.js +223 -0
  24. package/dist/compliance/immutable-record.js +148 -0
  25. package/dist/compliance/index.js +260 -0
  26. package/dist/compliance/privacy-impact-assessment.js +364 -0
  27. package/dist/compliance/pseudonymization.js +269 -0
  28. package/dist/compliance/query-firewall.js +1182 -0
  29. package/dist/compliance/rate-limiter.js +580 -0
  30. package/dist/compliance/right-to-erasure.js +117 -0
  31. package/dist/compliance/row-level-security.js +246 -0
  32. package/dist/compliance/security-decorator.js +574 -0
  33. package/dist/compliance/security-monitor.js +525 -0
  34. package/dist/compliance/sensitive-data-discovery.js +476 -0
  35. package/dist/compliance/session-isolation.js +472 -0
  36. package/dist/compliance/tls-enforcer.js +108 -0
  37. package/dist/compliance/worm-storage.js +712 -0
  38. package/dist/connection-manager.js +198 -0
  39. package/dist/connection-pool.js +519 -0
  40. package/dist/decorators/audit.js +136 -0
  41. package/dist/decorators/belongs-to-many.js +115 -0
  42. package/dist/decorators/belongs-to.js +115 -0
  43. package/dist/decorators/check.js +435 -0
  44. package/dist/decorators/collate.js +329 -0
  45. package/dist/decorators/comment.js +205 -0
  46. package/dist/decorators/database-settings.js +236 -0
  47. package/dist/decorators/default.js +244 -0
  48. package/dist/decorators/encryption.js +235 -0
  49. package/dist/decorators/engine.js +97 -0
  50. package/dist/decorators/fk-constraints.js +594 -0
  51. package/dist/decorators/foreign-table.js +136 -0
  52. package/dist/decorators/generated.js +274 -0
  53. package/dist/decorators/has-many.js +127 -0
  54. package/dist/decorators/has-one.js +116 -0
  55. package/dist/decorators/hstore.js +129 -0
  56. package/dist/decorators/index.js +355 -0
  57. package/dist/decorators/json-column.js +83 -0
  58. package/dist/decorators/jsonb.js +101 -0
  59. package/dist/decorators/orm-decorators.js +425 -0
  60. package/dist/decorators/permissions.js +294 -0
  61. package/dist/decorators/procedure.js +210 -0
  62. package/dist/decorators/query-options.js +556 -0
  63. package/dist/decorators/range.js +167 -0
  64. package/dist/decorators/set-column.js +83 -0
  65. package/dist/decorators/spatial.js +114 -0
  66. package/dist/decorators/storage.js +580 -0
  67. package/dist/decorators/timezone.js +512 -0
  68. package/dist/decorators/trigger.js +90 -0
  69. package/dist/decorators/uuid.js +135 -0
  70. package/dist/decorators/view.js +258 -0
  71. package/dist/diagrams/chen-diagram.js +354 -0
  72. package/dist/diagrams/class-diagram.js +384 -0
  73. package/dist/diagrams/dependency-diagram.js +432 -0
  74. package/dist/diagrams/er-diagram.js +605 -0
  75. package/dist/diagrams/flow-diagram.js +394 -0
  76. package/dist/diagrams/gantt-diagram.js +411 -0
  77. package/dist/diagrams/index-diagram.js +353 -0
  78. package/dist/diagrams/index.js +184 -0
  79. package/dist/diagrams/migration-diagram.js +316 -0
  80. package/dist/diagrams/model-diagram.js +616 -0
  81. package/dist/diagrams/package-diagram.js +376 -0
  82. package/dist/diagrams/palette.js +85 -0
  83. package/dist/diagrams/relational-diagram.js +455 -0
  84. package/dist/diagrams/schemadoc-diagram.js +309 -0
  85. package/dist/diagrams/sequence-diagram.js +307 -0
  86. package/dist/diagrams/state-diagram.js +344 -0
  87. package/dist/diagrams/svg-dom.js +111 -0
  88. package/dist/diagrams/tree-diagram.js +250 -0
  89. package/dist/dialects/clickhouse/index.js +1742 -0
  90. package/dist/dialects/cockroachdb/index.js +4677 -0
  91. package/dist/dialects/cratedb/index.js +441 -0
  92. package/dist/dialects/databricks/index.js +517 -0
  93. package/dist/dialects/db2/index.js +2225 -0
  94. package/dist/dialects/dialect.js +720 -0
  95. package/dist/dialects/duckdb/index.js +2014 -0
  96. package/dist/dialects/exasol/index.js +357 -0
  97. package/dist/dialects/firebird/index.js +495 -0
  98. package/dist/dialects/greenplum/index.js +457 -0
  99. package/dist/dialects/hana/index.js +1741 -0
  100. package/dist/dialects/mariadb/index.js +3590 -0
  101. package/dist/dialects/mssql/index.js +2296 -0
  102. package/dist/dialects/mysql/index.js +4058 -0
  103. package/dist/dialects/oracle/index.js +2999 -0
  104. package/dist/dialects/postgres/index.js +5319 -0
  105. package/dist/dialects/query-stream-helper.js +152 -0
  106. package/dist/dialects/questdb/index.js +433 -0
  107. package/dist/dialects/redshift/index.js +2277 -0
  108. package/dist/dialects/singlestore/index.js +354 -0
  109. package/dist/dialects/snowflake/index.js +2082 -0
  110. package/dist/dialects/spanner/index.js +1768 -0
  111. package/dist/dialects/sqlite/index.js +3382 -0
  112. package/dist/dialects/tidb/index.js +377 -0
  113. package/dist/dialects/timescaledb/index.js +164 -0
  114. package/dist/dialects/trino/index.js +420 -0
  115. package/dist/dialects/turso/index.js +227 -0
  116. package/dist/dialects/vertica/index.js +317 -0
  117. package/dist/dialects/yugabytedb/index.js +424 -0
  118. package/dist/errors/index.js +472 -0
  119. package/dist/errors/utils.js +340 -0
  120. package/dist/errors.js +21 -0
  121. package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
  122. package/dist/extensions/catalog/mssql-features.js +147 -0
  123. package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
  124. package/dist/extensions/catalog/oracle-db2-features.js +196 -0
  125. package/dist/extensions/catalog/postgres-extensions.js +516 -0
  126. package/dist/extensions/index.js +71 -0
  127. package/dist/extensions/types.js +13 -0
  128. package/dist/foreign-data.js +173 -0
  129. package/dist/hooks/hooks-manager.js +350 -0
  130. package/dist/hooks/index.js +37 -0
  131. package/dist/index.js +463 -0
  132. package/dist/logging.js +335 -0
  133. package/dist/migrations/index.js +40 -0
  134. package/dist/migrations/migration.js +196 -0
  135. package/dist/migrations/migrator.js +411 -0
  136. package/dist/migrations/prormmigration.js +275 -0
  137. package/dist/migrations/query-interface.js +435 -0
  138. package/dist/migrations/seeder.js +353 -0
  139. package/dist/models/associations.js +852 -0
  140. package/dist/models/constraints.js +288 -0
  141. package/dist/models/data-types.js +2518 -0
  142. package/dist/models/decorators.js +445 -0
  143. package/dist/models/index.js +33 -0
  144. package/dist/models/indexes.js +531 -0
  145. package/dist/models/methods.js +382 -0
  146. package/dist/models/model-manager.js +103 -0
  147. package/dist/models/model.js +5349 -0
  148. package/dist/models/operators.js +67 -0
  149. package/dist/models/scopes.js +189 -0
  150. package/dist/models/typescript-types.js +26 -0
  151. package/dist/nosql/aerospike/index.js +205 -0
  152. package/dist/nosql/allegrograph/index.js +169 -0
  153. package/dist/nosql/arangodb/index.js +364 -0
  154. package/dist/nosql/azure-blob/index.js +206 -0
  155. package/dist/nosql/beanstalkd/index.js +231 -0
  156. package/dist/nosql/beequeue/index.js +210 -0
  157. package/dist/nosql/bigchaindb/index.js +195 -0
  158. package/dist/nosql/bigtable/index.js +224 -0
  159. package/dist/nosql/blazegraph/index.js +173 -0
  160. package/dist/nosql/bullmq/index.js +191 -0
  161. package/dist/nosql/cassandra/index.js +174 -0
  162. package/dist/nosql/chroma/index.js +190 -0
  163. package/dist/nosql/cloudflare-kv/index.js +220 -0
  164. package/dist/nosql/coherence/index.js +200 -0
  165. package/dist/nosql/cosmosdb/index.js +157 -0
  166. package/dist/nosql/couchbase/index.js +213 -0
  167. package/dist/nosql/dax/index.js +212 -0
  168. package/dist/nosql/deno-kv/index.js +206 -0
  169. package/dist/nosql/dgraph/index.js +171 -0
  170. package/dist/nosql/doris/index.js +169 -0
  171. package/dist/nosql/druid/index.js +162 -0
  172. package/dist/nosql/dynamodb/index.js +937 -0
  173. package/dist/nosql/elasticsearch/index.js +377 -0
  174. package/dist/nosql/etcd/index.js +502 -0
  175. package/dist/nosql/eventhubs/index.js +213 -0
  176. package/dist/nosql/eventstore/index.js +254 -0
  177. package/dist/nosql/faunadb/index.js +188 -0
  178. package/dist/nosql/firestore/index.js +177 -0
  179. package/dist/nosql/fluree/index.js +148 -0
  180. package/dist/nosql/fuseki/index.js +170 -0
  181. package/dist/nosql/gcs/index.js +172 -0
  182. package/dist/nosql/gearman/index.js +160 -0
  183. package/dist/nosql/geode/index.js +196 -0
  184. package/dist/nosql/graphdb/index.js +169 -0
  185. package/dist/nosql/graylog/index.js +188 -0
  186. package/dist/nosql/gridgain/index.js +171 -0
  187. package/dist/nosql/hazelcast/index.js +162 -0
  188. package/dist/nosql/hbase/index.js +230 -0
  189. package/dist/nosql/ignite/index.js +173 -0
  190. package/dist/nosql/immudb/index.js +184 -0
  191. package/dist/nosql/index.js +232 -0
  192. package/dist/nosql/infinispan/index.js +200 -0
  193. package/dist/nosql/influxdb/index.js +0 -0
  194. package/dist/nosql/kafka/index.js +234 -0
  195. package/dist/nosql/keyspaces/index.js +189 -0
  196. package/dist/nosql/kinesis/index.js +253 -0
  197. package/dist/nosql/leveldb/index.js +153 -0
  198. package/dist/nosql/lmdb/index.js +160 -0
  199. package/dist/nosql/loki/index.js +201 -0
  200. package/dist/nosql/marklogic/index.js +204 -0
  201. package/dist/nosql/materialize/index.js +145 -0
  202. package/dist/nosql/meilisearch/index.js +154 -0
  203. package/dist/nosql/memcached/index.js +223 -0
  204. package/dist/nosql/milvus/index.js +410 -0
  205. package/dist/nosql/minio/index.js +265 -0
  206. package/dist/nosql/momento/index.js +179 -0
  207. package/dist/nosql/mongodb/index.js +461 -0
  208. package/dist/nosql/nats/index.js +247 -0
  209. package/dist/nosql/nedb/index.js +164 -0
  210. package/dist/nosql/neo4j/index.js +450 -0
  211. package/dist/nosql/neptune/index.js +470 -0
  212. package/dist/nosql/nsq/index.js +200 -0
  213. package/dist/nosql/opensearch/index.js +186 -0
  214. package/dist/nosql/orientdb/index.js +175 -0
  215. package/dist/nosql/papertrail/index.js +200 -0
  216. package/dist/nosql/pinecone/index.js +0 -0
  217. package/dist/nosql/pinot/index.js +133 -0
  218. package/dist/nosql/pouchdb/index.js +172 -0
  219. package/dist/nosql/prometheus/index.js +174 -0
  220. package/dist/nosql/provendb/index.js +147 -0
  221. package/dist/nosql/pubsub/index.js +187 -0
  222. package/dist/nosql/pulsar/index.js +232 -0
  223. package/dist/nosql/qdrant/index.js +295 -0
  224. package/dist/nosql/qldb/index.js +192 -0
  225. package/dist/nosql/r2/index.js +281 -0
  226. package/dist/nosql/rabbitmq/index.js +237 -0
  227. package/dist/nosql/ravendb/index.js +175 -0
  228. package/dist/nosql/redis/index.js +607 -0
  229. package/dist/nosql/redpanda/index.js +237 -0
  230. package/dist/nosql/resque/index.js +203 -0
  231. package/dist/nosql/rethinkdb/index.js +232 -0
  232. package/dist/nosql/rocksdb/index.js +152 -0
  233. package/dist/nosql/rockset/index.js +126 -0
  234. package/dist/nosql/s3/index.js +298 -0
  235. package/dist/nosql/scylladb/index.js +178 -0
  236. package/dist/nosql/signoz/index.js +227 -0
  237. package/dist/nosql/sns/index.js +201 -0
  238. package/dist/nosql/solr/index.js +231 -0
  239. package/dist/nosql/splunk/index.js +227 -0
  240. package/dist/nosql/sqs/index.js +246 -0
  241. package/dist/nosql/stardog/index.js +169 -0
  242. package/dist/nosql/starrocks/index.js +170 -0
  243. package/dist/nosql/store.js +2 -0
  244. package/dist/nosql/sumologic/index.js +213 -0
  245. package/dist/nosql/surrealdb/index.js +179 -0
  246. package/dist/nosql/terminusdb/index.js +151 -0
  247. package/dist/nosql/tigergraph/index.js +469 -0
  248. package/dist/nosql/typesense/index.js +148 -0
  249. package/dist/nosql/unqlite/index.js +157 -0
  250. package/dist/nosql/upstash/index.js +167 -0
  251. package/dist/nosql/vercel-kv/index.js +166 -0
  252. package/dist/nosql/victoriametrics/index.js +213 -0
  253. package/dist/nosql/virtuoso/index.js +169 -0
  254. package/dist/nosql/weaviate/index.js +276 -0
  255. package/dist/operators/index.js +108 -0
  256. package/dist/operators.js +2690 -0
  257. package/dist/prisma-migrate/index.js +38 -0
  258. package/dist/prisma-migrate/migration-generator.js +125 -0
  259. package/dist/prisma-migrate/model-generator.js +172 -0
  260. package/dist/prisma-migrate/relations.js +100 -0
  261. package/dist/prisma-migrate/schema-parser.js +167 -0
  262. package/dist/prisma-migrate/type-mapper.js +40 -0
  263. package/dist/prorm.js +6832 -0
  264. package/dist/query-builders/cte-builder.js +80 -0
  265. package/dist/query-builders/functions/aggregate.js +390 -0
  266. package/dist/query-builders/functions/conditional.js +503 -0
  267. package/dist/query-builders/functions/datetime.js +695 -0
  268. package/dist/query-builders/functions/fulltext.js +439 -0
  269. package/dist/query-builders/functions/index.js +93 -0
  270. package/dist/query-builders/functions/json.js +427 -0
  271. package/dist/query-builders/functions/math.js +399 -0
  272. package/dist/query-builders/functions/string.js +518 -0
  273. package/dist/query-builders/functions/window.js +328 -0
  274. package/dist/query-builders/include-builder.js +323 -0
  275. package/dist/query-builders/index-expression-builder.js +242 -0
  276. package/dist/query-builders/index.js +161 -0
  277. package/dist/query-builders/insert-builder.js +164 -0
  278. package/dist/query-builders/model-helpers.js +38 -0
  279. package/dist/query-builders/order-limit-builder.js +239 -0
  280. package/dist/query-builders/sql-compiler.js +2040 -0
  281. package/dist/query-builders/subquery-builder.js +152 -0
  282. package/dist/query-builders/update-builder.js +182 -0
  283. package/dist/query-builders/view-builder.js +218 -0
  284. package/dist/query-builders/where-builder.js +1532 -0
  285. package/dist/query-interface.js +622 -0
  286. package/dist/query-optimizers/batch-optimizer.js +426 -0
  287. package/dist/query-optimizers/explain-plans.js +415 -0
  288. package/dist/query-optimizers/index.js +51 -0
  289. package/dist/query-optimizers/prepared-statement-cache.js +423 -0
  290. package/dist/query-optimizers/query-hints.js +438 -0
  291. package/dist/query-optimizers/query-optimizer.js +278 -0
  292. package/dist/query-optimizers/slow-query-logger.js +271 -0
  293. package/dist/replica-manager.js +507 -0
  294. package/dist/schema/index.js +15 -0
  295. package/dist/schema/migration-generator.js +374 -0
  296. package/dist/schema/schema-differ.js +549 -0
  297. package/dist/schema/types.js +6 -0
  298. package/dist/sql-constants.js +300 -0
  299. package/dist/sqlite-advanced.js +1047 -0
  300. package/dist/streams/index.js +12 -0
  301. package/dist/streams/transforms.js +178 -0
  302. package/dist/transaction.js +456 -0
  303. package/dist/types/index.js +180 -0
  304. package/dist/user-management.js +127 -0
  305. package/dist/utils/date.js +300 -0
  306. package/dist/utils/index.js +1079 -0
  307. package/dist/utils/string.js +161 -0
  308. package/dist/validators/index.js +19 -0
  309. package/dist/validators/validator.js +911 -0
  310. package/package.json +190 -0
@@ -0,0 +1,502 @@
1
+ "use strict";
2
+ /**
3
+ * etcd store implementation.
4
+ *
5
+ * etcd is a distributed, strongly-consistent key-value store used for
6
+ * service discovery, distributed configuration and coordination primitives
7
+ * (leader election, distributed locking). It has no query language, no
8
+ * schema, and no `JOIN`/transaction-log concept in the SQL sense, so it does
9
+ * not fit the SQL-shaped `Dialect` interface used by the SQL dialects in
10
+ * this repo. Instead it implements the minimal `NoSqlStore` marker
11
+ * interface (`src/nosql/store.ts`) and exposes etcd's real primitives -
12
+ * key/value get/put/delete (including prefix range operations), watch
13
+ * (etcd's signature "subscribe to key/prefix changes" feature), leases
14
+ * (TTL-based key expiry with keep-alive), distributed locking, and
15
+ * compare-and-swap transactions - as a typed, promise-based API instead of
16
+ * forcing everything into a `query(sql)` shape.
17
+ *
18
+ * Uses the official `etcd3` driver.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.EtcdStore = void 0;
22
+ const etcd3_1 = require("etcd3");
23
+ const errors_1 = require("../../errors");
24
+ function wrapConnectionError(err, hosts) {
25
+ const parent = err instanceof Error ? err : new Error(String(err));
26
+ const host = Array.isArray(hosts) ? hosts.join(',') : hosts;
27
+ const message = /unavailable|econnrefused/i.test(parent.message)
28
+ ? 'Connection refused'
29
+ : `Unable to connect to etcd: ${parent.message}`;
30
+ return new errors_1.ConnectionError(message, { parent, database: 'etcd', host });
31
+ }
32
+ function wrapDatabaseError(err, message) {
33
+ const parent = err instanceof Error ? err : new Error(String(err));
34
+ // Keep the `action` context (e.g. "etcd PUT failed") but don't discard the
35
+ // real underlying etcd error text, the same way the Redis store does.
36
+ const combined = message ? `${message}: ${parent.message}` : parent.message;
37
+ return errors_1.DatabaseError.from(parent, { message: combined });
38
+ }
39
+ function kvToRecord(kvs) {
40
+ return { ...kvs };
41
+ }
42
+ /**
43
+ * `EtcdStore` wraps an `etcd3` client and exposes etcd's real primitives:
44
+ * key/value get/put/delete (single-key and prefix-range), watch, leases,
45
+ * distributed locking, and compare-and-swap transactions.
46
+ */
47
+ class EtcdStore {
48
+ constructor(options) {
49
+ this.name = 'etcd';
50
+ this.library = 'etcd3';
51
+ this.client = null;
52
+ this.connected = false;
53
+ /** Leases granted through `grantLease()`, keyed by lease ID, so later calls can look them back up. */
54
+ this.leases = new Map();
55
+ this.options = options;
56
+ }
57
+ // ---------------------------------------------------------------------
58
+ // Connection lifecycle
59
+ // ---------------------------------------------------------------------
60
+ toClientOptions() {
61
+ // Spread (rather than pick known fields one-by-one) so any additional
62
+ // etcd3 `IOptions` fields a caller passes through (e.g. `grpcOptions`,
63
+ // `faultHandling`) reach the underlying client too.
64
+ return { ...this.options };
65
+ }
66
+ async connect() {
67
+ if (this.connected && this.client) {
68
+ return;
69
+ }
70
+ try {
71
+ const client = new etcd3_1.Etcd3(this.toClientOptions());
72
+ // etcd3's constructor doesn't itself dial out; issue a cheap call to
73
+ // surface connectivity problems (bad hosts, refused connections,
74
+ // auth failures) immediately, the same way RedisStore eagerly
75
+ // connects instead of waiting for the first real command.
76
+ await client.get('__prorm_connect_probe__').string();
77
+ this.client = client;
78
+ this.connected = true;
79
+ }
80
+ catch (err) {
81
+ this.client?.close();
82
+ this.client = null;
83
+ this.connected = false;
84
+ throw wrapConnectionError(err, this.options.hosts);
85
+ }
86
+ }
87
+ async disconnect() {
88
+ for (const lease of this.leases.values()) {
89
+ lease.release();
90
+ }
91
+ this.leases.clear();
92
+ if (this.client) {
93
+ this.client.close();
94
+ this.client = null;
95
+ }
96
+ this.connected = false;
97
+ }
98
+ isConnected() {
99
+ return this.connected && this.client !== null;
100
+ }
101
+ /** Returns the underlying `etcd3` client for anything not wrapped here. */
102
+ getClient() {
103
+ return this.requireClient();
104
+ }
105
+ requireClient() {
106
+ if (!this.client) {
107
+ throw new errors_1.ConnectionError('Not connected to etcd', { database: 'etcd' });
108
+ }
109
+ return this.client;
110
+ }
111
+ async exec(fn, action) {
112
+ const client = this.requireClient();
113
+ try {
114
+ return await fn(client);
115
+ }
116
+ catch (err) {
117
+ throw wrapDatabaseError(err, `etcd ${action} failed`);
118
+ }
119
+ }
120
+ // ---------------------------------------------------------------------
121
+ // JSON helpers (opt-in convenience on top of the raw string API)
122
+ // ---------------------------------------------------------------------
123
+ async putJSON(key, value, options = {}) {
124
+ return this.put(key, JSON.stringify(value), options);
125
+ }
126
+ async getJSON(key) {
127
+ const raw = await this.get(key);
128
+ if (raw === null) {
129
+ return null;
130
+ }
131
+ try {
132
+ return JSON.parse(raw);
133
+ }
134
+ catch (err) {
135
+ throw wrapDatabaseError(err, `etcd GETJSON failed (invalid JSON for key "${key}")`);
136
+ }
137
+ }
138
+ // ---------------------------------------------------------------------
139
+ // Single-key operations
140
+ // ---------------------------------------------------------------------
141
+ async get(key) {
142
+ return this.exec((c) => c.get(key).string(), 'GET');
143
+ }
144
+ async getBuffer(key) {
145
+ return this.exec((c) => c.get(key).buffer(), 'GET');
146
+ }
147
+ async put(key, value, options = {}) {
148
+ await this.exec(async (c) => {
149
+ const builder = c.put(key).value(value);
150
+ if (options.lease !== undefined) {
151
+ builder.lease(options.lease);
152
+ }
153
+ await builder.exec();
154
+ }, 'PUT');
155
+ }
156
+ /** Deletes a single key. Returns the number of keys actually deleted (`0` or `1`). */
157
+ async delete(key) {
158
+ return this.exec(async (c) => {
159
+ const res = await c.delete().key(key).exec();
160
+ return Number(res.deleted);
161
+ }, 'DELETE');
162
+ }
163
+ async exists(key) {
164
+ return this.exec((c) => c.get(key).exists(), 'EXISTS');
165
+ }
166
+ // ---------------------------------------------------------------------
167
+ // Prefix / range operations
168
+ // ---------------------------------------------------------------------
169
+ /** All key/value pairs whose key starts with `prefix`. */
170
+ async getPrefix(prefix) {
171
+ return this.exec(async (c) => kvToRecord(await c.getAll().prefix(prefix).strings()), 'GETPREFIX');
172
+ }
173
+ /** All keys (without values) starting with `prefix`. */
174
+ async getKeysWithPrefix(prefix) {
175
+ return this.exec((c) => c.getAll().prefix(prefix).keys(), 'GETPREFIX KEYS');
176
+ }
177
+ /** Number of keys starting with `prefix`, without transferring their values. */
178
+ async countPrefix(prefix) {
179
+ return this.exec((c) => c.getAll().prefix(prefix).count(), 'COUNTPREFIX');
180
+ }
181
+ /** Deletes every key starting with `prefix`. Returns the number of keys deleted. */
182
+ async deletePrefix(prefix) {
183
+ return this.exec(async (c) => {
184
+ const res = await c.delete().prefix(prefix).exec();
185
+ return Number(res.deleted);
186
+ }, 'DELETEPREFIX');
187
+ }
188
+ // ---------------------------------------------------------------------
189
+ // Watch (etcd's signature feature: subscribe to key/prefix changes)
190
+ // ---------------------------------------------------------------------
191
+ attachWatchHandler(watcher, handler) {
192
+ watcher.on('put', (kv, previous) => {
193
+ handler({
194
+ type: 'put',
195
+ key: kv.key.toString(),
196
+ value: kv.value.toString(),
197
+ previousValue: previous ? previous.value.toString() : null,
198
+ });
199
+ });
200
+ watcher.on('delete', (kv, previous) => {
201
+ handler({
202
+ type: 'delete',
203
+ key: kv.key.toString(),
204
+ value: null,
205
+ previousValue: previous ? previous.value.toString() : null,
206
+ });
207
+ });
208
+ }
209
+ /**
210
+ * Watch a single key for changes, invoking `handler` for every `put`/
211
+ * `delete` on that key. Returns a handle whose `cancel()` stops the
212
+ * underlying watch stream.
213
+ */
214
+ async watch(key, handler) {
215
+ return this.exec(async (c) => {
216
+ const watcher = await c.watch().key(key).withPreviousKV().create();
217
+ this.attachWatchHandler(watcher, handler);
218
+ return { watcher, cancel: () => watcher.cancel() };
219
+ }, 'WATCH');
220
+ }
221
+ /**
222
+ * Watch every key starting with `prefix` for changes, invoking `handler`
223
+ * for every `put`/`delete` on any matching key. Returns a handle whose
224
+ * `cancel()` stops the underlying watch stream.
225
+ */
226
+ async watchPrefix(prefix, handler) {
227
+ return this.exec(async (c) => {
228
+ const watcher = await c.watch().prefix(prefix).withPreviousKV().create();
229
+ this.attachWatchHandler(watcher, handler);
230
+ return { watcher, cancel: () => watcher.cancel() };
231
+ }, 'WATCH');
232
+ }
233
+ // ---------------------------------------------------------------------
234
+ // Leases (TTL-based key expiry + keep-alive)
235
+ // ---------------------------------------------------------------------
236
+ /**
237
+ * Grants a new lease with the given TTL (in seconds). By default the
238
+ * lease is kept alive automatically in the background (`autoKeepAlive`);
239
+ * pass `{ autoKeepAlive: false }` to manage keep-alives manually via
240
+ * `keepAliveOnce()`. Returns the lease ID, which can be passed to
241
+ * `put(key, value, { lease })` to associate keys with the lease so they
242
+ * expire automatically when it does.
243
+ */
244
+ async grantLease(ttlSeconds, options = {}) {
245
+ return this.exec(async (c) => {
246
+ const leaseOptions = { autoKeepAlive: options.autoKeepAlive ?? true };
247
+ const lease = c.lease(ttlSeconds, leaseOptions);
248
+ const id = await lease.grant();
249
+ this.leases.set(id, lease);
250
+ return id;
251
+ }, 'LEASE GRANT');
252
+ }
253
+ /** Puts `key`/`value` under a previously granted lease, so it expires when the lease does. */
254
+ async putWithLease(key, value, leaseId) {
255
+ const lease = this.requireLease(leaseId);
256
+ try {
257
+ await lease.put(key).value(value).exec();
258
+ }
259
+ catch (err) {
260
+ throw wrapDatabaseError(err, 'etcd PUT (with lease) failed');
261
+ }
262
+ }
263
+ /** Fires a single, immediate keep-alive for a lease, resetting its TTL countdown. */
264
+ async keepAliveOnce(leaseId) {
265
+ const lease = this.requireLease(leaseId);
266
+ try {
267
+ await lease.keepaliveOnce();
268
+ }
269
+ catch (err) {
270
+ throw wrapDatabaseError(err, 'etcd LEASE KEEPALIVE failed');
271
+ }
272
+ }
273
+ /** Revokes a lease immediately, evicting every key still attached to it. */
274
+ async revokeLease(leaseId) {
275
+ const lease = this.requireLease(leaseId);
276
+ try {
277
+ await lease.revoke();
278
+ }
279
+ finally {
280
+ this.leases.delete(leaseId);
281
+ }
282
+ }
283
+ /**
284
+ * Stops sending keep-alives for a lease, letting it expire naturally when
285
+ * its TTL elapses, instead of revoking it immediately. Use `revokeLease()`
286
+ * to evict its keys right away.
287
+ */
288
+ releaseLease(leaseId) {
289
+ const lease = this.leases.get(leaseId);
290
+ lease?.release();
291
+ this.leases.delete(leaseId);
292
+ }
293
+ /**
294
+ * Registers a handler that fires when etcd indicates a lease has been
295
+ * lost (TTL expired without a successful keep-alive, or it was revoked
296
+ * server-side). Not fired when `revokeLease()`/`releaseLease()` is called
297
+ * locally and succeeds normally.
298
+ */
299
+ onLeaseLost(leaseId, handler) {
300
+ const lease = this.requireLease(leaseId);
301
+ lease.on('lost', handler);
302
+ }
303
+ requireLease(leaseId) {
304
+ const lease = this.leases.get(leaseId);
305
+ if (!lease) {
306
+ throw new errors_1.DatabaseError(`Unknown etcd lease "${leaseId}" (not granted through this store instance)`);
307
+ }
308
+ return lease;
309
+ }
310
+ // ---------------------------------------------------------------------
311
+ // Distributed locking
312
+ // ---------------------------------------------------------------------
313
+ /**
314
+ * Acquires a distributed lock on `key`, blocking (queueing behind the
315
+ * current holder, in acquisition order) until it's free rather than
316
+ * rejecting immediately. Under the hood this is a lease on the key
317
+ * that's revoked on `release()`, or timed out by etcd if the holder
318
+ * dies - `ttlSeconds` controls that lease's TTL (etcd3 defaults to 30s).
319
+ *
320
+ * Note: `etcd3`'s own `Lock#acquire()` is a single compare-and-swap
321
+ * attempt - it *rejects immediately* with `EtcdLockFailedError` if the
322
+ * key already exists, rather than waiting, despite this store's docs
323
+ * (and the in-repo test mock) describing blocking/queueing semantics.
324
+ * To actually deliver that documented contract against a real etcd
325
+ * server, `acquireLockOrThrow()` below retries: on
326
+ * `EtcdLockFailedError` it watches the key until it's deleted (i.e. the
327
+ * current holder released or its lease expired), then retries the CAS.
328
+ * Multiple waiters can wake and race the retry simultaneously; that's
329
+ * fine because the retry is itself a CAS - at most one of them wins
330
+ * each round, and the rest go back to waiting.
331
+ */
332
+ async acquireLock(key, ttlSeconds) {
333
+ return this.exec(async (c) => {
334
+ const lock = await this.acquireLockOrThrow(c, key, ttlSeconds);
335
+ return this.toLockHandle(lock);
336
+ }, 'LOCK');
337
+ }
338
+ /**
339
+ * Wraps an acquired `etcd3` `Lock` in an `EtcdLockHandle`. `etcd3`'s own
340
+ * `Lock#leaseId()` keeps returning the (now-revoked) lease ID even after
341
+ * `release()` - verified against a real etcd server - which contradicts
342
+ * this handle's documented `null`-after-release contract, so track
343
+ * "released" locally instead of trusting the underlying lock for that.
344
+ */
345
+ toLockHandle(lock) {
346
+ let released = false;
347
+ return {
348
+ leaseId: async () => (released ? null : lock.leaseId()),
349
+ release: async () => {
350
+ await lock.release();
351
+ released = true;
352
+ },
353
+ };
354
+ }
355
+ /**
356
+ * Acquires a distributed lock on `key`, runs `fn`, and releases the lock
357
+ * once `fn`'s result settles (whether it resolves or rejects). This is
358
+ * the recommended way to use locks - it can't leak a held lock the way
359
+ * manual `acquireLock()`/`release()` pairing can if `fn` throws.
360
+ */
361
+ async withLock(key, fn, ttlSeconds) {
362
+ const client = this.requireClient();
363
+ let lock;
364
+ try {
365
+ lock = await this.acquireLockOrThrow(client, key, ttlSeconds);
366
+ }
367
+ catch (err) {
368
+ throw wrapDatabaseError(err, 'etcd LOCK failed');
369
+ }
370
+ try {
371
+ const value = await fn();
372
+ await lock.release();
373
+ return value;
374
+ }
375
+ catch (err) {
376
+ await lock.release().catch(() => undefined);
377
+ throw err;
378
+ }
379
+ }
380
+ /**
381
+ * Repeatedly attempts `client.lock(key).acquire()`, waiting for the key
382
+ * to be deleted (via a watch) and retrying whenever it fails with
383
+ * `EtcdLockFailedError`, so the returned promise doesn't settle until
384
+ * the lock is actually acquired (see `acquireLock()` above for why this
385
+ * is necessary against a real etcd server).
386
+ */
387
+ async acquireLockOrThrow(client, key, ttlSeconds) {
388
+ for (;;) {
389
+ const lock = client.lock(key);
390
+ if (ttlSeconds !== undefined) {
391
+ lock.ttl(ttlSeconds);
392
+ }
393
+ try {
394
+ await lock.acquire();
395
+ return lock;
396
+ }
397
+ catch (err) {
398
+ if (!(err instanceof etcd3_1.EtcdLockFailedError)) {
399
+ throw err;
400
+ }
401
+ await this.waitForKeyDeletion(client, key);
402
+ }
403
+ }
404
+ }
405
+ /** Resolves once `key` no longer exists (already gone, or deleted while we watch it). */
406
+ async waitForKeyDeletion(client, key) {
407
+ const stillThere = await client.get(key).exists();
408
+ if (!stillThere) {
409
+ return;
410
+ }
411
+ const watcher = await client.watch().key(key).create();
412
+ try {
413
+ await new Promise((resolve) => {
414
+ let settled = false;
415
+ const finish = () => {
416
+ if (settled) {
417
+ return;
418
+ }
419
+ settled = true;
420
+ resolve();
421
+ };
422
+ watcher.on('delete', finish);
423
+ // The key may have been deleted between the exists() check above
424
+ // and the watcher attaching; re-check to avoid waiting forever.
425
+ client
426
+ .get(key)
427
+ .exists()
428
+ .then((exists) => {
429
+ if (!exists) {
430
+ finish();
431
+ }
432
+ })
433
+ .catch(() => finish());
434
+ });
435
+ }
436
+ finally {
437
+ await watcher.cancel().catch(() => undefined);
438
+ }
439
+ }
440
+ // ---------------------------------------------------------------------
441
+ // Transactions (compare-and-swap via etcd3's if()/then()/else())
442
+ // ---------------------------------------------------------------------
443
+ /**
444
+ * Starts a raw etcd transaction: `if (key.<column> <cmp> value) { ... }`.
445
+ * Returns etcd3's `ComparatorBuilder` directly so callers get its full
446
+ * `.and()/.then()/.else()/.commit()` chain; combine with `putOp()`/
447
+ * `deleteOp()`/`getOp()` below to build the `then`/`else` clauses.
448
+ *
449
+ * ```typescript
450
+ * const result = await store
451
+ * .ifCompare('config-version', 'Value', '==', '1')
452
+ * .then(store.putOp('config-version', '2'))
453
+ * .else(store.getOp('config-version'))
454
+ * .commit();
455
+ * ```
456
+ */
457
+ ifCompare(key, column, cmp, value) {
458
+ return this.requireClient().if(key, column, cmp, value);
459
+ }
460
+ /** A `put` operation builder, for use as a `.then()`/`.else()` clause in `ifCompare()`. */
461
+ putOp(key, value) {
462
+ return this.requireClient().put(key).value(value);
463
+ }
464
+ /** A `delete` operation builder, for use as a `.then()`/`.else()` clause in `ifCompare()`. */
465
+ deleteOp(key) {
466
+ return this.requireClient().delete().key(key);
467
+ }
468
+ /** A `get` operation builder, for use as a `.then()`/`.else()` clause in `ifCompare()`. */
469
+ getOp(key) {
470
+ return this.requireClient().get(key);
471
+ }
472
+ /**
473
+ * Convenience compare-and-swap: atomically sets `key` to `newValue` only
474
+ * if its current value equals `expectedValue` (or, if `expectedValue` is
475
+ * `null`, only if the key does not currently exist). Returns whether the
476
+ * swap happened.
477
+ */
478
+ async compareAndSwap(key, expectedValue, newValue) {
479
+ return this.exec(async (c) => {
480
+ const comparator = expectedValue === null
481
+ ? c.if(key, 'Create', '==', 0)
482
+ : c.if(key, 'Value', '==', expectedValue);
483
+ const result = await comparator.then(c.put(key).value(newValue)).commit();
484
+ return result.succeeded;
485
+ }, 'TXN');
486
+ }
487
+ /**
488
+ * Atomically deletes `key` only if its current value equals
489
+ * `expectedValue`. Returns whether the delete happened.
490
+ */
491
+ async compareAndDelete(key, expectedValue) {
492
+ return this.exec(async (c) => {
493
+ const result = await c
494
+ .if(key, 'Value', '==', expectedValue)
495
+ .then(c.delete().key(key))
496
+ .commit();
497
+ return result.succeeded;
498
+ }, 'TXN');
499
+ }
500
+ }
501
+ exports.EtcdStore = EtcdStore;
502
+ exports.default = EtcdStore;