effect-dynamodb 1.22.0 → 1.23.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/dist/Aggregate.d.ts +9 -4
- package/dist/Aggregate.d.ts.map +1 -1
- package/dist/Aggregate.js +644 -219
- package/dist/Aggregate.js.map +1 -1
- package/dist/Batch.d.ts +7 -0
- package/dist/Batch.d.ts.map +1 -1
- package/dist/Batch.js +132 -7
- package/dist/Batch.js.map +1 -1
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +13 -31
- package/dist/Collection.js.map +1 -1
- package/dist/DynamoClient.d.ts +40 -3
- package/dist/DynamoClient.d.ts.map +1 -1
- package/dist/DynamoClient.js +70 -19
- package/dist/DynamoClient.js.map +1 -1
- package/dist/Entity.d.ts +138 -55
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js +3061 -626
- package/dist/Entity.js.map +1 -1
- package/dist/EventStore.d.ts +15 -6
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +169 -148
- package/dist/EventStore.js.map +1 -1
- package/dist/Query.d.ts +75 -3
- package/dist/Query.d.ts.map +1 -1
- package/dist/Query.js +228 -30
- package/dist/Query.js.map +1 -1
- package/dist/Transaction.d.ts +13 -2
- package/dist/Transaction.d.ts.map +1 -1
- package/dist/Transaction.js +48 -38
- package/dist/Transaction.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/BoundCrud.d.ts +31 -20
- package/dist/internal/BoundCrud.d.ts.map +1 -1
- package/dist/internal/BoundCrud.js +1 -1
- package/dist/internal/BoundCrud.js.map +1 -1
- package/dist/internal/BoundQuery.d.ts +47 -2
- package/dist/internal/BoundQuery.d.ts.map +1 -1
- package/dist/internal/BoundQuery.js +182 -18
- package/dist/internal/BoundQuery.js.map +1 -1
- package/dist/internal/EntityCombinators.d.ts +32 -10
- package/dist/internal/EntityCombinators.d.ts.map +1 -1
- package/dist/internal/EntityCombinators.js +16 -7
- package/dist/internal/EntityCombinators.js.map +1 -1
- package/dist/internal/EntityOps.d.ts +44 -10
- package/dist/internal/EntityOps.d.ts.map +1 -1
- package/dist/internal/EntityOps.js +48 -5
- package/dist/internal/EntityOps.js.map +1 -1
- package/dist/internal/Expr.d.ts +23 -0
- package/dist/internal/Expr.d.ts.map +1 -1
- package/dist/internal/Expr.js +84 -9
- package/dist/internal/Expr.js.map +1 -1
- package/dist/internal/ItemSize.d.ts +38 -0
- package/dist/internal/ItemSize.d.ts.map +1 -0
- package/dist/internal/ItemSize.js +62 -0
- package/dist/internal/ItemSize.js.map +1 -0
- package/dist/internal/TransactWriteOps.d.ts +112 -19
- package/dist/internal/TransactWriteOps.d.ts.map +1 -1
- package/dist/internal/TransactWriteOps.js +210 -33
- package/dist/internal/TransactWriteOps.js.map +1 -1
- package/dist/internal/TransactableOps.d.ts +33 -14
- package/dist/internal/TransactableOps.d.ts.map +1 -1
- package/dist/internal/TransactableOps.js +46 -19
- package/dist/internal/TransactableOps.js.map +1 -1
- package/package.json +2 -2
package/dist/Query.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Implements Pipeable for ergonomic composition.
|
|
9
9
|
*/
|
|
10
10
|
import type { AttributeValue } from "@aws-sdk/client-dynamodb";
|
|
11
|
-
import
|
|
11
|
+
import { ValidationError } from "@effect-dynamodb/schema/Errors.js";
|
|
12
12
|
import { Effect, Pipeable, Stream } from "effect";
|
|
13
13
|
import { DynamoClient, type DynamoClientError } from "./DynamoClient.js";
|
|
14
14
|
import { type Expr } from "./internal/Expr.js";
|
|
@@ -55,6 +55,11 @@ interface QueryState {
|
|
|
55
55
|
readonly maxPagesValue: number | undefined;
|
|
56
56
|
readonly scanForward: boolean;
|
|
57
57
|
readonly consistentRead: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The index is a GSI, which DynamoDB only reads eventually consistently:
|
|
60
|
+
* `consistentRead` is refused before sending (#133).
|
|
61
|
+
*/
|
|
62
|
+
readonly globalIndex: boolean;
|
|
58
63
|
readonly ignoreOwnershipFlag: boolean;
|
|
59
64
|
readonly exclusiveStartKey: Record<string, AttributeValue> | undefined;
|
|
60
65
|
readonly isScan: boolean;
|
|
@@ -69,6 +74,42 @@ interface QueryState {
|
|
|
69
74
|
* request over-reads and the surplus is discarded (see {@link limit}).
|
|
70
75
|
*/
|
|
71
76
|
readonly keyFields: ReadonlyArray<string> | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Which rows are items (#133). An entity's version snapshots, soft-delete
|
|
79
|
+
* tombstones and time-series events keep its `__edd_e__`, so the ownership
|
|
80
|
+
* filter alone admits them to a query of the table's primary key, or a
|
|
81
|
+
* scan. Rows that fail it are dropped as they arrive, before they are
|
|
82
|
+
* decoded or counted — see {@link isExcludedRow}. (Client-side for scans
|
|
83
|
+
* too: a `FilterExpression` saves no read capacity, and no expression
|
|
84
|
+
* tells a live row from history as exactly.)
|
|
85
|
+
*/
|
|
86
|
+
readonly liveRows: LiveRows | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Run once at the start of each terminal, against the resolved table: may
|
|
89
|
+
* swap the sort key `begins_with` operand and add a row filter (`keep`)
|
|
90
|
+
* applied, like {@link liveRows}, to rows before they are decoded.
|
|
91
|
+
* An entity's `versions` uses it to read history written by an earlier
|
|
92
|
+
* release alongside its own (#133).
|
|
93
|
+
*/
|
|
94
|
+
readonly prepare: QueryPrepare | undefined;
|
|
95
|
+
/** @internal Set by {@link prepare}: rows outside it are dropped before decode. */
|
|
96
|
+
readonly keepRow: ((row: Record<string, AttributeValue>) => boolean) | undefined;
|
|
97
|
+
}
|
|
98
|
+
/** @internal See {@link QueryState.prepare}. */
|
|
99
|
+
export type QueryPrepare = (tableName: string) => Effect.Effect<{
|
|
100
|
+
/** Replace a `begins_with` sort key condition on `from` with one on `to`. */
|
|
101
|
+
readonly replaceBeginsWith?: {
|
|
102
|
+
readonly from: string;
|
|
103
|
+
readonly to: string;
|
|
104
|
+
} | undefined;
|
|
105
|
+
readonly keep?: ((row: Record<string, AttributeValue>) => boolean) | undefined;
|
|
106
|
+
}, DynamoClientError, DynamoClient>;
|
|
107
|
+
/** @internal See {@link QueryState.liveRows}. */
|
|
108
|
+
export interface LiveRows {
|
|
109
|
+
/** Whether a raw row is an item, rather than an entity's history. */
|
|
110
|
+
readonly isLive: (row: Record<string, AttributeValue>) => boolean;
|
|
111
|
+
/** The attributes `isLive` reads — kept in a request that projects. */
|
|
112
|
+
readonly reads: ReadonlyArray<string>;
|
|
72
113
|
}
|
|
73
114
|
declare const QueryTypeId: unique symbol;
|
|
74
115
|
export type QueryTypeId = typeof QueryTypeId;
|
|
@@ -96,9 +137,15 @@ export declare const make: <A>(config: {
|
|
|
96
137
|
readonly skField: string | undefined;
|
|
97
138
|
readonly entityTypes: ReadonlyArray<string>;
|
|
98
139
|
readonly decoder: (raw: Record<string, unknown>) => Effect.Effect<A, ValidationError>;
|
|
140
|
+
/** `indexName` names a GSI (see {@link QueryState.globalIndex}). */
|
|
141
|
+
readonly globalIndex?: boolean | undefined;
|
|
99
142
|
readonly resolveTableName?: Effect.Effect<string, never, any> | undefined;
|
|
100
143
|
/** Index key + table key attribute names (used to rebuild cursors). */
|
|
101
144
|
readonly keyFields?: ReadonlyArray<string | undefined> | undefined;
|
|
145
|
+
/** Which rows are items (see {@link QueryState.liveRows}). */
|
|
146
|
+
readonly liveRows?: LiveRows | undefined;
|
|
147
|
+
/** See {@link QueryState.prepare}. */
|
|
148
|
+
readonly prepare?: QueryPrepare | undefined;
|
|
102
149
|
}) => Query<A>;
|
|
103
150
|
/**
|
|
104
151
|
* Create a scan-mode Query. Shares all combinators and terminals with Query
|
|
@@ -110,9 +157,15 @@ export declare const makeScan: <A>(config: {
|
|
|
110
157
|
readonly indexName: string | undefined;
|
|
111
158
|
readonly entityTypes: ReadonlyArray<string>;
|
|
112
159
|
readonly decoder: (raw: Record<string, unknown>) => Effect.Effect<A, ValidationError>;
|
|
160
|
+
/** `indexName` names a GSI (see {@link QueryState.globalIndex}). */
|
|
161
|
+
readonly globalIndex?: boolean | undefined;
|
|
113
162
|
readonly resolveTableName?: Effect.Effect<string, never, any> | undefined;
|
|
114
163
|
/** Index key + table key attribute names (used to rebuild cursors). */
|
|
115
164
|
readonly keyFields?: ReadonlyArray<string | undefined> | undefined;
|
|
165
|
+
/** Which rows are items (see {@link QueryState.liveRows}). */
|
|
166
|
+
readonly liveRows?: LiveRows | undefined;
|
|
167
|
+
/** See {@link QueryState.prepare}. */
|
|
168
|
+
readonly prepare?: QueryPrepare | undefined;
|
|
116
169
|
}) => Query<A>;
|
|
117
170
|
/**
|
|
118
171
|
* Add a sort key condition to the query.
|
|
@@ -155,7 +208,9 @@ export declare const pageSize: {
|
|
|
155
208
|
*/
|
|
156
209
|
export declare const reverse: <A>(self: Query<A>) => Query<A>;
|
|
157
210
|
/**
|
|
158
|
-
* Enable consistent reads for this query (or scan).
|
|
211
|
+
* Enable consistent reads for this query (or scan). Refused with a
|
|
212
|
+
* `ValidationError` when it runs on a global secondary index, which DynamoDB
|
|
213
|
+
* reads only eventually consistently.
|
|
159
214
|
*/
|
|
160
215
|
export declare const consistentRead: {
|
|
161
216
|
(): <A>(self: Query<A>) => Query<A>;
|
|
@@ -194,6 +249,20 @@ export declare const select: {
|
|
|
194
249
|
(attributes: ReadonlyArray<string>): <A>(self: Query<A>) => Query<Record<string, unknown>>;
|
|
195
250
|
<A>(self: Query<A>, attributes: ReadonlyArray<string>): Query<Record<string, unknown>>;
|
|
196
251
|
};
|
|
252
|
+
/**
|
|
253
|
+
* @internal A projection of stored attribute paths, decoded by `decode`: what
|
|
254
|
+
* `select` becomes when the names a caller asks for are not the names stored
|
|
255
|
+
* (#133). Always numbered placeholders (`#proj0`), whatever characters the
|
|
256
|
+
* stored names hold.
|
|
257
|
+
*/
|
|
258
|
+
export declare const selectProjected: <A>(self: Query<A>, storedPaths: ReadonlyArray<ReadonlyArray<string | number>>, decode: (raw: Record<string, unknown>) => unknown) => Query<Record<string, unknown>>;
|
|
259
|
+
/**
|
|
260
|
+
* @internal `select` / `selectPaths` of DOMAIN field names on an entity that
|
|
261
|
+
* stores some under other names (`DynamoModel.configure(..., { field })`): the
|
|
262
|
+
* projection names the stored attributes, and each item is handed back keyed
|
|
263
|
+
* by the domain names asked for (#133).
|
|
264
|
+
*/
|
|
265
|
+
export declare const selectRenamed: <A>(self: Query<A>, paths: ReadonlyArray<ReadonlyArray<string | number>>, resolveDbName: (domainName: string) => string) => Query<Record<string, unknown>>;
|
|
197
266
|
/**
|
|
198
267
|
* Add an Expr-based filter expression to the query.
|
|
199
268
|
* Multiple filterExpr calls are ANDed together.
|
|
@@ -304,8 +373,11 @@ export declare const count: <A>(self: Query<A>) => Effect.Effect<number, DynamoC
|
|
|
304
373
|
/**
|
|
305
374
|
* Return the built DynamoDB command input without executing.
|
|
306
375
|
* Useful for debugging, logging, or passing to DynamoClient directly.
|
|
376
|
+
* Fails with a `ValidationError` for a filter the query would refuse to send
|
|
377
|
+
* (an empty part under `or` / `not`, an `or()` with no parts, an `isIn` with
|
|
378
|
+
* no values).
|
|
307
379
|
*/
|
|
308
|
-
export declare const asParams: <A>(self: Query<A>) => Effect.Effect<Record<string, unknown>,
|
|
380
|
+
export declare const asParams: <A>(self: Query<A>) => Effect.Effect<Record<string, unknown>, ValidationError, any>;
|
|
309
381
|
export declare const isQuery: (u: unknown) => u is Query<unknown>;
|
|
310
382
|
export {};
|
|
311
383
|
//# sourceMappingURL=Query.d.ts.map
|
package/dist/Query.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Query.d.ts","sourceRoot":"","sources":["../src/Query.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,
|
|
1
|
+
{"version":3,"file":"Query.d.ts","sourceRoot":"","sources":["../src/Query.ts"],"names":[],"mappings":"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"}
|
package/dist/Query.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Implements Pipeable for ergonomic composition.
|
|
9
9
|
*/
|
|
10
|
+
import { ValidationError } from "@effect-dynamodb/schema/Errors.js";
|
|
10
11
|
import * as Projection from "@effect-dynamodb/schema/Projection.js";
|
|
11
12
|
import { Effect, Function, Option, Pipeable, Stream } from "effect";
|
|
12
13
|
import { DynamoClient } from "./DynamoClient.js";
|
|
13
|
-
import { compileExpr } from "./internal/Expr.js";
|
|
14
|
+
import { compileExpr, ExprTag, emptyPartProblem, isEmptyExpr } from "./internal/Expr.js";
|
|
14
15
|
import { compilePath } from "./internal/PathBuilder.js";
|
|
15
16
|
import { fromAttributeMap, toAttributeValue } from "./Marshaller.js";
|
|
16
17
|
/** @internal Dedupe + drop absent entries from a caller-supplied key field list. */
|
|
@@ -42,6 +43,9 @@ class QueryImpl {
|
|
|
42
43
|
// Constructor (internal — used by Entity and Collection)
|
|
43
44
|
// ---------------------------------------------------------------------------
|
|
44
45
|
export const make = (config) => new QueryImpl({
|
|
46
|
+
liveRows: config.liveRows,
|
|
47
|
+
prepare: config.prepare,
|
|
48
|
+
keepRow: undefined,
|
|
45
49
|
tableName: config.tableName,
|
|
46
50
|
indexName: config.indexName,
|
|
47
51
|
pkField: config.pkField,
|
|
@@ -56,6 +60,7 @@ export const make = (config) => new QueryImpl({
|
|
|
56
60
|
maxPagesValue: undefined,
|
|
57
61
|
scanForward: true,
|
|
58
62
|
consistentRead: false,
|
|
63
|
+
globalIndex: config.globalIndex ?? false,
|
|
59
64
|
ignoreOwnershipFlag: false,
|
|
60
65
|
exclusiveStartKey: undefined,
|
|
61
66
|
isScan: false,
|
|
@@ -71,6 +76,9 @@ export const make = (config) => new QueryImpl({
|
|
|
71
76
|
* No key condition — only filter expressions and entity type filter.
|
|
72
77
|
*/
|
|
73
78
|
export const makeScan = (config) => new QueryImpl({
|
|
79
|
+
liveRows: config.liveRows,
|
|
80
|
+
prepare: config.prepare,
|
|
81
|
+
keepRow: undefined,
|
|
74
82
|
tableName: config.tableName,
|
|
75
83
|
indexName: config.indexName,
|
|
76
84
|
pkField: "",
|
|
@@ -85,6 +93,7 @@ export const makeScan = (config) => new QueryImpl({
|
|
|
85
93
|
maxPagesValue: undefined,
|
|
86
94
|
scanForward: true,
|
|
87
95
|
consistentRead: false,
|
|
96
|
+
globalIndex: config.globalIndex ?? false,
|
|
88
97
|
ignoreOwnershipFlag: false,
|
|
89
98
|
exclusiveStartKey: undefined,
|
|
90
99
|
isScan: true,
|
|
@@ -151,7 +160,9 @@ export const reverse = (self) => new QueryImpl({
|
|
|
151
160
|
scanForward: false,
|
|
152
161
|
});
|
|
153
162
|
/**
|
|
154
|
-
* Enable consistent reads for this query (or scan).
|
|
163
|
+
* Enable consistent reads for this query (or scan). Refused with a
|
|
164
|
+
* `ValidationError` when it runs on a global secondary index, which DynamoDB
|
|
165
|
+
* reads only eventually consistently.
|
|
155
166
|
*/
|
|
156
167
|
export const consistentRead = Function.dual((args) => isQuery(args[0]), (self) => new QueryImpl({
|
|
157
168
|
...self._state,
|
|
@@ -201,16 +212,52 @@ export const select = Function.dual(2, (self, attributes) => {
|
|
|
201
212
|
decoder: (raw) => Effect.succeed(raw),
|
|
202
213
|
});
|
|
203
214
|
});
|
|
215
|
+
/**
|
|
216
|
+
* @internal A projection of stored attribute paths, decoded by `decode`: what
|
|
217
|
+
* `select` becomes when the names a caller asks for are not the names stored
|
|
218
|
+
* (#133). Always numbered placeholders (`#proj0`), whatever characters the
|
|
219
|
+
* stored names hold.
|
|
220
|
+
*/
|
|
221
|
+
export const selectProjected = (self, storedPaths, decode) => {
|
|
222
|
+
if (self._state.predicates.length > 0) {
|
|
223
|
+
throw new Error(rejectPredicateWithProjection("select() after filterBy()"));
|
|
224
|
+
}
|
|
225
|
+
return new QueryImpl({
|
|
226
|
+
...self._state,
|
|
227
|
+
projection: undefined,
|
|
228
|
+
projectionPaths: storedPaths,
|
|
229
|
+
decoder: (raw) => Effect.succeed(decode(raw)),
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* @internal `select` / `selectPaths` of DOMAIN field names on an entity that
|
|
234
|
+
* stores some under other names (`DynamoModel.configure(..., { field })`): the
|
|
235
|
+
* projection names the stored attributes, and each item is handed back keyed
|
|
236
|
+
* by the domain names asked for (#133).
|
|
237
|
+
*/
|
|
238
|
+
export const selectRenamed = (self, paths, resolveDbName) => {
|
|
239
|
+
const heads = [...new Set(paths.map((path) => String(path[0])))];
|
|
240
|
+
return selectProjected(self, paths.map((path) => [resolveDbName(String(path[0])), ...path.slice(1)]), (raw) => {
|
|
241
|
+
const item = {};
|
|
242
|
+
for (const head of heads) {
|
|
243
|
+
const value = raw[resolveDbName(head)];
|
|
244
|
+
if (value !== undefined)
|
|
245
|
+
item[head] = value;
|
|
246
|
+
}
|
|
247
|
+
return item;
|
|
248
|
+
});
|
|
249
|
+
};
|
|
204
250
|
/**
|
|
205
251
|
* Add an Expr-based filter expression to the query.
|
|
206
252
|
* Multiple filterExpr calls are ANDed together.
|
|
207
253
|
*/
|
|
208
254
|
export const filterExpr = Function.dual(2, (self, expr) => {
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
|
|
255
|
+
// A filter that asserts nothing (`.filter({})`, `and()`) is no filter: it is
|
|
256
|
+
// dropped here, never sent as `FilterExpression: ""`, which DynamoDB
|
|
257
|
+
// rejects (#133). An empty part anywhere else — under `or` or `not`, or an
|
|
258
|
+
// `or()` with no parts — is kept, and refused when the query runs
|
|
259
|
+
// (`emptyPartProblem`).
|
|
260
|
+
if (isEmptyExpr(expr))
|
|
214
261
|
return self;
|
|
215
262
|
return new QueryImpl({
|
|
216
263
|
...self._state,
|
|
@@ -308,10 +355,18 @@ const buildFilterClauses = (state) => {
|
|
|
308
355
|
values[`:et${i}`] = toAttributeValue(et);
|
|
309
356
|
});
|
|
310
357
|
}
|
|
311
|
-
// Expr-based filters (compiled from Entity.filter() callback/shorthand API)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
358
|
+
// Expr-based filters (compiled from Entity.filter() callback/shorthand API).
|
|
359
|
+
// Compiled as ONE expression: each compile numbers its placeholders from
|
|
360
|
+
// zero, so two compiled apart both wrote `#e0` and the second overwrote the
|
|
361
|
+
// first's name (#133).
|
|
362
|
+
if (state.exprFilters.length > 0) {
|
|
363
|
+
const compiled = compileExpr(state.exprFilters.length === 1
|
|
364
|
+
? state.exprFilters[0]
|
|
365
|
+
: { [ExprTag]: ExprTag, _tag: "and", exprs: state.exprFilters });
|
|
366
|
+
// Parenthesised beside the ownership clause: a top-level `OR` would
|
|
367
|
+
// otherwise bind looser than the `AND` and admit other entities' rows
|
|
368
|
+
// (`#eddE IN (:et0) AND a OR b`).
|
|
369
|
+
filterClauses.push(filterClauses.length > 0 ? `(${compiled.expression})` : compiled.expression);
|
|
315
370
|
Object.assign(names, compiled.names);
|
|
316
371
|
Object.assign(values, compiled.values);
|
|
317
372
|
}
|
|
@@ -419,6 +474,8 @@ const buildDynamoCommand = (state, tableName, overrides, extraProjectionFields =
|
|
|
419
474
|
// ---------------------------------------------------------------------------
|
|
420
475
|
// Internal: limit / pageSize execution helpers
|
|
421
476
|
// ---------------------------------------------------------------------------
|
|
477
|
+
/** The largest `Limit` a growing request asks for: DynamoDB stops at 1 MB anyway. */
|
|
478
|
+
const MAX_GROWN_LIMIT = 100_000;
|
|
422
479
|
/**
|
|
423
480
|
* @internal DynamoDB `Limit` for the next request.
|
|
424
481
|
*
|
|
@@ -432,15 +489,93 @@ const buildDynamoCommand = (state, tableName, overrides, extraProjectionFields =
|
|
|
432
489
|
* `pageSize` — or, unset, a natural (1 MB) page.
|
|
433
490
|
* - A client-side predicate ({@link filterBy}) rejects rows even later, after
|
|
434
491
|
* decode, so it disqualifies the budget for exactly the same reason.
|
|
492
|
+
* - Rows dropped as history ({@link QueryState.liveRows}, a prepared `keep`)
|
|
493
|
+
* are not: they can't be skipped server-side, and a run of them (an item's
|
|
494
|
+
* versions, its tombstones) can far outnumber the items. The first request
|
|
495
|
+
* asks for `limit`, and each later one twice the last — so a run of `n`
|
|
496
|
+
* history rows costs about `log2(n / limit)` requests, not `n / limit` — and
|
|
497
|
+
* the surplus past `limit` is discarded, the cursor rebuilt from the last
|
|
498
|
+
* item returned (#133). `pageSize`, when set, is used as is.
|
|
499
|
+
*
|
|
500
|
+
* `page` is the request's 1-based number within the terminal.
|
|
435
501
|
*/
|
|
436
|
-
const computeRequestLimit = (state, remaining) => {
|
|
502
|
+
const computeRequestLimit = (state, remaining, page = 1) => {
|
|
437
503
|
const pageSize = state.pageSizeValue;
|
|
438
504
|
if (remaining === undefined)
|
|
439
505
|
return pageSize;
|
|
440
506
|
if (state.exprFilters.length > 0 || state.predicates.length > 0)
|
|
441
507
|
return pageSize;
|
|
508
|
+
if (dropsRows(state)) {
|
|
509
|
+
if (pageSize !== undefined)
|
|
510
|
+
return pageSize;
|
|
511
|
+
const limit = state.limitValue ?? remaining;
|
|
512
|
+
return Math.min(limit * 2 ** (page - 1), MAX_GROWN_LIMIT);
|
|
513
|
+
}
|
|
442
514
|
return pageSize === undefined ? remaining : Math.min(pageSize, remaining);
|
|
443
515
|
};
|
|
516
|
+
/**
|
|
517
|
+
* @internal Whether the query drops rows client-side (#133): like a predicate,
|
|
518
|
+
* it rejects rows after they are read, so the `limit` budget cannot be handed
|
|
519
|
+
* to DynamoDB, and a count has to read the rows.
|
|
520
|
+
*/
|
|
521
|
+
const dropsRows = (state) => state.keepRow !== undefined || state.liveRows !== undefined;
|
|
522
|
+
/** @internal Is this raw row one the query leaves out (#133)? */
|
|
523
|
+
const isExcludedRow = (state, row) => (state.keepRow !== undefined && !state.keepRow(row)) ||
|
|
524
|
+
(state.liveRows !== undefined && !state.liveRows.isLive(row));
|
|
525
|
+
/**
|
|
526
|
+
* @internal A filter with an empty part where none may be (`emptyPartProblem`):
|
|
527
|
+
* the `ValidationError` every path that builds a request fails with, before
|
|
528
|
+
* `compileExpr` — which throws on one — ever sees it (#133).
|
|
529
|
+
*/
|
|
530
|
+
const refuseEmptyFilterParts = (state) => {
|
|
531
|
+
for (const filter of state.exprFilters) {
|
|
532
|
+
const problem = emptyPartProblem(filter);
|
|
533
|
+
if (problem !== undefined) {
|
|
534
|
+
return new ValidationError({
|
|
535
|
+
entityType: state.entityTypes.join(", ") || "unknown",
|
|
536
|
+
operation: "query.filter",
|
|
537
|
+
cause: `filter: ${problem} Nothing was sent.`,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return undefined;
|
|
542
|
+
};
|
|
543
|
+
/**
|
|
544
|
+
* @internal The state a terminal runs: {@link QueryState.prepare} applied
|
|
545
|
+
* against the resolved table.
|
|
546
|
+
*/
|
|
547
|
+
const prepared = (state, tableName) => {
|
|
548
|
+
// A consistent read of a GSI — which DynamoDB only reads eventually
|
|
549
|
+
// consistently — is refused before anything is sent (#133).
|
|
550
|
+
if (state.consistentRead && state.globalIndex) {
|
|
551
|
+
return Effect.fail(new ValidationError({
|
|
552
|
+
entityType: state.entityTypes.join(", ") || "unknown",
|
|
553
|
+
operation: "query.consistentRead",
|
|
554
|
+
cause: `consistentRead: index "${state.indexName}" is a global secondary index, which ` +
|
|
555
|
+
"DynamoDB reads only eventually consistently. Nothing was sent.",
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
// A filter with an empty part where none may be is refused before
|
|
559
|
+
// anything is sent (#133).
|
|
560
|
+
const filterRefusal = refuseEmptyFilterParts(state);
|
|
561
|
+
if (filterRefusal !== undefined)
|
|
562
|
+
return Effect.fail(filterRefusal);
|
|
563
|
+
return state.prepare === undefined
|
|
564
|
+
? Effect.succeed(state)
|
|
565
|
+
: Effect.map(state.prepare(tableName), (prep) => {
|
|
566
|
+
const swap = prep.replaceBeginsWith;
|
|
567
|
+
return {
|
|
568
|
+
...state,
|
|
569
|
+
prepare: undefined,
|
|
570
|
+
keepRow: prep.keep,
|
|
571
|
+
skConditions: swap === undefined
|
|
572
|
+
? state.skConditions
|
|
573
|
+
: state.skConditions.map((c) => "beginsWith" in c.condition && c.condition.beginsWith === swap.from
|
|
574
|
+
? { ...c, condition: { beginsWith: swap.to } }
|
|
575
|
+
: c),
|
|
576
|
+
};
|
|
577
|
+
});
|
|
578
|
+
};
|
|
444
579
|
/** @internal Does the decoded item pass every client-side predicate? */
|
|
445
580
|
const accepts = (state, item) => {
|
|
446
581
|
for (const predicate of state.predicates) {
|
|
@@ -477,16 +612,22 @@ const cursorFromItem = (item, names) => {
|
|
|
477
612
|
* over-reading request can still rebuild an accurate cursor. They are stripped
|
|
478
613
|
* from the items handed back, so the caller sees exactly what it selected.
|
|
479
614
|
*/
|
|
480
|
-
const cursorProjectionFields = (state
|
|
481
|
-
|
|
482
|
-
|
|
615
|
+
const cursorProjectionFields = (state,
|
|
616
|
+
/** Whether the terminal rebuilds a cursor (`execute`); `paginate` doesn't. */
|
|
617
|
+
forCursor) => {
|
|
483
618
|
const projected = new Set([
|
|
484
619
|
...(state.projection ?? []),
|
|
485
620
|
...(state.projectionPaths ?? []).map((segments) => String(segments[0])),
|
|
486
621
|
]);
|
|
487
622
|
if (projected.size === 0)
|
|
488
623
|
return [];
|
|
489
|
-
|
|
624
|
+
const borrowed = new Set(!forCursor || state.limitValue === undefined || !state.keyFields ? [] : state.keyFields);
|
|
625
|
+
// A query that drops rows reads what it judges them by (#133).
|
|
626
|
+
if (state.keepRow !== undefined && state.skField !== undefined)
|
|
627
|
+
borrowed.add(state.skField);
|
|
628
|
+
for (const attr of state.liveRows?.reads ?? [])
|
|
629
|
+
borrowed.add(attr);
|
|
630
|
+
return [...borrowed].filter((field) => !projected.has(field));
|
|
490
631
|
};
|
|
491
632
|
// ---------------------------------------------------------------------------
|
|
492
633
|
// Terminals
|
|
@@ -521,16 +662,18 @@ export const collect = (self) => Effect.gen(function* () {
|
|
|
521
662
|
*/
|
|
522
663
|
export const execute = (self) => Effect.gen(function* () {
|
|
523
664
|
const client = yield* DynamoClient;
|
|
524
|
-
const
|
|
525
|
-
|
|
665
|
+
const tableName = self._state.resolveTableName
|
|
666
|
+
? yield* self._state.resolveTableName
|
|
667
|
+
: self._state.tableName;
|
|
668
|
+
const state = yield* prepared(self._state, tableName);
|
|
526
669
|
const limitValue = state.limitValue;
|
|
527
670
|
if (limitValue !== undefined && limitValue <= 0) {
|
|
528
671
|
return { items: [], cursor: null };
|
|
529
672
|
}
|
|
530
673
|
// Key attributes borrowed into an active projection so an over-reading
|
|
531
674
|
// request can still rebuild a cursor. Stripped again before decoding.
|
|
532
|
-
const borrowedFields = cursorProjectionFields(state);
|
|
533
|
-
const hasPredicate = state.predicates.length > 0;
|
|
675
|
+
const borrowedFields = cursorProjectionFields(state, true);
|
|
676
|
+
const hasPredicate = state.predicates.length > 0 || dropsRows(state);
|
|
534
677
|
const items = [];
|
|
535
678
|
let startKey = state.exclusiveStartKey;
|
|
536
679
|
let pageCount = 0;
|
|
@@ -538,7 +681,7 @@ export const execute = (self) => Effect.gen(function* () {
|
|
|
538
681
|
while (true) {
|
|
539
682
|
pageCount++;
|
|
540
683
|
const remaining = limitValue === undefined ? undefined : limitValue - items.length;
|
|
541
|
-
const cmd = buildDynamoCommand(state, tableName, { ExclusiveStartKey: startKey, Limit: computeRequestLimit(state, remaining) }, borrowedFields);
|
|
684
|
+
const cmd = buildDynamoCommand(state, tableName, { ExclusiveStartKey: startKey, Limit: computeRequestLimit(state, remaining, pageCount) }, borrowedFields);
|
|
542
685
|
const result = state.isScan ? yield* client.scan(cmd) : yield* client.query(cmd);
|
|
543
686
|
const returned = (result.Items ?? []);
|
|
544
687
|
const lastEvaluatedKey = result.LastEvaluatedKey;
|
|
@@ -552,6 +695,8 @@ export const execute = (self) => Effect.gen(function* () {
|
|
|
552
695
|
/** Index of the last row examined when the limit was reached, else -1. */
|
|
553
696
|
let stoppedAt = -1;
|
|
554
697
|
for (let i = 0; i < take; i++) {
|
|
698
|
+
if (isExcludedRow(state, returned[i]))
|
|
699
|
+
continue;
|
|
555
700
|
const raw = fromAttributeMap(returned[i]);
|
|
556
701
|
for (const field of borrowedFields)
|
|
557
702
|
delete raw[field];
|
|
@@ -596,8 +741,13 @@ export const execute = (self) => Effect.gen(function* () {
|
|
|
596
741
|
export const paginate = (self) => paginateInternal(self);
|
|
597
742
|
const paginateInternal = (self) => Effect.gen(function* () {
|
|
598
743
|
const client = yield* DynamoClient;
|
|
599
|
-
const
|
|
600
|
-
|
|
744
|
+
const tableName = self._state.resolveTableName
|
|
745
|
+
? yield* self._state.resolveTableName
|
|
746
|
+
: self._state.tableName;
|
|
747
|
+
// Prepared when the stream runs, so a failure is the stream's.
|
|
748
|
+
return Stream.unwrap(prepared(self._state, tableName).pipe(Effect.provideService(DynamoClient, client), Effect.map((state) => pageStream(client, tableName, state))));
|
|
749
|
+
});
|
|
750
|
+
const pageStream = (client, tableName, state) => {
|
|
601
751
|
const limitValue = state.limitValue;
|
|
602
752
|
if (limitValue !== undefined && limitValue <= 0) {
|
|
603
753
|
return Stream.empty;
|
|
@@ -609,19 +759,28 @@ const paginateInternal = (self) => Effect.gen(function* () {
|
|
|
609
759
|
}, (pageState) => Effect.gen(function* () {
|
|
610
760
|
const pageCount = pageState.pageCount + 1;
|
|
611
761
|
const remaining = limitValue === undefined ? undefined : limitValue - pageState.emitted;
|
|
762
|
+
const borrowedFields = cursorProjectionFields(state, false);
|
|
612
763
|
const cmd = buildDynamoCommand(state, tableName, {
|
|
613
764
|
ExclusiveStartKey: pageState.key,
|
|
614
|
-
Limit: computeRequestLimit(state, remaining),
|
|
615
|
-
});
|
|
765
|
+
Limit: computeRequestLimit(state, remaining, pageCount),
|
|
766
|
+
}, borrowedFields);
|
|
616
767
|
const result = state.isScan ? yield* client.scan(cmd) : yield* client.query(cmd);
|
|
617
768
|
const returned = (result.Items ?? []);
|
|
618
769
|
// See `execute` — a client predicate rejects rows after decode, so
|
|
619
770
|
// the budget cannot bound the examine window, only the accepted one.
|
|
620
|
-
const hasPredicate = state.predicates.length > 0;
|
|
771
|
+
const hasPredicate = state.predicates.length > 0 || dropsRows(state);
|
|
621
772
|
const take = remaining === undefined || hasPredicate
|
|
622
773
|
? returned.length
|
|
623
774
|
: Math.min(remaining, returned.length);
|
|
624
|
-
const examined = yield* Effect.forEach(returned
|
|
775
|
+
const examined = yield* Effect.forEach(returned
|
|
776
|
+
.slice(0, take)
|
|
777
|
+
.filter((item) => !isExcludedRow(state, item))
|
|
778
|
+
.map((item) => {
|
|
779
|
+
const raw = fromAttributeMap(item);
|
|
780
|
+
for (const field of borrowedFields)
|
|
781
|
+
delete raw[field];
|
|
782
|
+
return raw;
|
|
783
|
+
}), (raw) => state.decoder(raw));
|
|
625
784
|
const kept = hasPredicate ? examined.filter((item) => accepts(state, item)) : examined;
|
|
626
785
|
const decoded = remaining === undefined ? kept : kept.slice(0, remaining);
|
|
627
786
|
const emitted = pageState.emitted + decoded.length;
|
|
@@ -637,7 +796,7 @@ const paginateInternal = (self) => Effect.gen(function* () {
|
|
|
637
796
|
: Option.none();
|
|
638
797
|
return [[decoded], nextState];
|
|
639
798
|
}));
|
|
640
|
-
}
|
|
799
|
+
};
|
|
641
800
|
/**
|
|
642
801
|
* Execute a count query. Uses `Select: "COUNT"` on DynamoDB — no items are returned.
|
|
643
802
|
* Returns the total count across all requests (respects {@link maxPages}).
|
|
@@ -649,8 +808,10 @@ const paginateInternal = (self) => Effect.gen(function* () {
|
|
|
649
808
|
*/
|
|
650
809
|
export const count = (self) => Effect.gen(function* () {
|
|
651
810
|
const client = yield* DynamoClient;
|
|
652
|
-
const
|
|
653
|
-
|
|
811
|
+
const tableName = self._state.resolveTableName
|
|
812
|
+
? yield* self._state.resolveTableName
|
|
813
|
+
: self._state.tableName;
|
|
814
|
+
const state = yield* prepared(self._state, tableName);
|
|
654
815
|
const limitValue = state.limitValue;
|
|
655
816
|
if (limitValue !== undefined && limitValue <= 0)
|
|
656
817
|
return 0;
|
|
@@ -665,6 +826,37 @@ export const count = (self) => Effect.gen(function* () {
|
|
|
665
826
|
const items = yield* collect(self);
|
|
666
827
|
return items.length;
|
|
667
828
|
}
|
|
829
|
+
// Rows that may be history are judged by a few attributes, so only those
|
|
830
|
+
// are read — never whole items (#133).
|
|
831
|
+
if (dropsRows(state)) {
|
|
832
|
+
const judged = [
|
|
833
|
+
...new Set([
|
|
834
|
+
...(state.liveRows?.reads ?? []),
|
|
835
|
+
...(state.keepRow !== undefined && state.skField !== undefined ? [state.skField] : []),
|
|
836
|
+
]),
|
|
837
|
+
];
|
|
838
|
+
const judging = { ...state, projection: judged, projectionPaths: undefined };
|
|
839
|
+
let counted = 0;
|
|
840
|
+
let pages = 0;
|
|
841
|
+
let start;
|
|
842
|
+
do {
|
|
843
|
+
pages++;
|
|
844
|
+
const remaining = limitValue === undefined ? undefined : limitValue - counted;
|
|
845
|
+
const cmd = buildDynamoCommand(judging, tableName, {
|
|
846
|
+
ExclusiveStartKey: start,
|
|
847
|
+
Limit: computeRequestLimit(state, remaining, pages),
|
|
848
|
+
});
|
|
849
|
+
const result = state.isScan ? yield* client.scan(cmd) : yield* client.query(cmd);
|
|
850
|
+
const rows = (result.Items ?? []);
|
|
851
|
+
counted += rows.filter((row) => !isExcludedRow(state, row)).length;
|
|
852
|
+
start = result.LastEvaluatedKey;
|
|
853
|
+
if (limitValue !== undefined && counted >= limitValue)
|
|
854
|
+
return limitValue;
|
|
855
|
+
if (state.maxPagesValue != null && pages >= state.maxPagesValue)
|
|
856
|
+
break;
|
|
857
|
+
} while (start != null);
|
|
858
|
+
return counted;
|
|
859
|
+
}
|
|
668
860
|
let total = 0;
|
|
669
861
|
let pageCount = 0;
|
|
670
862
|
let exclusiveStartKey;
|
|
@@ -689,9 +881,15 @@ export const count = (self) => Effect.gen(function* () {
|
|
|
689
881
|
/**
|
|
690
882
|
* Return the built DynamoDB command input without executing.
|
|
691
883
|
* Useful for debugging, logging, or passing to DynamoClient directly.
|
|
884
|
+
* Fails with a `ValidationError` for a filter the query would refuse to send
|
|
885
|
+
* (an empty part under `or` / `not`, an `or()` with no parts, an `isIn` with
|
|
886
|
+
* no values).
|
|
692
887
|
*/
|
|
693
888
|
export const asParams = (self) => Effect.gen(function* () {
|
|
694
889
|
const state = self._state;
|
|
890
|
+
const refusal = refuseEmptyFilterParts(state);
|
|
891
|
+
if (refusal !== undefined)
|
|
892
|
+
return yield* refusal;
|
|
695
893
|
const tableName = state.resolveTableName ? yield* state.resolveTableName : state.tableName;
|
|
696
894
|
return buildDynamoCommand(state, tableName);
|
|
697
895
|
});
|