@enbox/dwn-sdk-js 0.3.7 → 0.3.9
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/browser.mjs +8 -8
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +2591 -1435
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/core/constants.js +20 -0
- package/dist/esm/src/core/constants.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +24 -1
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/grant-authorization.js +4 -4
- package/dist/esm/src/core/grant-authorization.js.map +1 -1
- package/dist/esm/src/core/message.js +89 -4
- package/dist/esm/src/core/message.js.map +1 -1
- package/dist/esm/src/core/messages-grant-authorization.js +147 -55
- package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization.js +76 -0
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/core/records-grant-authorization.js +40 -15
- package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
- package/dist/esm/src/handlers/messages-read.js +5 -5
- package/dist/esm/src/handlers/messages-read.js.map +1 -1
- package/dist/esm/src/handlers/messages-subscribe.js +109 -7
- package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/messages-sync.js +341 -96
- package/dist/esm/src/handlers/messages-sync.js.map +1 -1
- package/dist/esm/src/handlers/protocols-configure.js +81 -2
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/records-count.js +30 -0
- package/dist/esm/src/handlers/records-count.js.map +1 -1
- package/dist/esm/src/handlers/records-delete.js +3 -2
- package/dist/esm/src/handlers/records-delete.js.map +1 -1
- package/dist/esm/src/handlers/records-query.js +30 -0
- package/dist/esm/src/handlers/records-query.js.map +1 -1
- package/dist/esm/src/handlers/records-read.js +3 -2
- package/dist/esm/src/handlers/records-read.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +31 -0
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +21 -14
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +2 -0
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/messages-read.js +6 -3
- package/dist/esm/src/interfaces/messages-read.js.map +1 -1
- package/dist/esm/src/interfaces/messages-subscribe.js +6 -3
- package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/messages-sync.js +17 -3
- package/dist/esm/src/interfaces/messages-sync.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +5 -2
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-query.js +8 -4
- package/dist/esm/src/interfaces/protocols-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-count.js +5 -0
- package/dist/esm/src/interfaces/records-count.js.map +1 -1
- package/dist/esm/src/interfaces/records-delete.js +6 -2
- package/dist/esm/src/interfaces/records-delete.js.map +1 -1
- package/dist/esm/src/interfaces/records-query.js +5 -0
- package/dist/esm/src/interfaces/records-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-read.js +6 -3
- package/dist/esm/src/interfaces/records-read.js.map +1 -1
- package/dist/esm/src/interfaces/records-subscribe.js +5 -0
- package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/records-write.js +6 -3
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/protocols/permissions.js +28 -7
- package/dist/esm/src/protocols/permissions.js.map +1 -1
- package/dist/esm/src/sync/records-projection.js +228 -0
- package/dist/esm/src/sync/records-projection.js.map +1 -0
- package/dist/esm/src/types/message-types.js.map +1 -1
- package/dist/esm/src/types/permission-types.js.map +1 -1
- package/dist/esm/src/utils/permission-scope.js +37 -0
- package/dist/esm/src/utils/permission-scope.js.map +1 -0
- package/dist/esm/tests/core/grant-authorization.spec.js +26 -3
- package/dist/esm/tests/core/grant-authorization.spec.js.map +1 -1
- package/dist/esm/tests/core/records-grant-authorization.spec.js +117 -0
- package/dist/esm/tests/core/records-grant-authorization.spec.js.map +1 -0
- package/dist/esm/tests/features/permissions.spec.js +126 -0
- package/dist/esm/tests/features/permissions.spec.js.map +1 -1
- package/dist/esm/tests/features/records-record-limit.spec.js +14 -0
- package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-read.spec.js +345 -12
- package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-subscribe.spec.js +326 -9
- package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-sync.spec.js +1053 -7
- package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-configure.spec.js +361 -0
- package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-count.spec.js +75 -2
- package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-query.spec.js +73 -0
- package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js +75 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-write.spec.js +41 -0
- package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/messages-get.spec.js +107 -5
- package/dist/esm/tests/interfaces/messages-get.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/protocols-configure.spec.js +13 -0
- package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-delete.spec.js +12 -0
- package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-query.spec.js +10 -0
- package/dist/esm/tests/interfaces/records-query.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-subscribe.spec.js +10 -0
- package/dist/esm/tests/interfaces/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-write.spec.js +33 -0
- package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
- package/dist/esm/tests/sync/records-projection.spec.js +245 -0
- package/dist/esm/tests/sync/records-projection.spec.js.map +1 -0
- package/dist/esm/tests/test-suite.js +2 -0
- package/dist/esm/tests/test-suite.js.map +1 -1
- package/dist/esm/tests/utils/permission-scope.spec.js +66 -0
- package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -0
- package/dist/esm/tests/utils/test-data-generator.js +5 -2
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/constants.d.ts +13 -0
- package/dist/types/src/core/constants.d.ts.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +24 -1
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/grant-authorization.d.ts +1 -2
- package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/message.d.ts +41 -1
- package/dist/types/src/core/message.d.ts.map +1 -1
- package/dist/types/src/core/messages-grant-authorization.d.ts +36 -4
- package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization.d.ts +12 -0
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/core/records-grant-authorization.d.ts +6 -0
- package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts +2 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-sync.d.ts +31 -0
- package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
- package/dist/types/src/handlers/protocols-configure.d.ts +3 -0
- package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/handlers/records-count.d.ts +4 -0
- package/dist/types/src/handlers/records-count.d.ts.map +1 -1
- package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
- package/dist/types/src/handlers/records-query.d.ts +4 -0
- package/dist/types/src/handlers/records-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-read.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +6 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-read.d.ts +1 -1
- package/dist/types/src/interfaces/messages-read.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-sync.d.ts +4 -1
- package/dist/types/src/interfaces/messages-sync.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-count.d.ts +1 -0
- package/dist/types/src/interfaces/records-count.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-delete.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-query.d.ts +1 -0
- package/dist/types/src/interfaces/records-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-read.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-subscribe.d.ts +1 -0
- package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/protocols/permissions.d.ts +2 -0
- package/dist/types/src/protocols/permissions.d.ts.map +1 -1
- package/dist/types/src/sync/records-projection.d.ts +98 -0
- package/dist/types/src/sync/records-projection.d.ts.map +1 -0
- package/dist/types/src/types/message-types.d.ts +1 -0
- package/dist/types/src/types/message-types.d.ts.map +1 -1
- package/dist/types/src/types/messages-types.d.ts +21 -3
- package/dist/types/src/types/messages-types.d.ts.map +1 -1
- package/dist/types/src/types/permission-types.d.ts +4 -0
- package/dist/types/src/types/permission-types.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +4 -0
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/src/types/subscriptions.d.ts +18 -3
- package/dist/types/src/types/subscriptions.d.ts.map +1 -1
- package/dist/types/src/utils/permission-scope.d.ts +29 -0
- package/dist/types/src/utils/permission-scope.d.ts.map +1 -0
- package/dist/types/tests/core/records-grant-authorization.spec.d.ts +2 -0
- package/dist/types/tests/core/records-grant-authorization.spec.d.ts.map +1 -0
- package/dist/types/tests/features/permissions.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-record-limit.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-read.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/protocols-configure.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-count.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-query.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
- package/dist/types/tests/sync/records-projection.spec.d.ts +2 -0
- package/dist/types/tests/sync/records-projection.spec.d.ts.map +1 -0
- package/dist/types/tests/test-suite.d.ts.map +1 -1
- package/dist/types/tests/utils/permission-scope.spec.d.ts +2 -0
- package/dist/types/tests/utils/permission-scope.spec.d.ts.map +1 -0
- package/dist/types/tests/utils/test-data-generator.d.ts +5 -2
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/constants.ts +24 -0
- package/src/core/dwn-error.ts +24 -1
- package/src/core/grant-authorization.ts +7 -5
- package/src/core/message.ts +153 -6
- package/src/core/messages-grant-authorization.ts +282 -70
- package/src/core/protocol-authorization.ts +130 -0
- package/src/core/records-grant-authorization.ts +64 -21
- package/src/handlers/messages-read.ts +7 -5
- package/src/handlers/messages-subscribe.ts +149 -9
- package/src/handlers/messages-sync.ts +593 -102
- package/src/handlers/protocols-configure.ts +103 -2
- package/src/handlers/records-count.ts +33 -0
- package/src/handlers/records-delete.ts +3 -2
- package/src/handlers/records-query.ts +33 -0
- package/src/handlers/records-read.ts +3 -2
- package/src/handlers/records-subscribe.ts +34 -0
- package/src/handlers/records-write.ts +21 -15
- package/src/index.ts +7 -3
- package/src/interfaces/messages-read.ts +8 -5
- package/src/interfaces/messages-subscribe.ts +12 -9
- package/src/interfaces/messages-sync.ts +33 -12
- package/src/interfaces/protocols-configure.ts +8 -4
- package/src/interfaces/protocols-query.ts +13 -9
- package/src/interfaces/records-count.ts +7 -0
- package/src/interfaces/records-delete.ts +9 -5
- package/src/interfaces/records-query.ts +7 -0
- package/src/interfaces/records-read.ts +6 -3
- package/src/interfaces/records-subscribe.ts +7 -0
- package/src/interfaces/records-write.ts +25 -17
- package/src/protocols/permissions.ts +47 -9
- package/src/sync/records-projection.ts +328 -0
- package/src/types/message-types.ts +1 -0
- package/src/types/messages-types.ts +23 -3
- package/src/types/permission-types.ts +5 -1
- package/src/types/records-types.ts +5 -1
- package/src/types/subscriptions.ts +19 -3
- package/src/utils/permission-scope.ts +55 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import type { Filter } from '../types/query-types.js';
|
|
2
|
+
import type { Hash } from '../types/smt-types.js';
|
|
3
|
+
import type { MessageStore, MessageStoreOptions } from '../types/message-store.js';
|
|
4
|
+
|
|
5
|
+
import { DwnInterfaceName } from '../enums/dwn-interface-method.js';
|
|
6
|
+
import { FilterUtility } from '../utils/filter.js';
|
|
7
|
+
import { hashToHex } from '../smt/smt-utils.js';
|
|
8
|
+
import { isRecordsPrimaryProjectionExcludedProtocol } from '../core/constants.js';
|
|
9
|
+
import { lexicographicalCompare } from '../utils/string.js';
|
|
10
|
+
import { Message } from '../core/message.js';
|
|
11
|
+
import { SMTStoreMemory } from '../smt/smt-store-memory.js';
|
|
12
|
+
import { SparseMerkleTree } from '../smt/sparse-merkle-tree.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Projection-root algorithm for record-primary scoped subsets.
|
|
16
|
+
*
|
|
17
|
+
* This version builds an on-demand SMT over latest Records primary message CIDs
|
|
18
|
+
* selected by protocol plus optional exact protocolPath or context subtree.
|
|
19
|
+
* Dependency records, protocol configs, and record data payloads are not part
|
|
20
|
+
* of this root.
|
|
21
|
+
*/
|
|
22
|
+
export const RECORDS_PROJECTION_ROOT_VERSION = 'records-primary-scope-root-v1';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A Records primary projection scope.
|
|
26
|
+
*
|
|
27
|
+
* `protocolPath` is an exact type-path match. `contextId` is a boundary-aware
|
|
28
|
+
* subtree match: the candidate context must equal the scoped context or start
|
|
29
|
+
* with `${contextId}/`. `protocolPath` and `contextId` are mutually exclusive.
|
|
30
|
+
*/
|
|
31
|
+
export type RecordsProjectionScope = {
|
|
32
|
+
protocol: string;
|
|
33
|
+
protocolPath?: string;
|
|
34
|
+
contextId?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type RecordsProjectionInput = {
|
|
38
|
+
tenant: string;
|
|
39
|
+
messageStore: MessageStore;
|
|
40
|
+
scopes: readonly [RecordsProjectionScope, ...RecordsProjectionScope[]];
|
|
41
|
+
options?: MessageStoreOptions;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type RecordsProjectionTreeInput = RecordsProjectionInput & {
|
|
45
|
+
prefix: boolean[];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type RecordsProjectionSnapshot = {
|
|
49
|
+
getRoot(): Promise<Hash>;
|
|
50
|
+
getRootHex(): Promise<string>;
|
|
51
|
+
getSubtreeHash(prefix: boolean[]): Promise<Hash>;
|
|
52
|
+
getLeaves(prefix: boolean[]): Promise<string[]>;
|
|
53
|
+
close(): Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type NormalizedRecordsProjectionScope = {
|
|
57
|
+
protocol: string;
|
|
58
|
+
} | {
|
|
59
|
+
protocol: string;
|
|
60
|
+
protocolPath: string;
|
|
61
|
+
} | {
|
|
62
|
+
protocol: string;
|
|
63
|
+
contextId: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Computes deterministic on-demand roots for Records projections.
|
|
68
|
+
*
|
|
69
|
+
* The snapshot API performs one store enumeration and serves tree operations
|
|
70
|
+
* from that in-memory view. A future store-level snapshot/high-watermark can be
|
|
71
|
+
* threaded through the `options` input without changing the projection shape.
|
|
72
|
+
*/
|
|
73
|
+
export class RecordsProjection {
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns the sorted latest Records primary message CIDs covered by a scope union.
|
|
77
|
+
*/
|
|
78
|
+
public static async getPrimaryMessageCids({
|
|
79
|
+
tenant,
|
|
80
|
+
messageStore,
|
|
81
|
+
scopes,
|
|
82
|
+
options,
|
|
83
|
+
}: RecordsProjectionInput): Promise<string[]> {
|
|
84
|
+
const filters = RecordsProjection.normalizeScopes(scopes)
|
|
85
|
+
.filter(scope => !isRecordsPrimaryProjectionExcludedProtocol(scope.protocol))
|
|
86
|
+
.flatMap(scope => RecordsProjection.constructFilters(scope));
|
|
87
|
+
if (filters.length === 0) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { messages } = await messageStore.query(tenant, filters, undefined, undefined, options);
|
|
92
|
+
const messageCids = await Promise.all(messages.map(message => Message.getCid(message)));
|
|
93
|
+
|
|
94
|
+
return [...new Set(messageCids)].sort(lexicographicalCompare); // NOSONAR — projection IDs require locale-independent bytewise ordering.
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Returns the projection root hash.
|
|
99
|
+
*/
|
|
100
|
+
public static async getRoot(input: RecordsProjectionInput): Promise<Hash> {
|
|
101
|
+
return RecordsProjection.withTree(input, tree => tree.getRoot());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Returns the projection root hash encoded as lowercase hex.
|
|
106
|
+
*/
|
|
107
|
+
public static async getRootHex(input: RecordsProjectionInput): Promise<string> {
|
|
108
|
+
return hashToHex(await RecordsProjection.getRoot(input));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns the subtree hash for a bit prefix within this projection.
|
|
113
|
+
*/
|
|
114
|
+
public static async getSubtreeHash(input: RecordsProjectionTreeInput): Promise<Hash> {
|
|
115
|
+
return RecordsProjection.withTree(input, tree => tree.getSubtreeHash(input.prefix));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns the message CIDs under a bit prefix within this projection.
|
|
120
|
+
*/
|
|
121
|
+
public static async getLeaves(input: RecordsProjectionTreeInput): Promise<string[]> {
|
|
122
|
+
return RecordsProjection.withTree(input, tree => tree.getLeaves(input.prefix));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Builds an in-memory projection tree from one primary-CID enumeration.
|
|
127
|
+
*/
|
|
128
|
+
public static async createSnapshot(input: RecordsProjectionInput): Promise<RecordsProjectionSnapshot> {
|
|
129
|
+
const tree = await RecordsProjection.createTree(input);
|
|
130
|
+
return {
|
|
131
|
+
close : () => tree.close(),
|
|
132
|
+
getLeaves : (prefix: boolean[]) => tree.getLeaves(prefix),
|
|
133
|
+
getRoot : () => tree.getRoot(),
|
|
134
|
+
getRootHex : async () => hashToHex(await tree.getRoot()),
|
|
135
|
+
getSubtreeHash : (prefix: boolean[]) => tree.getSubtreeHash(prefix),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Normalizes a scope union into sorted, duplicate-free, subsumption-reduced entries.
|
|
141
|
+
*/
|
|
142
|
+
public static normalizeScopes(
|
|
143
|
+
scopes: readonly [RecordsProjectionScope, ...RecordsProjectionScope[]],
|
|
144
|
+
): [NormalizedRecordsProjectionScope, ...NormalizedRecordsProjectionScope[]] {
|
|
145
|
+
if (scopes.length === 0) {
|
|
146
|
+
throw new Error('RecordsProjection: scopes must contain at least one scope.');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const normalized = scopes.map(scope => RecordsProjection.normalizeScope(scope));
|
|
150
|
+
const deduped = new Map<string, NormalizedRecordsProjectionScope>();
|
|
151
|
+
for (const scope of normalized) {
|
|
152
|
+
deduped.set(RecordsProjection.scopeKey(scope), scope);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const uniqueScopes = [...deduped.values()];
|
|
156
|
+
const protocolWide = new Set(
|
|
157
|
+
uniqueScopes
|
|
158
|
+
.filter(scope => RecordsProjection.isProtocolWideScope(scope))
|
|
159
|
+
.map(scope => scope.protocol)
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const reduced = uniqueScopes.filter(scope => {
|
|
163
|
+
if (protocolWide.has(scope.protocol)) {
|
|
164
|
+
return RecordsProjection.isProtocolWideScope(scope);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (RecordsProjection.isContextScope(scope)) {
|
|
168
|
+
return !uniqueScopes.some(candidate =>
|
|
169
|
+
candidate !== scope &&
|
|
170
|
+
RecordsProjection.isContextScope(candidate) &&
|
|
171
|
+
candidate.protocol === scope.protocol &&
|
|
172
|
+
RecordsProjection.contextIdSubsumes(candidate.contextId, scope.contextId)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return true;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const result = reduced.sort(RecordsProjection.compareScopes);
|
|
180
|
+
return result as [NormalizedRecordsProjectionScope, ...NormalizedRecordsProjectionScope[]];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private static async withTree<T>(
|
|
184
|
+
input: RecordsProjectionInput,
|
|
185
|
+
fn: (tree: SparseMerkleTree) => Promise<T>,
|
|
186
|
+
): Promise<T> {
|
|
187
|
+
const tree = await RecordsProjection.createTree(input);
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
return await fn(tree);
|
|
191
|
+
} finally {
|
|
192
|
+
await tree.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private static async createTree(input: RecordsProjectionInput): Promise<SparseMerkleTree> {
|
|
197
|
+
const tree = new SparseMerkleTree(new SMTStoreMemory());
|
|
198
|
+
await tree.initialize();
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const messageCids = await RecordsProjection.getPrimaryMessageCids(input);
|
|
202
|
+
for (const messageCid of messageCids) {
|
|
203
|
+
await tree.insert(messageCid);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return tree;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
await tree.close();
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private static normalizeScope(scope: RecordsProjectionScope): NormalizedRecordsProjectionScope {
|
|
214
|
+
const protocol = RecordsProjection.requireNonEmptyString(scope.protocol, 'protocol');
|
|
215
|
+
|
|
216
|
+
if (scope.protocolPath !== undefined && scope.contextId !== undefined) {
|
|
217
|
+
throw new Error('RecordsProjection: protocolPath and contextId scopes are mutually exclusive.');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (scope.protocolPath !== undefined) {
|
|
221
|
+
return {
|
|
222
|
+
protocol,
|
|
223
|
+
protocolPath: RecordsProjection.requireNonEmptyString(scope.protocolPath, 'protocolPath'),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (scope.contextId !== undefined) {
|
|
228
|
+
return {
|
|
229
|
+
protocol,
|
|
230
|
+
contextId: RecordsProjection.requireNonEmptyString(scope.contextId, 'contextId'),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return { protocol };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private static constructFilters(scope: NormalizedRecordsProjectionScope): Filter[] {
|
|
238
|
+
const baseFilter: Filter = {
|
|
239
|
+
interface : DwnInterfaceName.Records,
|
|
240
|
+
isLatestBaseState : true,
|
|
241
|
+
protocol : scope.protocol,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
if ('protocolPath' in scope) {
|
|
245
|
+
return [{ ...baseFilter, protocolPath: scope.protocolPath }];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if ('contextId' in scope) {
|
|
249
|
+
const childContextPrefix = `${scope.contextId}/`;
|
|
250
|
+
return [
|
|
251
|
+
{ ...baseFilter, contextId: scope.contextId },
|
|
252
|
+
{
|
|
253
|
+
...baseFilter,
|
|
254
|
+
contextId: FilterUtility.constructPrefixFilterAsRangeFilter(childContextPrefix),
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return [baseFilter];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private static requireNonEmptyString(value: string, field: string): string {
|
|
263
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
264
|
+
throw new Error(`RecordsProjection: ${field} must be a non-empty string.`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private static scopeKey(scope: NormalizedRecordsProjectionScope): string {
|
|
271
|
+
if ('protocolPath' in scope) {
|
|
272
|
+
return `${scope.protocol}\u001fprotocolPath\u001f${scope.protocolPath}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if ('contextId' in scope) {
|
|
276
|
+
return `${scope.protocol}\u001fcontextId\u001f${scope.contextId}`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return `${scope.protocol}\u001fprotocol`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private static isProtocolWideScope(
|
|
283
|
+
scope: NormalizedRecordsProjectionScope,
|
|
284
|
+
): scope is { protocol: string } {
|
|
285
|
+
return !('protocolPath' in scope) && !('contextId' in scope);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private static isContextScope(
|
|
289
|
+
scope: NormalizedRecordsProjectionScope,
|
|
290
|
+
): scope is { protocol: string; contextId: string } {
|
|
291
|
+
return 'contextId' in scope;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private static contextIdSubsumes(parentContextId: string, childContextId: string): boolean {
|
|
295
|
+
return childContextId === parentContextId ||
|
|
296
|
+
childContextId.startsWith(`${parentContextId}/`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private static compareScopes(
|
|
300
|
+
a: NormalizedRecordsProjectionScope,
|
|
301
|
+
b: NormalizedRecordsProjectionScope,
|
|
302
|
+
): number {
|
|
303
|
+
const protocolCompare = lexicographicalCompare(a.protocol, b.protocol);
|
|
304
|
+
if (protocolCompare !== 0) {
|
|
305
|
+
return protocolCompare;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const aRank = RecordsProjection.scopeRank(a);
|
|
309
|
+
const bRank = RecordsProjection.scopeRank(b);
|
|
310
|
+
if (aRank !== bRank) {
|
|
311
|
+
return aRank - bRank;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return lexicographicalCompare(RecordsProjection.scopeValue(a), RecordsProjection.scopeValue(b));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private static scopeRank(scope: NormalizedRecordsProjectionScope): number {
|
|
318
|
+
if ('protocolPath' in scope) { return 1; }
|
|
319
|
+
if ('contextId' in scope) { return 2; }
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private static scopeValue(scope: NormalizedRecordsProjectionScope): string {
|
|
324
|
+
if ('protocolPath' in scope) { return scope.protocolPath; }
|
|
325
|
+
if ('contextId' in scope) { return scope.contextId; }
|
|
326
|
+
return '';
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -92,6 +92,7 @@ export type DelegatedGrantRecordsWriteMessage = {
|
|
|
92
92
|
export type GenericSignaturePayload = {
|
|
93
93
|
descriptorCid: string;
|
|
94
94
|
permissionGrantId?: string;
|
|
95
|
+
permissionGrantIds?: string[];
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
98
|
* Record ID of a permission grant DWN `RecordsWrite` with `delegated` set to `true`.
|
|
@@ -24,7 +24,7 @@ export type MessagesReadDescriptor = {
|
|
|
24
24
|
method: DwnMethodName.Read;
|
|
25
25
|
messageCid: string;
|
|
26
26
|
messageTimestamp: string;
|
|
27
|
-
|
|
27
|
+
permissionGrantIds?: string[];
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export type MessagesReadMessage = GenericMessage & {
|
|
@@ -50,8 +50,14 @@ export type MessagesSyncDescriptor = {
|
|
|
50
50
|
messageTimestamp : string;
|
|
51
51
|
action : MessagesSyncAction;
|
|
52
52
|
protocol? : string; // optional protocol scope
|
|
53
|
+
projectionRootVersion?: string;
|
|
54
|
+
projectionScopes?: {
|
|
55
|
+
protocol: string;
|
|
56
|
+
protocolPath?: string;
|
|
57
|
+
contextId?: string;
|
|
58
|
+
}[];
|
|
53
59
|
prefix? : string; // bit path for subtree/leaves (e.g. "0110101...")
|
|
54
|
-
|
|
60
|
+
permissionGrantIds? : string[];
|
|
55
61
|
/**
|
|
56
62
|
* For `action: 'diff'`: a map of `{ bitPrefix: hexHash }` representing the client's
|
|
57
63
|
* subtree hashes at `depth`. The server compares each hash against its own tree
|
|
@@ -85,6 +91,18 @@ export type MessagesSyncDiffEntry = {
|
|
|
85
91
|
encodedData? : string;
|
|
86
92
|
};
|
|
87
93
|
|
|
94
|
+
export type MessagesSyncDependencyClass = 'protocolsConfigure' | 'recordsInitialWrite';
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Advisory dependency hint returned with projected diff responses. Dependency
|
|
98
|
+
* entries are not part of the projected root; clients must rederive that the
|
|
99
|
+
* dependency is required by the referenced primary before applying it.
|
|
100
|
+
*/
|
|
101
|
+
export type MessagesSyncDependencyEntry = MessagesSyncDiffEntry & {
|
|
102
|
+
dependencyClass: MessagesSyncDependencyClass;
|
|
103
|
+
rootMessageCid: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
88
106
|
export type MessagesSyncReply = GenericMessageReply & {
|
|
89
107
|
root? : string; // hex-encoded root hash (for 'root' action)
|
|
90
108
|
hash? : string; // hex-encoded subtree hash (for 'subtree' action)
|
|
@@ -93,6 +111,8 @@ export type MessagesSyncReply = GenericMessageReply & {
|
|
|
93
111
|
onlyRemote? : MessagesSyncDiffEntry[];
|
|
94
112
|
/** For 'diff' action: bit prefixes where the client has entries the server doesn't. */
|
|
95
113
|
onlyLocal? : string[];
|
|
114
|
+
/** Advisory dependency messages needed to apply projected `onlyRemote` primary entries. */
|
|
115
|
+
dependencies? : MessagesSyncDependencyEntry[];
|
|
96
116
|
};
|
|
97
117
|
|
|
98
118
|
export type MessagesSubscribeMessageOptions = {
|
|
@@ -115,7 +135,7 @@ export type MessagesSubscribeDescriptor = {
|
|
|
115
135
|
method: DwnMethodName.Subscribe;
|
|
116
136
|
messageTimestamp: string;
|
|
117
137
|
filters: MessagesFilter[];
|
|
118
|
-
|
|
138
|
+
permissionGrantIds?: string[];
|
|
119
139
|
/**
|
|
120
140
|
* Progress token to resume from. When provided, the handler replays events
|
|
121
141
|
* from the EventLog starting after this position instead of returning no
|
|
@@ -96,6 +96,10 @@ export type MessagesPermissionScope = {
|
|
|
96
96
|
interface: DwnInterfaceName.Messages;
|
|
97
97
|
method: DwnMethodName.Read;
|
|
98
98
|
protocol?: string;
|
|
99
|
+
/** May only be present when `protocol` is defined and `protocolPath` is undefined */
|
|
100
|
+
contextId?: string;
|
|
101
|
+
/** May only be present when `protocol` is defined and `contextId` is undefined */
|
|
102
|
+
protocolPath?: string;
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
/**
|
|
@@ -123,4 +127,4 @@ export type PermissionConditions = {
|
|
|
123
127
|
* If `undefined`, it is optional to make the message public.
|
|
124
128
|
*/
|
|
125
129
|
publication?: PermissionConditionPublication;
|
|
126
|
-
};
|
|
130
|
+
};
|
|
@@ -105,6 +105,7 @@ export type RecordsCountDescriptor = {
|
|
|
105
105
|
method: DwnMethodName.Count;
|
|
106
106
|
messageTimestamp: string;
|
|
107
107
|
filter: RecordsFilter;
|
|
108
|
+
permissionGrantId?: string;
|
|
108
109
|
};
|
|
109
110
|
|
|
110
111
|
export type RecordsCountMessage = GenericMessage & {
|
|
@@ -120,6 +121,7 @@ export type RecordsQueryDescriptor = {
|
|
|
120
121
|
method: DwnMethodName.Query;
|
|
121
122
|
messageTimestamp: string;
|
|
122
123
|
filter: RecordsFilter;
|
|
124
|
+
permissionGrantId?: string;
|
|
123
125
|
dateSort?: DateSort;
|
|
124
126
|
pagination?: Pagination;
|
|
125
127
|
};
|
|
@@ -129,6 +131,7 @@ export type RecordsSubscribeDescriptor = {
|
|
|
129
131
|
method: DwnMethodName.Subscribe;
|
|
130
132
|
messageTimestamp: string;
|
|
131
133
|
filter: RecordsFilter;
|
|
134
|
+
permissionGrantId?: string;
|
|
132
135
|
dateSort?: DateSort;
|
|
133
136
|
pagination?: Pagination;
|
|
134
137
|
/**
|
|
@@ -267,9 +270,10 @@ export type RecordsDeleteDescriptor = {
|
|
|
267
270
|
method: DwnMethodName.Delete;
|
|
268
271
|
messageTimestamp: string;
|
|
269
272
|
recordId: string;
|
|
273
|
+
permissionGrantId?: string;
|
|
270
274
|
|
|
271
275
|
/**
|
|
272
276
|
* Denotes if all the descendent records should be purged.
|
|
273
277
|
*/
|
|
274
278
|
prune: boolean
|
|
275
|
-
};
|
|
279
|
+
};
|
|
@@ -110,15 +110,31 @@ export type SubscriptionEose = {
|
|
|
110
110
|
cursor : ProgressToken;
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Subscription-level error emitted after a subscription is already open.
|
|
115
|
+
*
|
|
116
|
+
* This is used for conditions that invalidate future delivery, such as a
|
|
117
|
+
* delegated subscription grant being revoked after the subscription was
|
|
118
|
+
* authorized. The cursor identifies the event that caused delivery to stop.
|
|
119
|
+
*/
|
|
120
|
+
export type SubscriptionError = {
|
|
121
|
+
type : 'error';
|
|
122
|
+
cursor : ProgressToken;
|
|
123
|
+
error : {
|
|
124
|
+
code : string;
|
|
125
|
+
detail : string;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
113
129
|
/**
|
|
114
130
|
* Discriminated union of subscription event types delivered to
|
|
115
131
|
* {@link SubscriptionListener} callbacks.
|
|
116
132
|
*/
|
|
117
|
-
export type SubscriptionMessage = SubscriptionEvent | SubscriptionEose;
|
|
133
|
+
export type SubscriptionMessage = SubscriptionEvent | SubscriptionEose | SubscriptionError;
|
|
118
134
|
|
|
119
135
|
/**
|
|
120
|
-
* Callback for {@link EventLog.subscribe}. Receives
|
|
121
|
-
*
|
|
136
|
+
* Callback for {@link EventLog.subscribe}. Receives events, catch-up EOSE
|
|
137
|
+
* markers, or subscription-level errors that stop delivery.
|
|
122
138
|
*/
|
|
123
139
|
export type SubscriptionListener = (message: SubscriptionMessage) => void;
|
|
124
140
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal protocol-bound scope shape used by permission grant authorization.
|
|
3
|
+
*
|
|
4
|
+
* Permission grants and candidate records/filters are projected down to these
|
|
5
|
+
* fields before matching so grant selection and server enforcement use the
|
|
6
|
+
* same rules.
|
|
7
|
+
*/
|
|
8
|
+
export type ProtocolScope = {
|
|
9
|
+
protocol?: string;
|
|
10
|
+
protocolPath?: string;
|
|
11
|
+
contextId?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Shared matching rules for protocol-scoped permission grants.
|
|
16
|
+
*
|
|
17
|
+
* Matching fails closed for invalid combinations: `protocolPath` and
|
|
18
|
+
* `contextId` are mutually exclusive, and either field requires `protocol`.
|
|
19
|
+
* `protocol` and `protocolPath` match by exact equality. `contextId` scopes
|
|
20
|
+
* match a context subtree: the target context must equal the scoped context or
|
|
21
|
+
* begin with the scoped context followed by `/`.
|
|
22
|
+
*/
|
|
23
|
+
export class PermissionScopeMatcher {
|
|
24
|
+
/**
|
|
25
|
+
* Determines whether a candidate target is within a grant scope.
|
|
26
|
+
*/
|
|
27
|
+
public static matches(scope: ProtocolScope, target: ProtocolScope): boolean {
|
|
28
|
+
if (scope.protocolPath !== undefined && scope.contextId !== undefined) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if ((scope.protocolPath !== undefined || scope.contextId !== undefined) && scope.protocol === undefined) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (scope.protocol !== undefined && scope.protocol !== target.protocol) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (scope.protocolPath !== undefined) {
|
|
41
|
+
return scope.protocolPath === target.protocolPath;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (scope.contextId !== undefined) {
|
|
45
|
+
return PermissionScopeMatcher.matchesContextId(scope.contextId, target.contextId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private static matchesContextId(scopeContextId: string, candidateContextId: unknown): boolean {
|
|
52
|
+
return typeof candidateContextId === 'string' &&
|
|
53
|
+
(candidateContextId === scopeContextId || candidateContextId.startsWith(scopeContextId + '/'));
|
|
54
|
+
}
|
|
55
|
+
}
|