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,937 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DynamoDbStore = exports.DynamoDbError = exports.DynamoStreamViewType = exports.DynamoReturnValues = void 0;
4
+ /**
5
+ * Amazon DynamoDB store.
6
+ *
7
+ * DynamoDB is a partition-key-based key-value/document store, not a SQL
8
+ * database — it has no arbitrary WHERE-clause queries, no joins, and no
9
+ * ad hoc filtering without first reading data (`FilterExpression` runs
10
+ * *after* items are read/consumed against RCU, it does not reduce the
11
+ * amount of data scanned). Access patterns must be designed up front
12
+ * around the table's partition key (and optional sort key), plus any
13
+ * Global/Local Secondary Indexes. Because none of that fits the SQL-shaped
14
+ * `Dialect` interface (`src/dialects/dialect.ts`), this store instead
15
+ * implements the minimal `NoSqlStore` marker interface
16
+ * (`src/nosql/store.ts`) and exposes DynamoDB's own item-level operations
17
+ * directly.
18
+ *
19
+ * Built on AWS SDK v3: `DynamoDBClient` (`@aws-sdk/client-dynamodb`) is the
20
+ * low-level client that speaks the DynamoDB wire protocol in terms of
21
+ * `AttributeValue` maps (`{ S: 'foo' }`, `{ N: '1' }`, ...). This store
22
+ * wraps it in a `DynamoDBDocumentClient` (`@aws-sdk/lib-dynamodb`), which
23
+ * marshals/unmarshals plain JavaScript objects to/from that wire format
24
+ * automatically, so callers of this class work with ordinary objects.
25
+ */
26
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
27
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
28
+ const errors_1 = require("../../errors");
29
+ /**
30
+ * Companion enum for DynamoDB's `ReturnValues` parameter (PutItem, UpdateItem,
31
+ * DeleteItem). Provided for callers who prefer enum members over raw string
32
+ * literals; the underlying option fields still accept the original
33
+ * `'NONE' | 'ALL_OLD' | 'ALL_NEW' | 'UPDATED_OLD' | 'UPDATED_NEW'` string
34
+ * literals too; this is purely additive and does not narrow what's accepted.
35
+ */
36
+ var DynamoReturnValues;
37
+ (function (DynamoReturnValues) {
38
+ DynamoReturnValues["None"] = "NONE";
39
+ DynamoReturnValues["AllOld"] = "ALL_OLD";
40
+ DynamoReturnValues["AllNew"] = "ALL_NEW";
41
+ DynamoReturnValues["UpdatedOld"] = "UPDATED_OLD";
42
+ DynamoReturnValues["UpdatedNew"] = "UPDATED_NEW";
43
+ })(DynamoReturnValues || (exports.DynamoReturnValues = DynamoReturnValues = {}));
44
+ /**
45
+ * Companion enum for DynamoDB's `StreamViewType` (used by `enableStreams`).
46
+ * Provided for callers who prefer enum members over raw string literals; the
47
+ * `enableStreams` parameter still accepts the original
48
+ * `'NEW_IMAGE' | 'OLD_IMAGE' | 'NEW_AND_OLD_IMAGES' | 'KEYS_ONLY'` string
49
+ * literals too; this is purely additive and does not narrow what's accepted.
50
+ */
51
+ var DynamoStreamViewType;
52
+ (function (DynamoStreamViewType) {
53
+ DynamoStreamViewType["NewImage"] = "NEW_IMAGE";
54
+ DynamoStreamViewType["OldImage"] = "OLD_IMAGE";
55
+ DynamoStreamViewType["NewAndOldImages"] = "NEW_AND_OLD_IMAGES";
56
+ DynamoStreamViewType["KeysOnly"] = "KEYS_ONLY";
57
+ })(DynamoStreamViewType || (exports.DynamoStreamViewType = DynamoStreamViewType = {}));
58
+ const MAX_BATCH_GET_ITEMS = 100;
59
+ const MAX_BATCH_WRITE_ITEMS = 25;
60
+ const DEFAULT_MAX_ATTEMPTS = 5;
61
+ const DEFAULT_BASE_DELAY_MS = 50;
62
+ const DEFAULT_MAX_DELAY_MS = 2000;
63
+ /**
64
+ * `DatabaseError` subclass used for all DynamoDB failures wrapped by this
65
+ * store. Adds `cancellationReasons`, populated whenever the underlying AWS
66
+ * error is a `TransactionCanceledException` (thrown by
67
+ * `transactWriteItems()`/`transactGetItems()`), so callers can inspect which
68
+ * item(s) caused the transaction to be cancelled and why without digging
69
+ * into `.parent`/`.original`.
70
+ */
71
+ class DynamoDbError extends errors_1.DatabaseError {
72
+ constructor(message, options = {}) {
73
+ const { cancellationReasons, ...rest } = options;
74
+ super(message, rest);
75
+ this.cancellationReasons = cancellationReasons;
76
+ this.name = 'DynamoDbError';
77
+ }
78
+ }
79
+ exports.DynamoDbError = DynamoDbError;
80
+ /**
81
+ * Amazon DynamoDB store, implementing the minimal `NoSqlStore` marker
82
+ * interface. Unlike the SQL dialects, there is no `query(sql)` — DynamoDB's
83
+ * operations are item/key-shaped (Get/Put/Update/Delete/Query/Scan/Batch/Transact),
84
+ * and that shape is preserved here rather than forced into a SQL string.
85
+ */
86
+ class DynamoDbStore {
87
+ constructor(options = {}) {
88
+ this.name = 'dynamodb';
89
+ this.library = '@aws-sdk/client-dynamodb';
90
+ this.client = null;
91
+ this.docClient = null;
92
+ this.connected = false;
93
+ this.options = options;
94
+ }
95
+ // ---------------------------------------------------------------------
96
+ // NoSqlStore lifecycle
97
+ // ---------------------------------------------------------------------
98
+ async connect() {
99
+ try {
100
+ const config = {
101
+ ...this.options.clientConfig,
102
+ region: this.options.region ?? this.options.clientConfig?.region,
103
+ endpoint: this.options.endpoint ?? this.options.clientConfig?.endpoint,
104
+ credentials: this.options.credentials ?? this.options.clientConfig?.credentials,
105
+ };
106
+ this.client = new client_dynamodb_1.DynamoDBClient(config);
107
+ this.docClient = lib_dynamodb_1.DynamoDBDocumentClient.from(this.client, this.options.translateConfig);
108
+ this.connected = true;
109
+ }
110
+ catch (err) {
111
+ this.connected = false;
112
+ throw new errors_1.ConnectionError(`Failed to connect to DynamoDB: ${err instanceof Error ? err.message : String(err)}`, { parent: err instanceof Error ? err : undefined });
113
+ }
114
+ }
115
+ async disconnect() {
116
+ try {
117
+ this.docClient?.destroy();
118
+ this.client?.destroy();
119
+ }
120
+ finally {
121
+ this.client = null;
122
+ this.docClient = null;
123
+ this.connected = false;
124
+ }
125
+ }
126
+ isConnected() {
127
+ return this.connected;
128
+ }
129
+ /** Returns the `DynamoDBDocumentClient` (plain-object marshalling), not the raw `DynamoDBClient`. */
130
+ getClient() {
131
+ return this.requireClient();
132
+ }
133
+ /** Access to the low-level `DynamoDBClient` (raw `AttributeValue` shapes), for anything the document client doesn't cover. */
134
+ getRawClient() {
135
+ if (!this.client) {
136
+ throw new errors_1.ConnectionError('DynamoDB client is not connected. Call connect() first.');
137
+ }
138
+ return this.client;
139
+ }
140
+ requireClient() {
141
+ if (!this.docClient || !this.connected) {
142
+ throw new errors_1.ConnectionError('DynamoDB client is not connected. Call connect() first.');
143
+ }
144
+ return this.docClient;
145
+ }
146
+ /**
147
+ * Wraps an AWS SDK failure in a `DynamoDbError` (a `DatabaseError`
148
+ * subclass) while preserving the original error (incl. `.name` like
149
+ * `ConditionalCheckFailedException`) on `.parent`/`.original`. When the
150
+ * underlying error is a `TransactionCanceledException`, its
151
+ * `CancellationReasons` (per-item cancellation detail from
152
+ * `transactWriteItems()`/`transactGetItems()`) are also surfaced directly
153
+ * on `.cancellationReasons`, so callers don't have to dig into `.parent`
154
+ * to find out which item caused a transaction to be cancelled.
155
+ */
156
+ wrapError(action, err) {
157
+ const parent = err instanceof Error ? err : undefined;
158
+ const name = parent?.name ? ` (${parent.name})` : '';
159
+ const rawReasons = err?.CancellationReasons;
160
+ const cancellationReasons = Array.isArray(rawReasons)
161
+ ? rawReasons.map((r) => ({ code: r.Code, message: r.Message, item: r.Item }))
162
+ : undefined;
163
+ return new DynamoDbError(`DynamoDB ${action} failed${name}: ${parent?.message ?? String(err)}`, {
164
+ parent,
165
+ original: parent?.message,
166
+ cancellationReasons,
167
+ });
168
+ }
169
+ // ---------------------------------------------------------------------
170
+ // Single-item CRUD
171
+ // ---------------------------------------------------------------------
172
+ async getItem(tableName, key, options = {}) {
173
+ const client = this.requireClient();
174
+ const input = {
175
+ TableName: tableName,
176
+ Key: key,
177
+ ConsistentRead: options.consistentRead,
178
+ ProjectionExpression: options.projectionExpression,
179
+ };
180
+ try {
181
+ const result = await client.send(new lib_dynamodb_1.GetCommand(input));
182
+ return result.Item;
183
+ }
184
+ catch (err) {
185
+ throw this.wrapError(`GetItem on '${tableName}'`, err);
186
+ }
187
+ }
188
+ /**
189
+ * `options.returnValues` defaults to `'NONE'` (matching real PutItem
190
+ * default behavior). Pass `'ALL_OLD'` to get the item's previous value
191
+ * back (`undefined` if there wasn't one).
192
+ */
193
+ async putItem(tableName, item, options = {}) {
194
+ const client = this.requireClient();
195
+ const input = {
196
+ TableName: tableName,
197
+ Item: item,
198
+ ConditionExpression: options.conditionExpression,
199
+ ExpressionAttributeNames: options.expressionAttributeNames,
200
+ ExpressionAttributeValues: options.expressionAttributeValues,
201
+ ReturnValues: options.returnValues,
202
+ };
203
+ try {
204
+ const result = await client.send(new lib_dynamodb_1.PutCommand(input));
205
+ return result.Attributes;
206
+ }
207
+ catch (err) {
208
+ throw this.wrapError(`PutItem on '${tableName}'`, err);
209
+ }
210
+ }
211
+ /**
212
+ * Convenience update: SETs each key in `updates` to its value. Builds a
213
+ * simple `UpdateExpression`/`ExpressionAttributeValues` internally, using
214
+ * `#k0`, `#k1`, ... as attribute name placeholders (so reserved words like
215
+ * `status` are always safe) merged with any `options.expressionAttributeNames`.
216
+ * For REMOVE/ADD/DELETE or nested-path updates, use `updateItemRaw()`.
217
+ */
218
+ async updateItem(tableName, key, updates, options = {}) {
219
+ const entries = Object.entries(updates);
220
+ if (entries.length === 0) {
221
+ throw new errors_1.DatabaseError('updateItem() requires at least one attribute to update', {
222
+ original: 'empty updates object',
223
+ });
224
+ }
225
+ const names = { ...options.expressionAttributeNames };
226
+ const values = {};
227
+ const sets = [];
228
+ entries.forEach(([attr, value], i) => {
229
+ const nameKey = `#k${i}`;
230
+ const valueKey = `:v${i}`;
231
+ names[nameKey] = attr;
232
+ values[valueKey] = value;
233
+ sets.push(`${nameKey} = ${valueKey}`);
234
+ });
235
+ return this.updateItemRaw(tableName, {
236
+ TableName: tableName,
237
+ Key: key,
238
+ UpdateExpression: `SET ${sets.join(', ')}`,
239
+ ExpressionAttributeNames: names,
240
+ ExpressionAttributeValues: values,
241
+ ConditionExpression: options.conditionExpression,
242
+ ReturnValues: (options.returnValues ?? 'ALL_NEW'),
243
+ });
244
+ }
245
+ /** Escape hatch for update expressions beyond the flat-SET convenience wrapper above (REMOVE/ADD/DELETE, nested paths, etc). */
246
+ async updateItemRaw(tableName, input) {
247
+ const client = this.requireClient();
248
+ try {
249
+ const result = await client.send(new lib_dynamodb_1.UpdateCommand({ ...input, TableName: tableName }));
250
+ return result.Attributes;
251
+ }
252
+ catch (err) {
253
+ throw this.wrapError(`UpdateItem on '${tableName}'`, err);
254
+ }
255
+ }
256
+ /** `options.returnValues` defaults to `'ALL_OLD'` (returns the deleted item); pass `'NONE'` to skip returning it. */
257
+ async deleteItem(tableName, key, options = {}) {
258
+ const client = this.requireClient();
259
+ const input = {
260
+ TableName: tableName,
261
+ Key: key,
262
+ ConditionExpression: options.conditionExpression,
263
+ ExpressionAttributeNames: options.expressionAttributeNames,
264
+ ExpressionAttributeValues: options.expressionAttributeValues,
265
+ ReturnValues: (options.returnValues ?? 'ALL_OLD'),
266
+ };
267
+ try {
268
+ const result = await client.send(new lib_dynamodb_1.DeleteCommand(input));
269
+ return result.Attributes;
270
+ }
271
+ catch (err) {
272
+ throw this.wrapError(`DeleteItem on '${tableName}'`, err);
273
+ }
274
+ }
275
+ // ---------------------------------------------------------------------
276
+ // Query / Scan
277
+ // ---------------------------------------------------------------------
278
+ /**
279
+ * Query by partition key equality (plus an optional sort-key condition)
280
+ * against the base table or a named GSI/LSI. This is the primary way to
281
+ * read more than one item efficiently from DynamoDB — unlike Scan, it
282
+ * only reads the partition(s) that match, not the whole table.
283
+ */
284
+ async query(tableName, params) {
285
+ const client = this.requireClient();
286
+ try {
287
+ const result = await client.send(new lib_dynamodb_1.QueryCommand({
288
+ TableName: tableName,
289
+ IndexName: params.indexName,
290
+ KeyConditionExpression: params.keyConditionExpression,
291
+ ExpressionAttributeNames: params.expressionAttributeNames,
292
+ ExpressionAttributeValues: params.expressionAttributeValues,
293
+ FilterExpression: params.filterExpression,
294
+ ProjectionExpression: params.projectionExpression,
295
+ Limit: params.limit,
296
+ ScanIndexForward: params.scanIndexForward,
297
+ ExclusiveStartKey: params.exclusiveStartKey,
298
+ ConsistentRead: params.consistentRead,
299
+ }));
300
+ return {
301
+ items: (result.Items ?? []),
302
+ lastEvaluatedKey: result.LastEvaluatedKey,
303
+ count: result.Count ?? 0,
304
+ scannedCount: result.ScannedCount ?? 0,
305
+ };
306
+ }
307
+ catch (err) {
308
+ throw this.wrapError(`Query on '${tableName}'`, err);
309
+ }
310
+ }
311
+ /**
312
+ * Full table (or index) scan. This reads every item in the table — cost
313
+ * and latency scale with table size, not with how many items you actually
314
+ * want, and `filterExpression` is applied *after* the read (it does not
315
+ * reduce RCU consumption or the amount of data scanned). Prefer `query()`
316
+ * with a well-designed partition/sort key or GSI. Only reach for `scan()`
317
+ * for admin/maintenance/export tasks, small tables, or as a last resort
318
+ * when no query-friendly access pattern exists.
319
+ */
320
+ async scan(tableName, params = {}) {
321
+ const client = this.requireClient();
322
+ try {
323
+ const result = await client.send(new lib_dynamodb_1.ScanCommand({
324
+ TableName: tableName,
325
+ IndexName: params.indexName,
326
+ FilterExpression: params.filterExpression,
327
+ ProjectionExpression: params.projectionExpression,
328
+ ExpressionAttributeNames: params.expressionAttributeNames,
329
+ ExpressionAttributeValues: params.expressionAttributeValues,
330
+ Limit: params.limit,
331
+ ExclusiveStartKey: params.exclusiveStartKey,
332
+ ConsistentRead: params.consistentRead,
333
+ Segment: params.segment,
334
+ TotalSegments: params.totalSegments,
335
+ }));
336
+ return {
337
+ items: (result.Items ?? []),
338
+ lastEvaluatedKey: result.LastEvaluatedKey,
339
+ count: result.Count ?? 0,
340
+ scannedCount: result.ScannedCount ?? 0,
341
+ };
342
+ }
343
+ catch (err) {
344
+ throw this.wrapError(`Scan on '${tableName}'`, err);
345
+ }
346
+ }
347
+ /**
348
+ * Auto-paginating `query()`: DynamoDB caps each `Query` response at 1MB of
349
+ * data, so a query over more items than that fits in a single page and
350
+ * returns a `lastEvaluatedKey` that the caller must pass back in as
351
+ * `exclusiveStartKey` to fetch the next page. Forgetting to loop on that
352
+ * silently truncates results. `queryAll()` follows `lastEvaluatedKey`
353
+ * automatically and returns every matching item across all pages.
354
+ *
355
+ * `params.limit`, if set, still applies per-page (as DynamoDB defines
356
+ * it) — it does not cap the total number of items returned here. To
357
+ * consume pages one at a time instead of buffering everything in memory,
358
+ * use `queryPages()`.
359
+ */
360
+ async queryAll(tableName, params) {
361
+ const items = [];
362
+ for await (const page of this.queryPages(tableName, params)) {
363
+ items.push(...page);
364
+ }
365
+ return items;
366
+ }
367
+ /**
368
+ * Async-generator form of `queryAll()`: yields each page of items as it
369
+ * arrives instead of buffering the entire result set in memory, while
370
+ * still following `lastEvaluatedKey` automatically.
371
+ */
372
+ async *queryPages(tableName, params) {
373
+ let exclusiveStartKey = params.exclusiveStartKey;
374
+ do {
375
+ const result = await this.query(tableName, { ...params, exclusiveStartKey });
376
+ yield result.items;
377
+ exclusiveStartKey = result.lastEvaluatedKey;
378
+ } while (exclusiveStartKey !== undefined);
379
+ }
380
+ /**
381
+ * Auto-paginating `scan()` — see `queryAll()` above for why this matters
382
+ * (the same 1MB-per-page cap applies to Scan). Reminder: `scan()` is
383
+ * already O(table size) per the warning on `scan()` itself; `scanAll()`
384
+ * makes that even more explicit by reading literally the whole table (or
385
+ * index) into memory across as many pages as it takes.
386
+ */
387
+ async scanAll(tableName, params = {}) {
388
+ const items = [];
389
+ for await (const page of this.scanPages(tableName, params)) {
390
+ items.push(...page);
391
+ }
392
+ return items;
393
+ }
394
+ /** Async-generator form of `scanAll()`; yields each page as it arrives. */
395
+ async *scanPages(tableName, params = {}) {
396
+ let exclusiveStartKey = params.exclusiveStartKey;
397
+ do {
398
+ const result = await this.scan(tableName, { ...params, exclusiveStartKey });
399
+ yield result.items;
400
+ exclusiveStartKey = result.lastEvaluatedKey;
401
+ } while (exclusiveStartKey !== undefined);
402
+ }
403
+ // ---------------------------------------------------------------------
404
+ // Batch operations
405
+ // ---------------------------------------------------------------------
406
+ /** Sleeps for `ms` milliseconds. */
407
+ delay(ms) {
408
+ return new Promise((resolve) => setTimeout(resolve, ms));
409
+ }
410
+ /**
411
+ * Exponential backoff with full jitter (AWS's recommended strategy):
412
+ * a random delay between 0 and `min(maxDelayMs, baseDelayMs * 2^attempt)`.
413
+ * `attempt` is 0-based (the delay before the *first* retry, i.e. after
414
+ * the initial attempt has already failed/returned unprocessed items).
415
+ */
416
+ backoffDelayMs(attempt, baseDelayMs, maxDelayMs) {
417
+ const cap = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
418
+ return Math.random() * cap;
419
+ }
420
+ /**
421
+ * BatchGetItem across one or more tables. DynamoDB caps a single
422
+ * BatchGetItem call at 100 items total; this method chunks `keys`
423
+ * internally (25-at-a-time chunks well under the limit, sent
424
+ * sequentially) so callers can pass an arbitrarily large key list without
425
+ * worrying about the limit themselves. Also retries any
426
+ * `UnprocessedKeys` DynamoDB returns for a chunk (throttling) with
427
+ * exponential backoff and jitter between attempts, up to
428
+ * `options.maxAttempts` (default 5) attempts per chunk, before giving up
429
+ * and returning whatever's still unprocessed to the caller.
430
+ */
431
+ async batchGetItem(tableName, keys, options = {}) {
432
+ const client = this.requireClient();
433
+ const chunkSize = Math.min(options.chunkSize ?? MAX_BATCH_GET_ITEMS, MAX_BATCH_GET_ITEMS);
434
+ const maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
435
+ const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
436
+ const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
437
+ const items = [];
438
+ const unprocessedKeys = [];
439
+ for (let i = 0; i < keys.length; i += chunkSize) {
440
+ const chunk = keys.slice(i, i + chunkSize);
441
+ try {
442
+ let requestKeys = chunk;
443
+ let attempts = 0;
444
+ while (requestKeys.length > 0 && attempts < maxAttempts) {
445
+ if (attempts > 0) {
446
+ await this.delay(this.backoffDelayMs(attempts - 1, baseDelayMs, maxDelayMs));
447
+ }
448
+ const result = await client.send(new lib_dynamodb_1.BatchGetCommand({
449
+ RequestItems: {
450
+ [tableName]: { Keys: requestKeys, ConsistentRead: options.consistentRead },
451
+ },
452
+ }));
453
+ const fetched = (result.Responses?.[tableName] ?? []);
454
+ items.push(...fetched);
455
+ const unprocessed = result.UnprocessedKeys?.[tableName]?.Keys;
456
+ requestKeys = unprocessed ?? [];
457
+ attempts += 1;
458
+ }
459
+ if (requestKeys.length > 0) {
460
+ unprocessedKeys.push(...requestKeys);
461
+ }
462
+ }
463
+ catch (err) {
464
+ throw this.wrapError(`BatchGetItem on '${tableName}'`, err);
465
+ }
466
+ }
467
+ return { items, unprocessedKeys };
468
+ }
469
+ /**
470
+ * BatchWriteItem (put and/or delete) against a single table. DynamoDB
471
+ * caps a single BatchWriteItem call at 25 write requests; this method
472
+ * chunks `requests` internally so callers can pass an arbitrarily large
473
+ * batch. Also retries `UnprocessedItems` with exponential backoff and
474
+ * jitter, up to `options.maxAttempts` (default 5) attempts per chunk,
475
+ * before returning whatever's left to the caller.
476
+ */
477
+ async batchWriteItem(tableName, requests, options = {}) {
478
+ const client = this.requireClient();
479
+ const chunkSize = Math.min(options.chunkSize ?? MAX_BATCH_WRITE_ITEMS, MAX_BATCH_WRITE_ITEMS);
480
+ const maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
481
+ const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
482
+ const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
483
+ let unprocessedCount = 0;
484
+ const toWriteRequest = (r) => 'put' in r && r.put !== undefined
485
+ ? { PutRequest: { Item: r.put } }
486
+ : { DeleteRequest: { Key: r.delete } };
487
+ for (let i = 0; i < requests.length; i += chunkSize) {
488
+ const chunk = requests.slice(i, i + chunkSize).map(toWriteRequest);
489
+ try {
490
+ let pending = chunk;
491
+ let attempts = 0;
492
+ while (pending.length > 0 && attempts < maxAttempts) {
493
+ if (attempts > 0) {
494
+ await this.delay(this.backoffDelayMs(attempts - 1, baseDelayMs, maxDelayMs));
495
+ }
496
+ const result = await client.send(new lib_dynamodb_1.BatchWriteCommand({ RequestItems: { [tableName]: pending } }));
497
+ const unprocessed = result.UnprocessedItems?.[tableName];
498
+ pending = unprocessed ?? [];
499
+ attempts += 1;
500
+ }
501
+ unprocessedCount += pending.length;
502
+ }
503
+ catch (err) {
504
+ throw this.wrapError(`BatchWriteItem on '${tableName}'`, err);
505
+ }
506
+ }
507
+ return { unprocessedCount };
508
+ }
509
+ // ---------------------------------------------------------------------
510
+ // Transactions (multi-item ACID, up to 100 items across tables)
511
+ // ---------------------------------------------------------------------
512
+ /**
513
+ * Passthrough to DynamoDB's TransactWriteItems — up to 100 Put/Update/
514
+ * Delete/ConditionCheck actions, atomically, potentially across multiple
515
+ * tables. Not chunked: DynamoDB rejects the whole transaction if it
516
+ * exceeds the limit, so it's on the caller to stay under it (unlike the
517
+ * batch methods above, there's no safe way to silently split a
518
+ * transaction into several without breaking its atomicity guarantee).
519
+ */
520
+ async transactWriteItems(input) {
521
+ const client = this.requireClient();
522
+ try {
523
+ const { transactItems, ...rest } = input;
524
+ await client.send(new lib_dynamodb_1.TransactWriteCommand({ ...rest, TransactItems: transactItems }));
525
+ }
526
+ catch (err) {
527
+ throw this.wrapError('TransactWriteItems', err);
528
+ }
529
+ }
530
+ /** Passthrough to DynamoDB's TransactGetItems — up to 100 Get actions, read as a single consistent snapshot. */
531
+ async transactGetItems(input) {
532
+ const client = this.requireClient();
533
+ try {
534
+ const { transactItems, ...rest } = input;
535
+ const result = await client.send(new lib_dynamodb_1.TransactGetCommand({ ...rest, TransactItems: transactItems }));
536
+ return (result.Responses ?? []).map((r) => r.Item);
537
+ }
538
+ catch (err) {
539
+ throw this.wrapError('TransactGetItems', err);
540
+ }
541
+ }
542
+ /**
543
+ * Enables DynamoDB Streams on a table via `UpdateTableCommand`'s
544
+ * `StreamSpecification`, choosing what a stream record captures:
545
+ * `KEYS_ONLY` (just the key attributes of the modified item), `NEW_IMAGE`
546
+ * (the entire item as it appears after modification), `OLD_IMAGE` (as it
547
+ * appeared before), or `NEW_AND_OLD_IMAGES` (both). This only flips the
548
+ * table-level setting and returns the ARN DynamoDB assigns the new
549
+ * stream — it does not read from the stream itself; see "Known
550
+ * limitations" in the README for why a stream *consumer* is out of scope
551
+ * for this store.
552
+ */
553
+ async enableStreams(tableName, streamViewType) {
554
+ const client = this.getRawClient();
555
+ try {
556
+ const result = await client.send(new client_dynamodb_1.UpdateTableCommand({
557
+ TableName: tableName,
558
+ StreamSpecification: {
559
+ StreamEnabled: true,
560
+ StreamViewType: streamViewType,
561
+ },
562
+ }));
563
+ return result.TableDescription?.LatestStreamArn;
564
+ }
565
+ catch (err) {
566
+ throw this.wrapError(`UpdateTable (enableStreams) on '${tableName}'`, err);
567
+ }
568
+ }
569
+ /** Disables DynamoDB Streams on a table via `UpdateTableCommand`. */
570
+ async disableStreams(tableName) {
571
+ const client = this.getRawClient();
572
+ try {
573
+ await client.send(new client_dynamodb_1.UpdateTableCommand({
574
+ TableName: tableName,
575
+ StreamSpecification: { StreamEnabled: false },
576
+ }));
577
+ }
578
+ catch (err) {
579
+ throw this.wrapError(`UpdateTable (disableStreams) on '${tableName}'`, err);
580
+ }
581
+ }
582
+ /**
583
+ * Reads back the table's current stream ARN (`undefined` if streams
584
+ * aren't enabled) via `DescribeTableCommand`. Consuming the stream itself
585
+ * (shard iterators / record polling) requires the separate
586
+ * `@aws-sdk/client-dynamodb-streams` package or a Lambda event source
587
+ * mapping — out of scope here, see the README.
588
+ */
589
+ async getStreamArn(tableName) {
590
+ const client = this.getRawClient();
591
+ try {
592
+ const result = await client.send(new client_dynamodb_1.DescribeTableCommand({ TableName: tableName }));
593
+ return result.Table?.LatestStreamArn;
594
+ }
595
+ catch (err) {
596
+ throw this.wrapError(`DescribeTable (getStreamArn) on '${tableName}'`, err);
597
+ }
598
+ }
599
+ /**
600
+ * Enables TTL on a table via `UpdateTimeToLiveCommand`: DynamoDB
601
+ * periodically (usually within 48 hours, not immediately) deletes items
602
+ * whose `attributeName` holds a Unix epoch-seconds number in the past.
603
+ * A table may only have one TTL attribute at a time.
604
+ */
605
+ async enableTTL(tableName, attributeName) {
606
+ const client = this.getRawClient();
607
+ try {
608
+ await client.send(new client_dynamodb_1.UpdateTimeToLiveCommand({
609
+ TableName: tableName,
610
+ TimeToLiveSpecification: { Enabled: true, AttributeName: attributeName },
611
+ }));
612
+ }
613
+ catch (err) {
614
+ throw this.wrapError(`UpdateTimeToLive (enableTTL) on '${tableName}'`, err);
615
+ }
616
+ }
617
+ /**
618
+ * Disables TTL on a table. DynamoDB requires the currently-enabled
619
+ * `AttributeName` to be repeated on the disabling call, so this first
620
+ * reads it back via `describeTTL()` and is a no-op if TTL isn't
621
+ * currently enabled on the table.
622
+ */
623
+ async disableTTL(tableName) {
624
+ const client = this.getRawClient();
625
+ const current = await this.describeTTL(tableName);
626
+ const attributeName = current.attributeName;
627
+ if (!attributeName)
628
+ return;
629
+ try {
630
+ await client.send(new client_dynamodb_1.UpdateTimeToLiveCommand({
631
+ TableName: tableName,
632
+ TimeToLiveSpecification: { Enabled: false, AttributeName: attributeName },
633
+ }));
634
+ }
635
+ catch (err) {
636
+ throw this.wrapError(`UpdateTimeToLive (disableTTL) on '${tableName}'`, err);
637
+ }
638
+ }
639
+ /** Reads back a table's TTL configuration via `DescribeTimeToLiveCommand`. */
640
+ async describeTTL(tableName) {
641
+ const client = this.getRawClient();
642
+ try {
643
+ const result = await client.send(new client_dynamodb_1.DescribeTimeToLiveCommand({ TableName: tableName }));
644
+ return {
645
+ status: result.TimeToLiveDescription?.TimeToLiveStatus,
646
+ attributeName: result.TimeToLiveDescription?.AttributeName,
647
+ };
648
+ }
649
+ catch (err) {
650
+ throw this.wrapError(`DescribeTimeToLive on '${tableName}'`, err);
651
+ }
652
+ }
653
+ // ---------------------------------------------------------------------
654
+ // Table lifecycle
655
+ // ---------------------------------------------------------------------
656
+ /**
657
+ * Creates a table via `CreateTableCommand`. Thin passthrough to the raw
658
+ * SDK input shape: `params.TableName`, `params.KeySchema`, and
659
+ * `params.AttributeDefinitions` are required (as real CreateTable
660
+ * requires), plus either `params.BillingMode: 'PAY_PER_REQUEST'` or
661
+ * `params.ProvisionedThroughput` (real CreateTable requires one or the
662
+ * other), and optionally `params.GlobalSecondaryIndexes`/
663
+ * `params.LocalSecondaryIndexes`. Table creation is asynchronous on AWS's
664
+ * side (the table starts in `CREATING` status) — this call returns as
665
+ * soon as DynamoDB accepts the request, it does not wait for the table to
666
+ * become `ACTIVE`; poll `describeTable()` if you need to wait.
667
+ */
668
+ async createTable(params) {
669
+ const client = this.getRawClient();
670
+ try {
671
+ const result = await client.send(new client_dynamodb_1.CreateTableCommand(params));
672
+ return result.TableDescription;
673
+ }
674
+ catch (err) {
675
+ throw this.wrapError(`CreateTable on '${params.TableName}'`, err);
676
+ }
677
+ }
678
+ /**
679
+ * Deletes a table via `DeleteTableCommand`. Like `createTable()`, this is
680
+ * asynchronous on AWS's side (the table moves to `DELETING` status); this
681
+ * call returns as soon as DynamoDB accepts the request.
682
+ */
683
+ async deleteTable(tableName) {
684
+ const client = this.getRawClient();
685
+ try {
686
+ const result = await client.send(new client_dynamodb_1.DeleteTableCommand({ TableName: tableName }));
687
+ return result.TableDescription;
688
+ }
689
+ catch (err) {
690
+ throw this.wrapError(`DeleteTable on '${tableName}'`, err);
691
+ }
692
+ }
693
+ /**
694
+ * Lists table names in the current account/region via `ListTablesCommand`.
695
+ * A single call returns at most 100 table names (or `params.limit` if
696
+ * lower); pass `params.exclusiveStartTableName` (from a previous call's
697
+ * `lastEvaluatedTableName`) to page through the rest.
698
+ */
699
+ async listTables(params = {}) {
700
+ const client = this.getRawClient();
701
+ try {
702
+ const result = await client.send(new client_dynamodb_1.ListTablesCommand({
703
+ ExclusiveStartTableName: params.exclusiveStartTableName,
704
+ Limit: params.limit,
705
+ }));
706
+ return {
707
+ tableNames: result.TableNames ?? [],
708
+ lastEvaluatedTableName: result.LastEvaluatedTableName,
709
+ };
710
+ }
711
+ catch (err) {
712
+ throw this.wrapError('ListTables', err);
713
+ }
714
+ }
715
+ /**
716
+ * Reads back a table's full description (status, key schema, billing
717
+ * mode, indexes, stream ARN, item count, etc.) via `DescribeTableCommand`.
718
+ * `getStreamArn()` above is a narrow convenience wrapper around this same
719
+ * command for just the stream ARN.
720
+ */
721
+ async describeTable(tableName) {
722
+ const client = this.getRawClient();
723
+ try {
724
+ const result = await client.send(new client_dynamodb_1.DescribeTableCommand({ TableName: tableName }));
725
+ return result.Table;
726
+ }
727
+ catch (err) {
728
+ throw this.wrapError(`DescribeTable on '${tableName}'`, err);
729
+ }
730
+ }
731
+ /**
732
+ * Adds or removes a Global Secondary Index on an existing table via
733
+ * `UpdateTableCommand`'s `GlobalSecondaryIndexUpdates`. DynamoDB only
734
+ * allows one GSI create/delete/update action per `UpdateTable` call, so
735
+ * `gsiUpdates` must contain exactly one `GlobalSecondaryIndexUpdate`
736
+ * entry (`{ Create: {...} }`, `{ Update: {...} }`, or `{ Delete: {...} }`).
737
+ * When creating a new GSI, `attributeDefinitions` must include the key
738
+ * attribute(s) of the new index (DynamoDB validates this).
739
+ */
740
+ async updateTableIndexes(tableName, gsiUpdates, attributeDefinitions) {
741
+ const client = this.getRawClient();
742
+ try {
743
+ const result = await client.send(new client_dynamodb_1.UpdateTableCommand({
744
+ TableName: tableName,
745
+ GlobalSecondaryIndexUpdates: gsiUpdates,
746
+ AttributeDefinitions: attributeDefinitions,
747
+ }));
748
+ return result.TableDescription;
749
+ }
750
+ catch (err) {
751
+ throw this.wrapError(`UpdateTable (updateTableIndexes) on '${tableName}'`, err);
752
+ }
753
+ }
754
+ // ---------------------------------------------------------------------
755
+ // Billing/capacity mode
756
+ // ---------------------------------------------------------------------
757
+ /**
758
+ * Switches a table between on-demand (`PAY_PER_REQUEST`) and provisioned
759
+ * (`PROVISIONED`) capacity via `UpdateTableCommand`. When switching to
760
+ * `PROVISIONED`, `throughput` (`ReadCapacityUnits`/`WriteCapacityUnits`)
761
+ * is required (real DynamoDB rejects the call without it); it's ignored
762
+ * when switching to `PAY_PER_REQUEST`.
763
+ */
764
+ async updateBillingMode(tableName, mode, throughput) {
765
+ const client = this.getRawClient();
766
+ try {
767
+ const result = await client.send(new client_dynamodb_1.UpdateTableCommand({
768
+ TableName: tableName,
769
+ BillingMode: mode,
770
+ ProvisionedThroughput: mode === 'PROVISIONED' ? throughput : undefined,
771
+ }));
772
+ return result.TableDescription;
773
+ }
774
+ catch (err) {
775
+ throw this.wrapError(`UpdateTable (updateBillingMode) on '${tableName}'`, err);
776
+ }
777
+ }
778
+ // ---------------------------------------------------------------------
779
+ // Point-in-Time Recovery (PITR)
780
+ // ---------------------------------------------------------------------
781
+ /**
782
+ * Enables Point-in-Time Recovery on a table via
783
+ * `UpdateContinuousBackupsCommand`, allowing restore to any second within
784
+ * the retention window (35 days by default; DynamoDB currently ignores
785
+ * a custom `recoveryPeriodInDays` on enable and defaults to 35).
786
+ */
787
+ async enablePITR(tableName) {
788
+ const client = this.getRawClient();
789
+ try {
790
+ await client.send(new client_dynamodb_1.UpdateContinuousBackupsCommand({
791
+ TableName: tableName,
792
+ PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
793
+ }));
794
+ }
795
+ catch (err) {
796
+ throw this.wrapError(`UpdateContinuousBackups (enablePITR) on '${tableName}'`, err);
797
+ }
798
+ }
799
+ /** Disables Point-in-Time Recovery on a table via `UpdateContinuousBackupsCommand`. */
800
+ async disablePITR(tableName) {
801
+ const client = this.getRawClient();
802
+ try {
803
+ await client.send(new client_dynamodb_1.UpdateContinuousBackupsCommand({
804
+ TableName: tableName,
805
+ PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: false },
806
+ }));
807
+ }
808
+ catch (err) {
809
+ throw this.wrapError(`UpdateContinuousBackups (disablePITR) on '${tableName}'`, err);
810
+ }
811
+ }
812
+ /** Reads back a table's PITR configuration via `DescribeContinuousBackupsCommand`. */
813
+ async describePITR(tableName) {
814
+ const client = this.getRawClient();
815
+ try {
816
+ const result = await client.send(new client_dynamodb_1.DescribeContinuousBackupsCommand({ TableName: tableName }));
817
+ const pitr = result.ContinuousBackupsDescription?.PointInTimeRecoveryDescription;
818
+ return {
819
+ status: pitr?.PointInTimeRecoveryStatus,
820
+ recoveryPeriodInDays: pitr?.RecoveryPeriodInDays,
821
+ earliestRestorableDateTime: pitr?.EarliestRestorableDateTime,
822
+ latestRestorableDateTime: pitr?.LatestRestorableDateTime,
823
+ };
824
+ }
825
+ catch (err) {
826
+ throw this.wrapError(`DescribeContinuousBackups on '${tableName}'`, err);
827
+ }
828
+ }
829
+ // ---------------------------------------------------------------------
830
+ // On-demand backup/restore
831
+ // ---------------------------------------------------------------------
832
+ //
833
+ // Distinct from PITR above: these are named, on-demand snapshots (created
834
+ // explicitly, kept until deleted) rather than the continuous, rolling
835
+ // 35-day recovery window PITR provides.
836
+ /** Creates an on-demand backup of a table via `CreateBackupCommand`. */
837
+ async createBackup(tableName, backupName) {
838
+ const client = this.getRawClient();
839
+ try {
840
+ const result = await client.send(new client_dynamodb_1.CreateBackupCommand({ TableName: tableName, BackupName: backupName }));
841
+ return result.BackupDetails;
842
+ }
843
+ catch (err) {
844
+ throw this.wrapError(`CreateBackup on '${tableName}'`, err);
845
+ }
846
+ }
847
+ /** Reads back a backup's full description via `DescribeBackupCommand`. */
848
+ async describeBackup(backupArn) {
849
+ const client = this.getRawClient();
850
+ try {
851
+ const result = await client.send(new client_dynamodb_1.DescribeBackupCommand({ BackupArn: backupArn }));
852
+ return result.BackupDescription;
853
+ }
854
+ catch (err) {
855
+ throw this.wrapError(`DescribeBackup on '${backupArn}'`, err);
856
+ }
857
+ }
858
+ /**
859
+ * Lists backups via `ListBackupsCommand`, optionally scoped to a single
860
+ * table. Does not auto-paginate — pass the last item's `BackupArn` back
861
+ * in as `exclusiveStartBackupArn` to fetch the next page, same pattern as
862
+ * `listTables()`.
863
+ */
864
+ async listBackups(tableName, options = {}) {
865
+ const client = this.getRawClient();
866
+ try {
867
+ const result = await client.send(new client_dynamodb_1.ListBackupsCommand({
868
+ TableName: tableName,
869
+ Limit: options.limit,
870
+ ExclusiveStartBackupArn: options.exclusiveStartBackupArn,
871
+ }));
872
+ return result.BackupSummaries ?? [];
873
+ }
874
+ catch (err) {
875
+ throw this.wrapError('ListBackups', err);
876
+ }
877
+ }
878
+ /**
879
+ * Restores a backup into a new table via `RestoreTableFromBackupCommand`.
880
+ * DynamoDB always restores into a *new* table (`newTableName` must not
881
+ * already exist) — it does not overwrite the original table the backup
882
+ * was taken from. Like `createTable()`, this is asynchronous: the new
883
+ * table starts in `CREATING` status.
884
+ */
885
+ async restoreTableFromBackup(backupArn, newTableName) {
886
+ const client = this.getRawClient();
887
+ try {
888
+ const result = await client.send(new client_dynamodb_1.RestoreTableFromBackupCommand({ BackupArn: backupArn, TargetTableName: newTableName }));
889
+ return result.TableDescription;
890
+ }
891
+ catch (err) {
892
+ throw this.wrapError(`RestoreTableFromBackup to '${newTableName}'`, err);
893
+ }
894
+ }
895
+ // ---------------------------------------------------------------------
896
+ // Tagging
897
+ // ---------------------------------------------------------------------
898
+ /** Adds (or updates, if the key already exists) tags on a table/backup resource via `TagResourceCommand`. */
899
+ async tagResource(resourceArn, tags) {
900
+ const client = this.getRawClient();
901
+ const Tags = Array.isArray(tags)
902
+ ? tags
903
+ : Object.entries(tags).map(([Key, Value]) => ({ Key, Value }));
904
+ try {
905
+ await client.send(new client_dynamodb_1.TagResourceCommand({ ResourceArn: resourceArn, Tags }));
906
+ }
907
+ catch (err) {
908
+ throw this.wrapError(`TagResource on '${resourceArn}'`, err);
909
+ }
910
+ }
911
+ /** Removes tags (by key) from a table/backup resource via `UntagResourceCommand`. */
912
+ async untagResource(resourceArn, tagKeys) {
913
+ const client = this.getRawClient();
914
+ try {
915
+ await client.send(new client_dynamodb_1.UntagResourceCommand({ ResourceArn: resourceArn, TagKeys: tagKeys }));
916
+ }
917
+ catch (err) {
918
+ throw this.wrapError(`UntagResource on '${resourceArn}'`, err);
919
+ }
920
+ }
921
+ /**
922
+ * Lists tags on a table/backup resource via `ListTagsOfResourceCommand`.
923
+ * Does not auto-paginate — pass a returned `nextToken` back in as
924
+ * `options.nextToken` to fetch the next page.
925
+ */
926
+ async listTags(resourceArn, options = {}) {
927
+ const client = this.getRawClient();
928
+ try {
929
+ const result = await client.send(new client_dynamodb_1.ListTagsOfResourceCommand({ ResourceArn: resourceArn, NextToken: options.nextToken }));
930
+ return { tags: result.Tags ?? [], nextToken: result.NextToken };
931
+ }
932
+ catch (err) {
933
+ throw this.wrapError(`ListTagsOfResource on '${resourceArn}'`, err);
934
+ }
935
+ }
936
+ }
937
+ exports.DynamoDbStore = DynamoDbStore;