effect-dynamodb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/Aggregate.d.ts +210 -0
- package/dist/Aggregate.d.ts.map +1 -0
- package/dist/Aggregate.js +1169 -0
- package/dist/Aggregate.js.map +1 -0
- package/dist/Batch.d.ts +62 -0
- package/dist/Batch.d.ts.map +1 -0
- package/dist/Batch.js +218 -0
- package/dist/Batch.js.map +1 -0
- package/dist/Collection.d.ts +58 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Collection.js +195 -0
- package/dist/Collection.js.map +1 -0
- package/dist/Collections.d.ts +121 -0
- package/dist/Collections.d.ts.map +1 -0
- package/dist/Collections.js +121 -0
- package/dist/Collections.js.map +1 -0
- package/dist/DynamoClient.d.ts +317 -0
- package/dist/DynamoClient.d.ts.map +1 -0
- package/dist/DynamoClient.js +653 -0
- package/dist/DynamoClient.js.map +1 -0
- package/dist/DynamoModel.d.ts +278 -0
- package/dist/DynamoModel.d.ts.map +1 -0
- package/dist/DynamoModel.js +453 -0
- package/dist/DynamoModel.js.map +1 -0
- package/dist/DynamoSchema.d.ts +137 -0
- package/dist/DynamoSchema.d.ts.map +1 -0
- package/dist/DynamoSchema.js +188 -0
- package/dist/DynamoSchema.js.map +1 -0
- package/dist/Entity.d.ts +628 -0
- package/dist/Entity.d.ts.map +1 -0
- package/dist/Entity.js +2371 -0
- package/dist/Entity.js.map +1 -0
- package/dist/Errors.d.ts +227 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Errors.js +93 -0
- package/dist/Errors.js.map +1 -0
- package/dist/EventStore.d.ts +208 -0
- package/dist/EventStore.d.ts.map +1 -0
- package/dist/EventStore.js +333 -0
- package/dist/EventStore.js.map +1 -0
- package/dist/Expression.d.ts +50 -0
- package/dist/Expression.d.ts.map +1 -0
- package/dist/Expression.js +170 -0
- package/dist/Expression.js.map +1 -0
- package/dist/KeyComposer.d.ts +125 -0
- package/dist/KeyComposer.d.ts.map +1 -0
- package/dist/KeyComposer.js +257 -0
- package/dist/KeyComposer.js.map +1 -0
- package/dist/Marshaller.d.ts +30 -0
- package/dist/Marshaller.d.ts.map +1 -0
- package/dist/Marshaller.js +115 -0
- package/dist/Marshaller.js.map +1 -0
- package/dist/Projection.d.ts +28 -0
- package/dist/Projection.d.ts.map +1 -0
- package/dist/Projection.js +39 -0
- package/dist/Projection.js.map +1 -0
- package/dist/Query.d.ts +214 -0
- package/dist/Query.d.ts.map +1 -0
- package/dist/Query.js +397 -0
- package/dist/Query.js.map +1 -0
- package/dist/Table.d.ts +178 -0
- package/dist/Table.d.ts.map +1 -0
- package/dist/Table.js +211 -0
- package/dist/Table.js.map +1 -0
- package/dist/Transaction.d.ts +79 -0
- package/dist/Transaction.d.ts.map +1 -0
- package/dist/Transaction.js +215 -0
- package/dist/Transaction.js.map +1 -0
- package/dist/_AggregateCursor.d.ts +44 -0
- package/dist/_AggregateCursor.d.ts.map +1 -0
- package/dist/_AggregateCursor.js +18 -0
- package/dist/_AggregateCursor.js.map +1 -0
- package/dist/_AggregateEdges.d.ts +77 -0
- package/dist/_AggregateEdges.d.ts.map +1 -0
- package/dist/_AggregateEdges.js +41 -0
- package/dist/_AggregateEdges.js.map +1 -0
- package/dist/_AggregateSchemas.d.ts +69 -0
- package/dist/_AggregateSchemas.d.ts.map +1 -0
- package/dist/_AggregateSchemas.js +194 -0
- package/dist/_AggregateSchemas.js.map +1 -0
- package/dist/_AggregateTypes.d.ts +94 -0
- package/dist/_AggregateTypes.d.ts.map +1 -0
- package/dist/_AggregateTypes.js +7 -0
- package/dist/_AggregateTypes.js.map +1 -0
- package/dist/_EntityCombinators.d.ts +155 -0
- package/dist/_EntityCombinators.d.ts.map +1 -0
- package/dist/_EntityCombinators.js +158 -0
- package/dist/_EntityCombinators.js.map +1 -0
- package/dist/_EntityConfig.d.ts +100 -0
- package/dist/_EntityConfig.d.ts.map +1 -0
- package/dist/_EntityConfig.js +7 -0
- package/dist/_EntityConfig.js.map +1 -0
- package/dist/_EntityOps.d.ts +205 -0
- package/dist/_EntityOps.d.ts.map +1 -0
- package/dist/_EntityOps.js +137 -0
- package/dist/_EntityOps.js.map +1 -0
- package/dist/_EntitySchemas.d.ts +57 -0
- package/dist/_EntitySchemas.d.ts.map +1 -0
- package/dist/_EntitySchemas.js +298 -0
- package/dist/_EntitySchemas.js.map +1 -0
- package/dist/_EntityTypes.d.ts +81 -0
- package/dist/_EntityTypes.d.ts.map +1 -0
- package/dist/_EntityTypes.js +7 -0
- package/dist/_EntityTypes.js.map +1 -0
- package/dist/_sparse_check.d.ts +2 -0
- package/dist/_sparse_check.d.ts.map +1 -0
- package/dist/_sparse_check.js +193 -0
- package/dist/_sparse_check.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/AggregateCursor.d.ts +44 -0
- package/dist/internal/AggregateCursor.d.ts.map +1 -0
- package/dist/internal/AggregateCursor.js +18 -0
- package/dist/internal/AggregateCursor.js.map +1 -0
- package/dist/internal/AggregateEdges.d.ts +77 -0
- package/dist/internal/AggregateEdges.d.ts.map +1 -0
- package/dist/internal/AggregateEdges.js +41 -0
- package/dist/internal/AggregateEdges.js.map +1 -0
- package/dist/internal/AggregateSchemas.d.ts +69 -0
- package/dist/internal/AggregateSchemas.d.ts.map +1 -0
- package/dist/internal/AggregateSchemas.js +193 -0
- package/dist/internal/AggregateSchemas.js.map +1 -0
- package/dist/internal/AggregateTypes.d.ts +94 -0
- package/dist/internal/AggregateTypes.d.ts.map +1 -0
- package/dist/internal/AggregateTypes.js +7 -0
- package/dist/internal/AggregateTypes.js.map +1 -0
- package/dist/internal/BoundQuery.d.ts +107 -0
- package/dist/internal/BoundQuery.d.ts.map +1 -0
- package/dist/internal/BoundQuery.js +111 -0
- package/dist/internal/BoundQuery.js.map +1 -0
- package/dist/internal/EntityCombinators.d.ts +173 -0
- package/dist/internal/EntityCombinators.d.ts.map +1 -0
- package/dist/internal/EntityCombinators.js +183 -0
- package/dist/internal/EntityCombinators.js.map +1 -0
- package/dist/internal/EntityConfig.d.ts +100 -0
- package/dist/internal/EntityConfig.d.ts.map +1 -0
- package/dist/internal/EntityConfig.js +7 -0
- package/dist/internal/EntityConfig.js.map +1 -0
- package/dist/internal/EntityOps.d.ts +268 -0
- package/dist/internal/EntityOps.d.ts.map +1 -0
- package/dist/internal/EntityOps.js +153 -0
- package/dist/internal/EntityOps.js.map +1 -0
- package/dist/internal/EntitySchemas.d.ts +57 -0
- package/dist/internal/EntitySchemas.d.ts.map +1 -0
- package/dist/internal/EntitySchemas.js +299 -0
- package/dist/internal/EntitySchemas.js.map +1 -0
- package/dist/internal/EntityTypes.d.ts +128 -0
- package/dist/internal/EntityTypes.d.ts.map +1 -0
- package/dist/internal/EntityTypes.js +7 -0
- package/dist/internal/EntityTypes.js.map +1 -0
- package/dist/internal/Expr.d.ts +141 -0
- package/dist/internal/Expr.d.ts.map +1 -0
- package/dist/internal/Expr.js +332 -0
- package/dist/internal/Expr.js.map +1 -0
- package/dist/internal/PathBuilder.d.ts +88 -0
- package/dist/internal/PathBuilder.d.ts.map +1 -0
- package/dist/internal/PathBuilder.js +100 -0
- package/dist/internal/PathBuilder.js.map +1 -0
- package/dist/internal/TransactableOps.d.ts +25 -0
- package/dist/internal/TransactableOps.d.ts.map +1 -0
- package/dist/internal/TransactableOps.js +58 -0
- package/dist/internal/TransactableOps.js.map +1 -0
- package/dist/internal/sparse_check.d.ts +2 -0
- package/dist/internal/sparse_check.d.ts.map +1 -0
- package/dist/internal/sparse_check.js +196 -0
- package/dist/internal/sparse_check.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DynamoClient — Effect service wrapping the AWS SDK DynamoDBClient.
|
|
3
|
+
*
|
|
4
|
+
* All DynamoDB operations are exposed as Effect-returning methods that fail
|
|
5
|
+
* with {@link DynamoError}. The underlying SDK client lifecycle is managed
|
|
6
|
+
* via `Layer.scoped` (acquire on layer build, destroy on scope close).
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { BatchGetItemCommand, BatchWriteItemCommand, CreateBackupCommand, CreateTableCommand, DeleteBackupCommand, DeleteItemCommand, DeleteTableCommand, DescribeContinuousBackupsCommand, DescribeExportCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DynamoDBClient, ExportTableToPointInTimeCommand, GetItemCommand, ListBackupsCommand, ListTablesCommand, ListTagsOfResourceCommand, PutItemCommand, QueryCommand, RestoreTableFromBackupCommand, RestoreTableToPointInTimeCommand, ScanCommand, TagResourceCommand, TransactGetItemsCommand, TransactWriteItemsCommand, UntagResourceCommand, UpdateContinuousBackupsCommand, UpdateItemCommand, UpdateTableCommand, UpdateTimeToLiveCommand, } from "@aws-sdk/client-dynamodb";
|
|
11
|
+
import { Config, Context, Effect, Layer } from "effect";
|
|
12
|
+
import { bind as aggregateBind } from "./Aggregate.js";
|
|
13
|
+
import * as DynamoSchema from "./DynamoSchema.js";
|
|
14
|
+
import { bind as entityBind } from "./Entity.js";
|
|
15
|
+
import { DynamoError, DynamoValidationError, InternalServerError, isAwsInternalServerError, isAwsResourceNotFound, isAwsThrottling, isAwsValidationError, ResourceNotFoundError, ThrottlingError, ValidationError, } from "./Errors.js";
|
|
16
|
+
import { BoundQueryImpl } from "./internal/BoundQuery.js";
|
|
17
|
+
import { createConditionOps } from "./internal/Expr.js";
|
|
18
|
+
import { createPathBuilder } from "./internal/PathBuilder.js";
|
|
19
|
+
import * as KeyComposer from "./KeyComposer.js";
|
|
20
|
+
import * as Query from "./Query.js";
|
|
21
|
+
import { definition as tableDefinition } from "./Table.js";
|
|
22
|
+
/**
|
|
23
|
+
* Effect Context.Service for the DynamoDB client service.
|
|
24
|
+
*
|
|
25
|
+
* Use `DynamoClient.layer(config)` to construct a live Layer that manages
|
|
26
|
+
* the underlying AWS SDK client lifecycle with `Effect.acquireRelease`.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const live = DynamoClient.layer({ region: "us-east-1" })
|
|
31
|
+
* pipe(program, Effect.provide(live), Effect.runPromise)
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class DynamoClient extends Context.Service()("@effect-dynamodb/DynamoClient") {
|
|
35
|
+
/**
|
|
36
|
+
* Create a live Layer that manages an AWS DynamoDBClient.
|
|
37
|
+
*
|
|
38
|
+
* @param config.region - AWS region
|
|
39
|
+
* @param config.endpoint - Optional endpoint override (e.g., for DynamoDB Local)
|
|
40
|
+
* @param config.credentials - Optional static credentials
|
|
41
|
+
*/
|
|
42
|
+
static layer = (config) => Layer.effect(DynamoClient, buildService(config.region, config.endpoint, config.credentials));
|
|
43
|
+
/**
|
|
44
|
+
* Create a live Layer that reads configuration from Effect Config providers
|
|
45
|
+
* (e.g., environment variables, config files).
|
|
46
|
+
*
|
|
47
|
+
* @param config.region - Config for AWS region
|
|
48
|
+
* @param config.endpoint - Optional Config for endpoint override
|
|
49
|
+
* @param config.credentials - Optional Config for static credentials
|
|
50
|
+
*/
|
|
51
|
+
static layerConfig = (config) => Layer.effect(DynamoClient, Effect.gen(function* () {
|
|
52
|
+
const region = yield* config.region;
|
|
53
|
+
const endpoint = config.endpoint ? yield* Config.option(config.endpoint) : undefined;
|
|
54
|
+
const credentials = config.credentials
|
|
55
|
+
? yield* Config.option(config.credentials)
|
|
56
|
+
: undefined;
|
|
57
|
+
return yield* buildService(region, endpoint && endpoint._tag === "Some" ? endpoint.value : undefined, credentials && credentials._tag === "Some" ? credentials.value : undefined);
|
|
58
|
+
}));
|
|
59
|
+
/**
|
|
60
|
+
* Create a typed client gateway from entities, aggregates, and (optionally) tables.
|
|
61
|
+
*
|
|
62
|
+
* Returns a namespaced client with `entities`, `aggregates`, `collections`, and
|
|
63
|
+
* `tables` properties. Collections are auto-discovered from entity index
|
|
64
|
+
* `collection` properties — no separate registration needed.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const db = yield* DynamoClient.make({
|
|
69
|
+
* entities: { Users, Tasks },
|
|
70
|
+
* aggregates: { OrderAggregate },
|
|
71
|
+
* tables: { MainTable },
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* yield* db.entities.Users.get({ userId: "123" })
|
|
75
|
+
* yield* db.aggregates.OrderAggregate.get({ orderId: "o-1" })
|
|
76
|
+
* yield* db.collections.assignments({ userId: "123" }).collect()
|
|
77
|
+
* yield* db.tables.MainTable.create()
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
static make = (config) => makeFromConfig(config);
|
|
81
|
+
}
|
|
82
|
+
const makeFromConfig = (config) => Effect.gen(function* () {
|
|
83
|
+
// 1. Resolve the provide function from context
|
|
84
|
+
const ctx = yield* Effect.context();
|
|
85
|
+
const provide = (effect) => Effect.provide(effect, ctx);
|
|
86
|
+
// Helper: validate query composites at runtime
|
|
87
|
+
const validateQueryComposites = (indexName, indexDef, composites) => {
|
|
88
|
+
const pkAttrs = indexDef.pk.composite;
|
|
89
|
+
const skAttrs = indexDef.sk.composite;
|
|
90
|
+
// 1. All PK composites must be present
|
|
91
|
+
for (const attr of pkAttrs) {
|
|
92
|
+
if (composites[attr] === undefined) {
|
|
93
|
+
throw new Error(`[EDD-9002] Missing required partition key attribute "${attr}" for index "${indexName}"`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// 2. SK composites must follow prefix ordering
|
|
97
|
+
let lastProvided = -1;
|
|
98
|
+
for (let i = 0; i < skAttrs.length; i++) {
|
|
99
|
+
if (composites[skAttrs[i]] !== undefined) {
|
|
100
|
+
if (i !== lastProvided + 1) {
|
|
101
|
+
const missing = skAttrs.slice(lastProvided + 1, i).join(", ");
|
|
102
|
+
throw new Error(`[EDD-9004] Sort key composite "${skAttrs[i]}" for index "${indexName}" requires prior composites: ${missing}. Sort key composites must follow prefix ordering.`);
|
|
103
|
+
}
|
|
104
|
+
lastProvided = i;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// 3. No excess properties
|
|
108
|
+
const validKeys = new Set([...pkAttrs, ...skAttrs]);
|
|
109
|
+
for (const key of Object.keys(composites)) {
|
|
110
|
+
if (!validKeys.has(key)) {
|
|
111
|
+
throw new Error(`[EDD-9006] Unknown composite attribute "${key}" for index "${indexName}". Valid attributes: ${[...validKeys].join(", ")}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
// Helper: build a BoundQuery for a single-entity index query
|
|
116
|
+
const buildEntityQueryAccessor = (entityLike, _indexName, indexDef) => {
|
|
117
|
+
return (composites) => {
|
|
118
|
+
validateQueryComposites(_indexName, indexDef, composites);
|
|
119
|
+
const pkValue = KeyComposer.composePk(entityLike._schema, entityLike.entityType, indexDef, composites);
|
|
120
|
+
const query = Query.make({
|
|
121
|
+
tableName: "",
|
|
122
|
+
indexName: indexDef.index,
|
|
123
|
+
pkField: indexDef.pk.field,
|
|
124
|
+
pkValue,
|
|
125
|
+
skField: indexDef.sk.field,
|
|
126
|
+
entityTypes: [entityLike.entityType],
|
|
127
|
+
decoder: (raw) => entityLike._decodeRecord(raw),
|
|
128
|
+
resolveTableName: entityLike._tableTag.useSync((tc) => tc.name),
|
|
129
|
+
});
|
|
130
|
+
// Apply SK prefix from provided composites
|
|
131
|
+
const hasSkComposites = indexDef.sk.composite.some((attr) => composites[attr] !== undefined);
|
|
132
|
+
const finalQuery = hasSkComposites
|
|
133
|
+
? Query.where(query, {
|
|
134
|
+
beginsWith: KeyComposer.composeSortKeyPrefix(entityLike._schema, entityLike.entityType, 1, indexDef, composites),
|
|
135
|
+
})
|
|
136
|
+
: query;
|
|
137
|
+
const pathBuilder = createPathBuilder();
|
|
138
|
+
const conditionOps = createConditionOps();
|
|
139
|
+
// composeSkCondition: prepend the entity SK prefix to user's .where() condition
|
|
140
|
+
const composeSkCondition = (condition) => {
|
|
141
|
+
const skPrefix = KeyComposer.composeSortKeyPrefix(entityLike._schema, entityLike.entityType, 1, indexDef, composites);
|
|
142
|
+
const prepend = (value) => `${skPrefix}#${value}`;
|
|
143
|
+
if ("eq" in condition)
|
|
144
|
+
return { eq: prepend(condition.eq) };
|
|
145
|
+
if ("lt" in condition)
|
|
146
|
+
return { lt: prepend(condition.lt) };
|
|
147
|
+
if ("lte" in condition)
|
|
148
|
+
return { lte: prepend(condition.lte) };
|
|
149
|
+
if ("gt" in condition)
|
|
150
|
+
return { gt: prepend(condition.gt) };
|
|
151
|
+
if ("gte" in condition)
|
|
152
|
+
return { gte: prepend(condition.gte) };
|
|
153
|
+
if ("between" in condition)
|
|
154
|
+
return { between: [prepend(condition.between[0]), prepend(condition.between[1])] };
|
|
155
|
+
if ("beginsWith" in condition)
|
|
156
|
+
return { beginsWith: prepend(condition.beginsWith) };
|
|
157
|
+
return condition;
|
|
158
|
+
};
|
|
159
|
+
const bqConfig = {
|
|
160
|
+
pathBuilder,
|
|
161
|
+
conditionOps,
|
|
162
|
+
provide,
|
|
163
|
+
composeSkCondition,
|
|
164
|
+
skFields: indexDef.sk.composite,
|
|
165
|
+
};
|
|
166
|
+
return new BoundQueryImpl(finalQuery, bqConfig);
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
// 2. Bind entities + build entity query accessors
|
|
170
|
+
const boundEntities = {};
|
|
171
|
+
// Track collection memberships for auto-discovery
|
|
172
|
+
// collectionName → { entityKey, entityLike, indexName, indexDef }[]
|
|
173
|
+
const collectionMembers = new Map();
|
|
174
|
+
for (const [key, entity] of Object.entries(config.entities)) {
|
|
175
|
+
const bound = yield* entityBind(entity);
|
|
176
|
+
const entityLike = entity;
|
|
177
|
+
const accessors = {};
|
|
178
|
+
// Add query accessor for each non-primary index
|
|
179
|
+
for (const [indexName, indexDef] of Object.entries(entityLike.indexes)) {
|
|
180
|
+
if (indexName === "primary")
|
|
181
|
+
continue;
|
|
182
|
+
if (!indexDef)
|
|
183
|
+
continue;
|
|
184
|
+
accessors[indexName] = buildEntityQueryAccessor(entityLike, indexName, indexDef);
|
|
185
|
+
// Track collection membership for auto-discovery
|
|
186
|
+
if (indexDef.collection) {
|
|
187
|
+
const collNames = Array.isArray(indexDef.collection)
|
|
188
|
+
? indexDef.collection
|
|
189
|
+
: [indexDef.collection];
|
|
190
|
+
for (const collName of collNames) {
|
|
191
|
+
if (!collectionMembers.has(collName)) {
|
|
192
|
+
collectionMembers.set(collName, []);
|
|
193
|
+
}
|
|
194
|
+
collectionMembers.get(collName).push({ entityKey: key, entityLike, indexDef });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Add scan accessor
|
|
199
|
+
accessors.scan = () => {
|
|
200
|
+
const scanQuery = Query.makeScan({
|
|
201
|
+
tableName: "",
|
|
202
|
+
indexName: undefined,
|
|
203
|
+
entityTypes: [entityLike.entityType],
|
|
204
|
+
decoder: (raw) => entityLike._decodeRecord(raw),
|
|
205
|
+
resolveTableName: entityLike._tableTag.useSync((tc) => tc.name),
|
|
206
|
+
});
|
|
207
|
+
const pathBuilder = createPathBuilder();
|
|
208
|
+
const conditionOps = createConditionOps();
|
|
209
|
+
const bqConfig = { pathBuilder, conditionOps, provide };
|
|
210
|
+
return new BoundQueryImpl(scanQuery, bqConfig);
|
|
211
|
+
};
|
|
212
|
+
boundEntities[key] = { ...bound, ...accessors };
|
|
213
|
+
}
|
|
214
|
+
// 3. Build auto-discovered collection accessors
|
|
215
|
+
const boundCollections = {};
|
|
216
|
+
for (const [collName, members] of collectionMembers) {
|
|
217
|
+
boundCollections[collName] = (composites) => {
|
|
218
|
+
// Use the first member for PK composition
|
|
219
|
+
const firstMember = members[0];
|
|
220
|
+
const indexDef = firstMember.indexDef;
|
|
221
|
+
// Build entity type → entity key lookup
|
|
222
|
+
const memberByType = new Map();
|
|
223
|
+
const entityTypes = [];
|
|
224
|
+
for (const member of members) {
|
|
225
|
+
memberByType.set(member.entityLike.entityType, member.entityKey);
|
|
226
|
+
entityTypes.push(member.entityLike.entityType);
|
|
227
|
+
}
|
|
228
|
+
// Collection decoder: decode and group by entity key
|
|
229
|
+
const collDecoder = (raw) => {
|
|
230
|
+
const entityType = raw.__edd_e__;
|
|
231
|
+
if (!entityType) {
|
|
232
|
+
return Effect.fail(new ValidationError({
|
|
233
|
+
entityType: "unknown",
|
|
234
|
+
operation: "collection.decode",
|
|
235
|
+
cause: "Item missing __edd_e__",
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
const entityKey = memberByType.get(entityType);
|
|
239
|
+
if (!entityKey) {
|
|
240
|
+
return Effect.succeed({ _memberKey: "__unknown__", _decoded: raw });
|
|
241
|
+
}
|
|
242
|
+
const member = members.find((m) => m.entityLike.entityType === entityType);
|
|
243
|
+
if (!member) {
|
|
244
|
+
return Effect.succeed({ _memberKey: "__unknown__", _decoded: raw });
|
|
245
|
+
}
|
|
246
|
+
return member.entityLike
|
|
247
|
+
._decodeRecord(raw)
|
|
248
|
+
.pipe(Effect.map((decoded) => ({ _memberKey: entityKey, _decoded: decoded })));
|
|
249
|
+
};
|
|
250
|
+
const pkValue = KeyComposer.composePk(firstMember.entityLike._schema, firstMember.entityLike.entityType, indexDef, composites);
|
|
251
|
+
// Always isolated — begins_with on collection SK prefix
|
|
252
|
+
let query = Query.make({
|
|
253
|
+
tableName: "",
|
|
254
|
+
indexName: indexDef.index,
|
|
255
|
+
pkField: indexDef.pk.field,
|
|
256
|
+
pkValue,
|
|
257
|
+
skField: indexDef.sk.field,
|
|
258
|
+
entityTypes,
|
|
259
|
+
decoder: collDecoder,
|
|
260
|
+
resolveTableName: firstMember.entityLike._tableTag.useSync((tc) => tc.name),
|
|
261
|
+
});
|
|
262
|
+
// Add begins_with on collection SK prefix for clustered collections.
|
|
263
|
+
// For isolated collections (default), each entity has its own entity-type
|
|
264
|
+
// SK prefix, so a collection-name begins_with would filter them all out.
|
|
265
|
+
//
|
|
266
|
+
// For sub-collections (collection: ["parent", "child"]), the SK prefix
|
|
267
|
+
// includes the full hierarchy from root up to and including the queried
|
|
268
|
+
// collection name. A query at the parent level matches every descendant.
|
|
269
|
+
if (indexDef.sk.field && indexDef.type === "clustered") {
|
|
270
|
+
const coll = indexDef.collection;
|
|
271
|
+
const hierarchy = Array.isArray(coll)
|
|
272
|
+
? coll.slice(0, coll.indexOf(collName) + 1)
|
|
273
|
+
: [collName];
|
|
274
|
+
const casing = indexDef.casing ?? firstMember.entityLike._schema.casing;
|
|
275
|
+
const pre = DynamoSchema.prefix(firstMember.entityLike._schema);
|
|
276
|
+
const casedNames = hierarchy.map((n) => DynamoSchema.applyCasing(n, casing));
|
|
277
|
+
const skPrefix = `${pre}#${casedNames.join("#")}`;
|
|
278
|
+
query = Query.where(query, { beginsWith: skPrefix });
|
|
279
|
+
}
|
|
280
|
+
// Wrap in BoundQuery that groups results by entity key
|
|
281
|
+
const pathBuilder = createPathBuilder();
|
|
282
|
+
const conditionOps = createConditionOps();
|
|
283
|
+
const collectionProvide = (eff) => Effect.provide(eff, ctx);
|
|
284
|
+
const bqConfig = {
|
|
285
|
+
pathBuilder,
|
|
286
|
+
conditionOps,
|
|
287
|
+
provide: collectionProvide,
|
|
288
|
+
};
|
|
289
|
+
const bq = new BoundQueryImpl(query, bqConfig);
|
|
290
|
+
// Override collect to group results
|
|
291
|
+
const originalCollect = bq.collect.bind(bq);
|
|
292
|
+
bq.collect = () => Effect.map(originalCollect(), (items) => {
|
|
293
|
+
const result = {};
|
|
294
|
+
for (const member of members) {
|
|
295
|
+
result[member.entityKey] = [];
|
|
296
|
+
}
|
|
297
|
+
for (const item of items) {
|
|
298
|
+
const memberKey = item._memberKey;
|
|
299
|
+
if (memberKey && result[memberKey]) {
|
|
300
|
+
result[memberKey].push(item._decoded);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return result;
|
|
304
|
+
});
|
|
305
|
+
return bq;
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
// 4. Bind aggregates
|
|
309
|
+
const boundAggregates = {};
|
|
310
|
+
if (config.aggregates) {
|
|
311
|
+
for (const [key, aggregate] of Object.entries(config.aggregates)) {
|
|
312
|
+
boundAggregates[key] = yield* aggregateBind(aggregate);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// 5. Build table operations
|
|
316
|
+
const client = yield* DynamoClient;
|
|
317
|
+
const tables = {};
|
|
318
|
+
if (config.tables) {
|
|
319
|
+
for (const [tableKey, table] of Object.entries(config.tables)) {
|
|
320
|
+
const tableConfig = yield* table.Tag;
|
|
321
|
+
// Merge any runtime-registered aggregates from `config.aggregates` whose
|
|
322
|
+
// `_tableTag` matches this table's tag. Users may intentionally omit
|
|
323
|
+
// aggregates from `Table.make({ aggregates })` to sidestep the multi-file
|
|
324
|
+
// circular-import problem, registering them on `DynamoClient.make` instead.
|
|
325
|
+
// Without this merge, `tableDefinition()` would never see those aggregates
|
|
326
|
+
// and `db.tables.*.create()` would silently drop their (L|G)SIs.
|
|
327
|
+
let mergedTable = table;
|
|
328
|
+
if (config.aggregates) {
|
|
329
|
+
const tableTagKey = table.Tag.key;
|
|
330
|
+
const existingAggs = table.aggregates;
|
|
331
|
+
const existingAggSet = new Set(Object.values(existingAggs));
|
|
332
|
+
const extraAggregateEntries = [];
|
|
333
|
+
for (const [aggKey, agg] of Object.entries(config.aggregates)) {
|
|
334
|
+
const aggTag = agg._tableTag;
|
|
335
|
+
if (aggTag === undefined)
|
|
336
|
+
continue;
|
|
337
|
+
if (aggTag.key !== tableTagKey)
|
|
338
|
+
continue;
|
|
339
|
+
// Skip if this aggregate is already registered on the table object
|
|
340
|
+
// (reference equality) so we don't emit duplicate LSI/GSI entries.
|
|
341
|
+
if (existingAggSet.has(agg))
|
|
342
|
+
continue;
|
|
343
|
+
extraAggregateEntries.push([aggKey, agg]);
|
|
344
|
+
}
|
|
345
|
+
if (extraAggregateEntries.length > 0) {
|
|
346
|
+
mergedTable = {
|
|
347
|
+
...table,
|
|
348
|
+
aggregates: {
|
|
349
|
+
...existingAggs,
|
|
350
|
+
...Object.fromEntries(extraAggregateEntries),
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
tables[tableKey] = buildTableOperationsFromTable(tableConfig.name, mergedTable, client);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// Group entities by table tag so we can derive full table schema for create().
|
|
360
|
+
// Aggregates are also assigned to a table tag via their underlying entities;
|
|
361
|
+
// we collect them here so create() includes their GSIs.
|
|
362
|
+
const entitiesByTag = new Map();
|
|
363
|
+
for (const [, entity] of Object.entries(config.entities)) {
|
|
364
|
+
const entityLike = entity;
|
|
365
|
+
const tagId = entityLike._tableTag.key;
|
|
366
|
+
if (!entitiesByTag.has(tagId)) {
|
|
367
|
+
entitiesByTag.set(tagId, { tag: entityLike._tableTag, entities: [], aggregates: [] });
|
|
368
|
+
}
|
|
369
|
+
entitiesByTag.get(tagId).entities.push(entityLike);
|
|
370
|
+
}
|
|
371
|
+
// Attach aggregates to whichever table tag matches their root entity tag.
|
|
372
|
+
// (Aggregates expose `_tableTag` from their root entity registration.)
|
|
373
|
+
if (config.aggregates) {
|
|
374
|
+
for (const [, agg] of Object.entries(config.aggregates)) {
|
|
375
|
+
const aggTag = agg._tableTag;
|
|
376
|
+
if (aggTag && entitiesByTag.has(aggTag.key)) {
|
|
377
|
+
entitiesByTag.get(aggTag.key).aggregates.push(agg);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (const [, { tag, entities: tableEntities, aggregates: tableAggregates },] of entitiesByTag) {
|
|
382
|
+
const tableConfig = yield* tag;
|
|
383
|
+
// Build a minimal Table-like object so tableDefinition() can derive GSIs
|
|
384
|
+
// (entities + aggregates).
|
|
385
|
+
const syntheticTable = {
|
|
386
|
+
entities: Object.fromEntries(tableEntities.map((e) => [e.entityType, e])),
|
|
387
|
+
aggregates: Object.fromEntries(tableAggregates.map((a, i) => [
|
|
388
|
+
a.name ?? `__aggregate_${i}__`,
|
|
389
|
+
a,
|
|
390
|
+
])),
|
|
391
|
+
};
|
|
392
|
+
tables[tableConfig.name] = buildTableOperationsFromTable(tableConfig.name, syntheticTable, client);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
entities: boundEntities,
|
|
397
|
+
aggregates: boundAggregates,
|
|
398
|
+
collections: boundCollections,
|
|
399
|
+
tables,
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// buildTableOperations — pre-bound table management
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
/** Check if a DynamoError wraps a ResourceInUseException (table already exists). */
|
|
406
|
+
const isResourceInUse = (err) => err._tag === "DynamoError" &&
|
|
407
|
+
err.cause != null &&
|
|
408
|
+
typeof err.cause === "object" &&
|
|
409
|
+
"name" in err.cause &&
|
|
410
|
+
err.cause.name === "ResourceInUseException";
|
|
411
|
+
/** Make createTable idempotent — ignore if table already exists. */
|
|
412
|
+
const idempotentCreate = (effect) => Effect.catchIf(effect, isResourceInUse, () => Effect.void);
|
|
413
|
+
const buildTableOperationsFromTable = (tableName, table, client) => {
|
|
414
|
+
const def = tableDefinition(table);
|
|
415
|
+
return {
|
|
416
|
+
...buildTableOperations(tableName, client),
|
|
417
|
+
create: (options) => idempotentCreate(client
|
|
418
|
+
.createTable({
|
|
419
|
+
TableName: tableName,
|
|
420
|
+
BillingMode: options?.billingMode ?? "PAY_PER_REQUEST",
|
|
421
|
+
...def,
|
|
422
|
+
})
|
|
423
|
+
.pipe(Effect.asVoid)),
|
|
424
|
+
};
|
|
425
|
+
};
|
|
426
|
+
const buildTableOperations = (tableName, client) => ({
|
|
427
|
+
create: (options) => idempotentCreate(client
|
|
428
|
+
.createTable({
|
|
429
|
+
TableName: tableName,
|
|
430
|
+
BillingMode: options?.billingMode ?? "PAY_PER_REQUEST",
|
|
431
|
+
KeySchema: [
|
|
432
|
+
{ AttributeName: "pk", KeyType: "HASH" },
|
|
433
|
+
{ AttributeName: "sk", KeyType: "RANGE" },
|
|
434
|
+
],
|
|
435
|
+
AttributeDefinitions: [
|
|
436
|
+
{ AttributeName: "pk", AttributeType: "S" },
|
|
437
|
+
{ AttributeName: "sk", AttributeType: "S" },
|
|
438
|
+
],
|
|
439
|
+
})
|
|
440
|
+
.pipe(Effect.asVoid)),
|
|
441
|
+
delete: () => client.deleteTable({ TableName: tableName }).pipe(Effect.asVoid),
|
|
442
|
+
describe: () => client.describeTable({ TableName: tableName }),
|
|
443
|
+
update: (input) => client
|
|
444
|
+
.updateTable({ ...input, TableName: tableName })
|
|
445
|
+
.pipe(Effect.asVoid),
|
|
446
|
+
backup: (name) => client.createBackup({ TableName: tableName, BackupName: name }),
|
|
447
|
+
listBackups: () => client.listBackups({ TableName: tableName }),
|
|
448
|
+
restoreFromBackup: (backupArn) => client.restoreTableFromBackup({
|
|
449
|
+
TargetTableName: `${tableName}-restore`,
|
|
450
|
+
BackupArn: backupArn,
|
|
451
|
+
}),
|
|
452
|
+
enablePointInTimeRecovery: () => client
|
|
453
|
+
.updateContinuousBackups({
|
|
454
|
+
TableName: tableName,
|
|
455
|
+
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
|
|
456
|
+
})
|
|
457
|
+
.pipe(Effect.asVoid),
|
|
458
|
+
disablePointInTimeRecovery: () => client
|
|
459
|
+
.updateContinuousBackups({
|
|
460
|
+
TableName: tableName,
|
|
461
|
+
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: false },
|
|
462
|
+
})
|
|
463
|
+
.pipe(Effect.asVoid),
|
|
464
|
+
restoreToPointInTime: (timestamp) => client.restoreTableToPointInTime({
|
|
465
|
+
SourceTableName: tableName,
|
|
466
|
+
TargetTableName: `${tableName}-pitr-restore`,
|
|
467
|
+
RestoreDateTime: timestamp,
|
|
468
|
+
}),
|
|
469
|
+
exportToS3: (s3Bucket, options) => client.exportTableToPointInTime({
|
|
470
|
+
TableArn: tableName, // Will need actual ARN — use describe first
|
|
471
|
+
S3Bucket: s3Bucket,
|
|
472
|
+
S3Prefix: options?.s3Prefix,
|
|
473
|
+
ExportFormat: options?.exportFormat,
|
|
474
|
+
}),
|
|
475
|
+
enableTTL: (attributeName) => client
|
|
476
|
+
.updateTimeToLive({
|
|
477
|
+
TableName: tableName,
|
|
478
|
+
TimeToLiveSpecification: {
|
|
479
|
+
Enabled: true,
|
|
480
|
+
AttributeName: attributeName,
|
|
481
|
+
},
|
|
482
|
+
})
|
|
483
|
+
.pipe(Effect.asVoid),
|
|
484
|
+
disableTTL: (attributeName) => client
|
|
485
|
+
.updateTimeToLive({
|
|
486
|
+
TableName: tableName,
|
|
487
|
+
TimeToLiveSpecification: {
|
|
488
|
+
Enabled: false,
|
|
489
|
+
AttributeName: attributeName,
|
|
490
|
+
},
|
|
491
|
+
})
|
|
492
|
+
.pipe(Effect.asVoid),
|
|
493
|
+
describeTTL: () => client.describeTimeToLive({ TableName: tableName }),
|
|
494
|
+
tag: (tags) => client
|
|
495
|
+
.tagResource({
|
|
496
|
+
ResourceArn: tableName, // Will need actual ARN
|
|
497
|
+
Tags: Object.entries(tags).map(([Key, Value]) => ({ Key, Value })),
|
|
498
|
+
})
|
|
499
|
+
.pipe(Effect.asVoid),
|
|
500
|
+
untag: (tagKeys) => client
|
|
501
|
+
.untagResource({
|
|
502
|
+
ResourceArn: tableName, // Will need actual ARN
|
|
503
|
+
TagKeys: [...tagKeys],
|
|
504
|
+
})
|
|
505
|
+
.pipe(Effect.asVoid),
|
|
506
|
+
tags: () => client.listTagsOfResource({ ResourceArn: tableName }),
|
|
507
|
+
});
|
|
508
|
+
/** @internal Classify an AWS SDK error into a specific tagged error type. */
|
|
509
|
+
const classifyError = (operation) => (cause) => {
|
|
510
|
+
if (isAwsThrottling(cause))
|
|
511
|
+
return new ThrottlingError({ operation, cause });
|
|
512
|
+
if (isAwsValidationError(cause))
|
|
513
|
+
return new DynamoValidationError({ operation, cause });
|
|
514
|
+
if (isAwsInternalServerError(cause))
|
|
515
|
+
return new InternalServerError({ operation, cause });
|
|
516
|
+
if (isAwsResourceNotFound(cause))
|
|
517
|
+
return new ResourceNotFoundError({ operation, cause });
|
|
518
|
+
return new DynamoError({ operation, cause });
|
|
519
|
+
};
|
|
520
|
+
/** @internal Build the DynamoClient service implementation from resolved config values. */
|
|
521
|
+
const buildService = (region, endpoint, credentials) => Effect.gen(function* () {
|
|
522
|
+
const clientConfig = { region };
|
|
523
|
+
if (endpoint !== undefined)
|
|
524
|
+
clientConfig.endpoint = endpoint;
|
|
525
|
+
if (credentials !== undefined)
|
|
526
|
+
clientConfig.credentials = credentials;
|
|
527
|
+
const client = yield* Effect.acquireRelease(Effect.sync(() => new DynamoDBClient(clientConfig)), (c) => Effect.sync(() => c.destroy()));
|
|
528
|
+
return {
|
|
529
|
+
createTable: (input) => Effect.tryPromise({
|
|
530
|
+
try: () => client.send(new CreateTableCommand(input)),
|
|
531
|
+
catch: classifyError("CreateTable"),
|
|
532
|
+
}),
|
|
533
|
+
deleteTable: (input) => Effect.tryPromise({
|
|
534
|
+
try: () => client.send(new DeleteTableCommand(input)),
|
|
535
|
+
catch: classifyError("DeleteTable"),
|
|
536
|
+
}),
|
|
537
|
+
describeTable: (input) => Effect.tryPromise({
|
|
538
|
+
try: () => client.send(new DescribeTableCommand(input)),
|
|
539
|
+
catch: classifyError("DescribeTable"),
|
|
540
|
+
}),
|
|
541
|
+
putItem: (input) => Effect.tryPromise({
|
|
542
|
+
try: () => client.send(new PutItemCommand(input)),
|
|
543
|
+
catch: classifyError("PutItem"),
|
|
544
|
+
}),
|
|
545
|
+
getItem: (input) => Effect.tryPromise({
|
|
546
|
+
try: () => client.send(new GetItemCommand(input)),
|
|
547
|
+
catch: classifyError("GetItem"),
|
|
548
|
+
}),
|
|
549
|
+
deleteItem: (input) => Effect.tryPromise({
|
|
550
|
+
try: () => client.send(new DeleteItemCommand(input)),
|
|
551
|
+
catch: classifyError("DeleteItem"),
|
|
552
|
+
}),
|
|
553
|
+
updateItem: (input) => Effect.tryPromise({
|
|
554
|
+
try: () => client.send(new UpdateItemCommand(input)),
|
|
555
|
+
catch: classifyError("UpdateItem"),
|
|
556
|
+
}),
|
|
557
|
+
query: (input) => Effect.tryPromise({
|
|
558
|
+
try: () => client.send(new QueryCommand(input)),
|
|
559
|
+
catch: classifyError("Query"),
|
|
560
|
+
}),
|
|
561
|
+
scan: (input) => Effect.tryPromise({
|
|
562
|
+
try: () => client.send(new ScanCommand(input)),
|
|
563
|
+
catch: classifyError("Scan"),
|
|
564
|
+
}),
|
|
565
|
+
batchGetItem: (input) => Effect.tryPromise({
|
|
566
|
+
try: () => client.send(new BatchGetItemCommand(input)),
|
|
567
|
+
catch: classifyError("BatchGetItem"),
|
|
568
|
+
}),
|
|
569
|
+
batchWriteItem: (input) => Effect.tryPromise({
|
|
570
|
+
try: () => client.send(new BatchWriteItemCommand(input)),
|
|
571
|
+
catch: classifyError("BatchWriteItem"),
|
|
572
|
+
}),
|
|
573
|
+
transactGetItems: (input) => Effect.tryPromise({
|
|
574
|
+
try: () => client.send(new TransactGetItemsCommand(input)),
|
|
575
|
+
catch: classifyError("TransactGetItems"),
|
|
576
|
+
}),
|
|
577
|
+
transactWriteItems: (input) => Effect.tryPromise({
|
|
578
|
+
try: () => client.send(new TransactWriteItemsCommand(input)),
|
|
579
|
+
catch: classifyError("TransactWriteItems"),
|
|
580
|
+
}),
|
|
581
|
+
// --- Table management ---
|
|
582
|
+
updateTable: (input) => Effect.tryPromise({
|
|
583
|
+
try: () => client.send(new UpdateTableCommand(input)),
|
|
584
|
+
catch: classifyError("UpdateTable"),
|
|
585
|
+
}),
|
|
586
|
+
listTables: (input) => Effect.tryPromise({
|
|
587
|
+
try: () => client.send(new ListTablesCommand(input)),
|
|
588
|
+
catch: classifyError("ListTables"),
|
|
589
|
+
}),
|
|
590
|
+
// --- Backup ---
|
|
591
|
+
createBackup: (input) => Effect.tryPromise({
|
|
592
|
+
try: () => client.send(new CreateBackupCommand(input)),
|
|
593
|
+
catch: classifyError("CreateBackup"),
|
|
594
|
+
}),
|
|
595
|
+
deleteBackup: (input) => Effect.tryPromise({
|
|
596
|
+
try: () => client.send(new DeleteBackupCommand(input)),
|
|
597
|
+
catch: classifyError("DeleteBackup"),
|
|
598
|
+
}),
|
|
599
|
+
listBackups: (input) => Effect.tryPromise({
|
|
600
|
+
try: () => client.send(new ListBackupsCommand(input)),
|
|
601
|
+
catch: classifyError("ListBackups"),
|
|
602
|
+
}),
|
|
603
|
+
restoreTableFromBackup: (input) => Effect.tryPromise({
|
|
604
|
+
try: () => client.send(new RestoreTableFromBackupCommand(input)),
|
|
605
|
+
catch: classifyError("RestoreTableFromBackup"),
|
|
606
|
+
}),
|
|
607
|
+
// --- PITR ---
|
|
608
|
+
describeContinuousBackups: (input) => Effect.tryPromise({
|
|
609
|
+
try: () => client.send(new DescribeContinuousBackupsCommand(input)),
|
|
610
|
+
catch: classifyError("DescribeContinuousBackups"),
|
|
611
|
+
}),
|
|
612
|
+
updateContinuousBackups: (input) => Effect.tryPromise({
|
|
613
|
+
try: () => client.send(new UpdateContinuousBackupsCommand(input)),
|
|
614
|
+
catch: classifyError("UpdateContinuousBackups"),
|
|
615
|
+
}),
|
|
616
|
+
restoreTableToPointInTime: (input) => Effect.tryPromise({
|
|
617
|
+
try: () => client.send(new RestoreTableToPointInTimeCommand(input)),
|
|
618
|
+
catch: classifyError("RestoreTableToPointInTime"),
|
|
619
|
+
}),
|
|
620
|
+
// --- Export ---
|
|
621
|
+
exportTableToPointInTime: (input) => Effect.tryPromise({
|
|
622
|
+
try: () => client.send(new ExportTableToPointInTimeCommand(input)),
|
|
623
|
+
catch: classifyError("ExportTableToPointInTime"),
|
|
624
|
+
}),
|
|
625
|
+
describeExport: (input) => Effect.tryPromise({
|
|
626
|
+
try: () => client.send(new DescribeExportCommand(input)),
|
|
627
|
+
catch: classifyError("DescribeExport"),
|
|
628
|
+
}),
|
|
629
|
+
// --- TTL ---
|
|
630
|
+
updateTimeToLive: (input) => Effect.tryPromise({
|
|
631
|
+
try: () => client.send(new UpdateTimeToLiveCommand(input)),
|
|
632
|
+
catch: classifyError("UpdateTimeToLive"),
|
|
633
|
+
}),
|
|
634
|
+
describeTimeToLive: (input) => Effect.tryPromise({
|
|
635
|
+
try: () => client.send(new DescribeTimeToLiveCommand(input)),
|
|
636
|
+
catch: classifyError("DescribeTimeToLive"),
|
|
637
|
+
}),
|
|
638
|
+
// --- Tags ---
|
|
639
|
+
tagResource: (input) => Effect.tryPromise({
|
|
640
|
+
try: () => client.send(new TagResourceCommand(input)),
|
|
641
|
+
catch: classifyError("TagResource"),
|
|
642
|
+
}),
|
|
643
|
+
untagResource: (input) => Effect.tryPromise({
|
|
644
|
+
try: () => client.send(new UntagResourceCommand(input)),
|
|
645
|
+
catch: classifyError("UntagResource"),
|
|
646
|
+
}),
|
|
647
|
+
listTagsOfResource: (input) => Effect.tryPromise({
|
|
648
|
+
try: () => client.send(new ListTagsOfResourceCommand(input)),
|
|
649
|
+
catch: classifyError("ListTagsOfResource"),
|
|
650
|
+
}),
|
|
651
|
+
};
|
|
652
|
+
});
|
|
653
|
+
//# sourceMappingURL=DynamoClient.js.map
|