@photon-grove/evt 0.1.0 → 0.1.1
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/README.md +50 -9
- package/dist/dynamodb/client.d.ts +83 -0
- package/dist/dynamodb/client.d.ts.map +1 -0
- package/dist/dynamodb/client.js +6 -0
- package/dist/dynamodb/client.js.map +1 -0
- package/dist/dynamodb/index.d.ts +5 -1
- package/dist/dynamodb/index.d.ts.map +1 -1
- package/dist/dynamodb/index.js +7 -3
- package/dist/dynamodb/index.js.map +1 -1
- package/dist/dynamodb/item.d.ts +2 -0
- package/dist/dynamodb/item.d.ts.map +1 -1
- package/dist/dynamodb/item.js +5 -4
- package/dist/dynamodb/item.js.map +1 -1
- package/dist/dynamodb/marker.d.ts +11 -0
- package/dist/dynamodb/marker.d.ts.map +1 -0
- package/dist/dynamodb/marker.js +56 -0
- package/dist/dynamodb/marker.js.map +1 -0
- package/dist/dynamodb/repository.d.ts +61 -0
- package/dist/dynamodb/repository.d.ts.map +1 -0
- package/dist/dynamodb/repository.js +731 -0
- package/dist/dynamodb/repository.js.map +1 -0
- package/dist/dynamodb/requests.d.ts +65 -0
- package/dist/dynamodb/requests.d.ts.map +1 -0
- package/dist/dynamodb/requests.js +179 -0
- package/dist/dynamodb/requests.js.map +1 -0
- package/dist/errors.d.ts +96 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +103 -2
- package/dist/errors.js.map +1 -1
- package/dist/features.d.ts +55 -0
- package/dist/features.d.ts.map +1 -0
- package/dist/features.js +126 -0
- package/dist/features.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/memory/index.d.ts +1 -1
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +1 -1
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/repository.d.ts +38 -2
- package/dist/memory/repository.d.ts.map +1 -1
- package/dist/memory/repository.js +95 -6
- package/dist/memory/repository.js.map +1 -1
- package/dist/repository.d.ts +30 -1
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js +3 -4
- package/dist/repository.js.map +1 -1
- package/dist/store.d.ts +3 -3
- package/dist/store.js +3 -3
- package/package.json +12 -3
- package/src/dynamodb/client.ts +102 -0
- package/src/dynamodb/index.ts +26 -2
- package/src/dynamodb/item.ts +6 -4
- package/src/dynamodb/marker.ts +64 -0
- package/src/dynamodb/repository.ts +1030 -0
- package/src/dynamodb/requests.ts +256 -0
- package/src/errors.ts +153 -2
- package/src/features.ts +175 -0
- package/src/index.ts +24 -1
- package/src/memory/index.ts +9 -1
- package/src/memory/repository.ts +156 -6
- package/src/repository.ts +34 -6
- package/src/store.ts +3 -3
package/dist/repository.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// same way: validation, the reserved namespaces, and the transaction budget.
|
|
3
3
|
import { headActionSize, headUpdate } from './dynamodb/head.js';
|
|
4
4
|
import { eventItem, itemSize, snapshotItem } from './dynamodb/item.js';
|
|
5
|
+
import { FEATURES_PK, REBUILD_LOCK_PK } from './dynamodb/marker.js';
|
|
5
6
|
import { CommitTooLargeError, EvtError, ReservedEntityIdError } from './errors.js';
|
|
6
7
|
import { eventId } from './event.js';
|
|
7
8
|
import { MAX_COMMIT_ITEM_BYTES } from './snapshots.js';
|
|
@@ -14,9 +15,6 @@ export const RESERVED_PREFIXES = Object.freeze([
|
|
|
14
15
|
'SYSTEM#',
|
|
15
16
|
'command-outcome#',
|
|
16
17
|
]);
|
|
17
|
-
// The control rows every commit checks: the feature marker and the rebuild lock.
|
|
18
|
-
const FEATURES_ID = 'SYSTEM#FEATURES';
|
|
19
|
-
const REBUILD_LOCK_ID = 'SYSTEM#REBUILD';
|
|
20
18
|
/** Whether an entity ID is in a reserved namespace. */
|
|
21
19
|
export function isReserved(entityId) {
|
|
22
20
|
return RESERVED_PREFIXES.some((prefix) => entityId.startsWith(prefix));
|
|
@@ -79,7 +77,8 @@ export function validateCommit(commit) {
|
|
|
79
77
|
* backend budgets a commit by these sizes, so an oversized commit fails the same way on each.
|
|
80
78
|
*/
|
|
81
79
|
export function commitSizes(commit, ttl) {
|
|
82
|
-
|
|
80
|
+
// The control rows every commit checks: the feature marker and the rebuild lock.
|
|
81
|
+
const sizes = [controlCheckSize(FEATURES_PK), controlCheckSize(REBUILD_LOCK_PK)];
|
|
83
82
|
const first = commit.events[0];
|
|
84
83
|
const last = commit.events.at(-1);
|
|
85
84
|
if (first === undefined || last === undefined) {
|
package/dist/repository.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,6EAA6E;AAE7E,OAAO,EAAC,cAAc,EAAE,UAAU,EAAC,MAAM,oBAAoB,CAAA;AAC7D,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAC,MAAM,oBAAoB,CAAA;AACpE,OAAO,EAAC,mBAAmB,EAAE,QAAQ,EAAE,qBAAqB,EAAC,MAAM,aAAa,CAAA;AAChF,OAAO,EAAgD,OAAO,EAAC,MAAM,YAAY,CAAA;AACjF,OAAO,EAAC,qBAAqB,EAAC,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
CHANGED
|
@@ -94,9 +94,9 @@ export declare class Store {
|
|
|
94
94
|
*/
|
|
95
95
|
execute<S, C, D extends EventDefinitions>(aggregate: Aggregate<S, C, D>, entityId: string, command: C, options?: ExecuteOptions): Promise<ExecuteResult<S>>;
|
|
96
96
|
/**
|
|
97
|
-
* Like {@link execute}, but loads and runs the command again after a retryable error (
|
|
98
|
-
*
|
|
99
|
-
* error is thrown. A rejection or a duplicate is a result, never retried.
|
|
97
|
+
* Like {@link execute}, but loads and runs the command again after a retryable error (see
|
|
98
|
+
* {@link isRetryable}), waiting {@link retryDelay} between attempts. When the attempts run out,
|
|
99
|
+
* the last error is thrown. A rejection or a duplicate is a result, never retried.
|
|
100
100
|
*/
|
|
101
101
|
executeWithRetry<S, C, D extends EventDefinitions>(aggregate: Aggregate<S, C, D>, entityId: string, command: C, options?: RetryOptions): Promise<ExecuteResult<S>>;
|
|
102
102
|
}
|
package/dist/store.js
CHANGED
|
@@ -85,9 +85,9 @@ export class Store {
|
|
|
85
85
|
return { kind: 'committed', state, events };
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
|
-
* Like {@link execute}, but loads and runs the command again after a retryable error (
|
|
89
|
-
*
|
|
90
|
-
* error is thrown. A rejection or a duplicate is a result, never retried.
|
|
88
|
+
* Like {@link execute}, but loads and runs the command again after a retryable error (see
|
|
89
|
+
* {@link isRetryable}), waiting {@link retryDelay} between attempts. When the attempts run out,
|
|
90
|
+
* the last error is thrown. A rejection or a duplicate is a result, never retried.
|
|
91
91
|
*/
|
|
92
92
|
async executeWithRetry(aggregate, entityId, command, options = {}) {
|
|
93
93
|
const policy = options.policy ?? DEFAULT_RETRY_POLICY;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@photon-grove/evt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Event sourcing for TypeScript, wire-compatible with the Go and Rust evt implementations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"event-sourcing",
|
|
@@ -44,14 +44,23 @@
|
|
|
44
44
|
},
|
|
45
45
|
"./package.json": "./package.json"
|
|
46
46
|
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@aws-sdk/client-dynamodb": "^3.0.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"@aws-sdk/client-dynamodb": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
47
55
|
"scripts": {
|
|
48
56
|
"build": "tsc",
|
|
49
57
|
"check": "tsc --noEmit && tsc -p tsconfig.test.json && oxlint --deny-warnings && node scripts/check-imports.ts",
|
|
50
58
|
"test": "vitest run"
|
|
51
59
|
},
|
|
52
60
|
"devDependencies": {
|
|
53
|
-
"@
|
|
54
|
-
"
|
|
61
|
+
"@aws-sdk/client-dynamodb": "3.1161.0",
|
|
62
|
+
"@types/node": "^26.6.6",
|
|
63
|
+
"oxlint": "1.90.0",
|
|
55
64
|
"typescript": "^7.0.2",
|
|
56
65
|
"vitest": "5.0.5"
|
|
57
66
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// The DynamoDB calls the repository makes, as a structural interface. The AWS SDK's aggregated
|
|
2
|
+
// `DynamoDB` client (`@aws-sdk/client-dynamodb`) satisfies it, so this package imports nothing
|
|
3
|
+
// from the SDK. Requests and responses use the low-level `AttributeValue` maps, never the document
|
|
4
|
+
// client's marshalling, because the storage format pins every attribute's type.
|
|
5
|
+
|
|
6
|
+
import type {Item} from './item.js'
|
|
7
|
+
|
|
8
|
+
/** The options the repository passes with every call. */
|
|
9
|
+
export interface RequestOptions {
|
|
10
|
+
abortSignal?: AbortSignal
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** One action of a `TransactWriteItems` call. */
|
|
14
|
+
export interface TransactItem {
|
|
15
|
+
ConditionCheck?: ConditionCheckAction
|
|
16
|
+
Put?: PutAction
|
|
17
|
+
Update?: UpdateAction
|
|
18
|
+
Delete?: DeleteAction
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The expression parts an action or a query may carry. */
|
|
22
|
+
export interface Expressions {
|
|
23
|
+
ConditionExpression?: string
|
|
24
|
+
ExpressionAttributeNames?: Record<string, string>
|
|
25
|
+
ExpressionAttributeValues?: Item
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ConditionCheckAction extends Expressions {
|
|
29
|
+
TableName: string
|
|
30
|
+
Key: Item
|
|
31
|
+
ConditionExpression: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PutAction extends Expressions {
|
|
35
|
+
TableName: string
|
|
36
|
+
Item: Item
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface UpdateAction extends Expressions {
|
|
40
|
+
TableName: string
|
|
41
|
+
Key: Item
|
|
42
|
+
UpdateExpression: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DeleteAction extends Expressions {
|
|
46
|
+
TableName: string
|
|
47
|
+
Key: Item
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface GetItemInput {
|
|
51
|
+
TableName: string
|
|
52
|
+
Key: Item
|
|
53
|
+
ConsistentRead?: boolean
|
|
54
|
+
ProjectionExpression?: string
|
|
55
|
+
ExpressionAttributeNames?: Record<string, string>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PutItemInput extends Expressions {
|
|
59
|
+
TableName: string
|
|
60
|
+
Item: Item
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface QueryInput {
|
|
64
|
+
TableName: string
|
|
65
|
+
KeyConditionExpression: string
|
|
66
|
+
ExpressionAttributeNames?: Record<string, string>
|
|
67
|
+
ExpressionAttributeValues: Item
|
|
68
|
+
ConsistentRead?: boolean
|
|
69
|
+
ProjectionExpression?: string
|
|
70
|
+
ScanIndexForward?: boolean
|
|
71
|
+
Limit?: number
|
|
72
|
+
ExclusiveStartKey?: Item
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ScanInput {
|
|
76
|
+
TableName: string
|
|
77
|
+
ConsistentRead?: boolean
|
|
78
|
+
ProjectionExpression?: string
|
|
79
|
+
Limit?: number
|
|
80
|
+
ExclusiveStartKey?: Item
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A page of a query or a scan. Items come back as the SDK decodes them. */
|
|
84
|
+
export interface Page {
|
|
85
|
+
Items?: Record<string, unknown>[] | undefined
|
|
86
|
+
LastEvaluatedKey?: Record<string, unknown> | undefined
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The calls {@link DynamoRepository} makes: the SDK's `DynamoDB` client has them all. */
|
|
90
|
+
export interface DynamoClient {
|
|
91
|
+
getItem(
|
|
92
|
+
input: GetItemInput,
|
|
93
|
+
options?: RequestOptions,
|
|
94
|
+
): Promise<{Item?: Record<string, unknown> | undefined}>
|
|
95
|
+
putItem(input: PutItemInput, options?: RequestOptions): Promise<unknown>
|
|
96
|
+
query(input: QueryInput, options?: RequestOptions): Promise<Page>
|
|
97
|
+
scan(input: ScanInput, options?: RequestOptions): Promise<Page>
|
|
98
|
+
transactWriteItems(
|
|
99
|
+
input: {TransactItems: TransactItem[]},
|
|
100
|
+
options?: RequestOptions,
|
|
101
|
+
): Promise<unknown>
|
|
102
|
+
}
|
package/src/dynamodb/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// @photon-grove/evt/dynamodb: the DynamoDB backend.
|
|
2
|
-
//
|
|
1
|
+
// @photon-grove/evt/dynamodb: the DynamoDB backend. It imports nothing from the AWS SDK: the
|
|
2
|
+
// repository takes the SDK's `DynamoDB` client, or anything shaped like it, as a DynamoClient.
|
|
3
3
|
|
|
4
4
|
export {
|
|
5
5
|
type AttributeValue,
|
|
@@ -17,5 +17,29 @@ export {
|
|
|
17
17
|
snapshotFromItem,
|
|
18
18
|
snapshotItem,
|
|
19
19
|
sortKey,
|
|
20
|
+
ttlFromItem,
|
|
20
21
|
viewItem,
|
|
21
22
|
} from './item.js'
|
|
23
|
+
export {
|
|
24
|
+
type ConditionCheckAction,
|
|
25
|
+
type DeleteAction,
|
|
26
|
+
type DynamoClient,
|
|
27
|
+
type Expressions,
|
|
28
|
+
type GetItemInput,
|
|
29
|
+
type Page,
|
|
30
|
+
type PutAction,
|
|
31
|
+
type PutItemInput,
|
|
32
|
+
type QueryInput,
|
|
33
|
+
type RequestOptions,
|
|
34
|
+
type ScanInput,
|
|
35
|
+
type TransactItem,
|
|
36
|
+
type UpdateAction,
|
|
37
|
+
} from './client.js'
|
|
38
|
+
export {FEATURES_PK, REBUILD_LOCK_PK, manifestFromItem, markerItem} from './marker.js'
|
|
39
|
+
export {DynamoRepository, type DynamoRepositoryOptions} from './repository.js'
|
|
40
|
+
export {
|
|
41
|
+
type CommitTtl,
|
|
42
|
+
type SnapshotTtl,
|
|
43
|
+
commitTransaction,
|
|
44
|
+
snapshotTransaction,
|
|
45
|
+
} from './requests.js'
|
package/src/dynamodb/item.ts
CHANGED
|
@@ -151,12 +151,14 @@ export function snapshotFromItem(item: Item): SerializedSnapshot {
|
|
|
151
151
|
|
|
152
152
|
/** Decodes a head item into its head and its `ttl`, if it has one. */
|
|
153
153
|
export function headFromItem(item: Item): {head: bigint; ttl: number | undefined} {
|
|
154
|
+
return {head: requiredNumber(item, 'head'), ttl: ttlFromItem(item)}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** An item's `ttl` in Unix seconds, or undefined when it has none. */
|
|
158
|
+
export function ttlFromItem(item: Item): number | undefined {
|
|
154
159
|
const ttl = optionalNumber(item, 'ttl')
|
|
155
160
|
|
|
156
|
-
return
|
|
157
|
-
head: requiredNumber(item, 'head'),
|
|
158
|
-
ttl: ttl === undefined ? undefined : safeInteger(ttl, 'ttl'),
|
|
159
|
-
}
|
|
161
|
+
return ttl === undefined ? undefined : safeInteger(ttl, 'ttl')
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
/** Whether an item is a stream's inline snapshot (`sk = 0`). */
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// The DynamoDB item of the feature-marker row. The control rows live in the reserved SYSTEM#
|
|
2
|
+
// namespace at sort key 0.
|
|
3
|
+
//
|
|
4
|
+
// | Attribute | Type | Feature-marker item (`pk = SYSTEM#FEATURES`, `sk = 0`) |
|
|
5
|
+
// | ------------------ | ---- | ------------------------------------------------------------ |
|
|
6
|
+
// | `version` | N | bumped on every change to the manifest |
|
|
7
|
+
// | `features` | S | JSON object, feature name → stored-format version, sorted |
|
|
8
|
+
// | `historyDependent` | S | JSON array of entity types, sorted. Absent when empty |
|
|
9
|
+
// | `retention` | S | JSON array of entity types, sorted. Absent when empty |
|
|
10
|
+
|
|
11
|
+
import {checkInt64, invalid} from '../fields.js'
|
|
12
|
+
import {type FeatureManifest, decodeManifest, encodeManifest} from '../features.js'
|
|
13
|
+
import {type Item, SNAPSHOT_SORT_KEY} from './item.js'
|
|
14
|
+
|
|
15
|
+
/** The partition key of the feature-marker row. */
|
|
16
|
+
export const FEATURES_PK = 'SYSTEM#FEATURES'
|
|
17
|
+
|
|
18
|
+
/** The partition key of the rebuild-lock row, which every commit checks is absent. */
|
|
19
|
+
export const REBUILD_LOCK_PK = 'SYSTEM#REBUILD'
|
|
20
|
+
|
|
21
|
+
/** Encodes a feature manifest as its marker item. Empty entity-type sets are left out. */
|
|
22
|
+
export function markerItem(manifest: FeatureManifest): Item {
|
|
23
|
+
const texts = encodeManifest(manifest)
|
|
24
|
+
const item: Item = {
|
|
25
|
+
pk: {S: FEATURES_PK},
|
|
26
|
+
sk: {N: SNAPSHOT_SORT_KEY.toString()},
|
|
27
|
+
version: {N: manifest.version.toString()},
|
|
28
|
+
features: {S: texts.features},
|
|
29
|
+
}
|
|
30
|
+
if (manifest.historyDependent.length > 0) {
|
|
31
|
+
item['historyDependent'] = {S: texts.historyDependent}
|
|
32
|
+
}
|
|
33
|
+
if (manifest.retention.length > 0) {
|
|
34
|
+
item['retention'] = {S: texts.retention}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return item
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Decodes a marker item. An absent text attribute is an empty map or set. */
|
|
41
|
+
export function manifestFromItem(item: Item): FeatureManifest {
|
|
42
|
+
const version = item['version']
|
|
43
|
+
if (version === undefined || !('N' in version) || !/^\d+$/.test(version.N)) {
|
|
44
|
+
throw invalid('the feature marker has no version')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return decodeManifest(checkInt64(BigInt(version.N), 'the feature marker version'), {
|
|
48
|
+
features: text(item, 'features'),
|
|
49
|
+
historyDependent: text(item, 'historyDependent'),
|
|
50
|
+
retention: text(item, 'retention'),
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function text(item: Item, name: string): string {
|
|
55
|
+
const value = item[name]
|
|
56
|
+
if (value === undefined) {
|
|
57
|
+
return ''
|
|
58
|
+
}
|
|
59
|
+
if (!('S' in value)) {
|
|
60
|
+
throw invalid(`the feature marker's ${name} attribute is not a string`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return value.S
|
|
64
|
+
}
|