@enbox/dwn-sdk-js 0.4.15 → 0.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/browser.mjs +1 -1
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/generated/precompiled-validators.js +734 -747
  4. package/dist/esm/generated/precompiled-validators.js.map +1 -1
  5. package/dist/esm/json-schemas/definitions.json +5 -0
  6. package/dist/esm/src/core/dwn-constant.js +2 -2
  7. package/dist/esm/src/core/dwn-error.js +0 -1
  8. package/dist/esm/src/core/dwn-error.js.map +1 -1
  9. package/dist/esm/src/core/protocol-authorization-validation.js +1 -24
  10. package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
  11. package/dist/esm/src/core/protocol-authorization.js +1 -5
  12. package/dist/esm/src/core/protocol-authorization.js.map +1 -1
  13. package/dist/esm/src/core/validation-state-reader.js +2 -3
  14. package/dist/esm/src/core/validation-state-reader.js.map +1 -1
  15. package/dist/esm/src/handlers/messages-query.js +2 -29
  16. package/dist/esm/src/handlers/messages-query.js.map +1 -1
  17. package/dist/esm/src/handlers/messages-subscribe.js +52 -1
  18. package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
  19. package/dist/esm/src/handlers/records-count.js +18 -10
  20. package/dist/esm/src/handlers/records-count.js.map +1 -1
  21. package/dist/esm/src/handlers/records-query.js +10 -7
  22. package/dist/esm/src/handlers/records-query.js.map +1 -1
  23. package/dist/esm/src/handlers/records-subscribe.js +10 -7
  24. package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
  25. package/dist/esm/src/handlers/records-write.js +8 -26
  26. package/dist/esm/src/handlers/records-write.js.map +1 -1
  27. package/dist/esm/src/index.js +1 -1
  28. package/dist/esm/src/index.js.map +1 -1
  29. package/dist/esm/src/interfaces/protocols-configure.js +4 -9
  30. package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
  31. package/dist/esm/src/store/index-level.js +143 -6
  32. package/dist/esm/src/store/index-level.js.map +1 -1
  33. package/dist/esm/src/store/message-store-level.js +162 -23
  34. package/dist/esm/src/store/message-store-level.js.map +1 -1
  35. package/dist/esm/src/store/storage-controller.js +1 -3
  36. package/dist/esm/src/store/storage-controller.js.map +1 -1
  37. package/dist/esm/src/types/protocols-types.js +0 -10
  38. package/dist/esm/src/types/protocols-types.js.map +1 -1
  39. package/dist/esm/src/utils/filter.js +35 -1
  40. package/dist/esm/src/utils/filter.js.map +1 -1
  41. package/dist/esm/src/utils/grant-key-coverage.js +4 -6
  42. package/dist/esm/src/utils/grant-key-coverage.js.map +1 -1
  43. package/dist/esm/src/utils/messages.js +40 -14
  44. package/dist/esm/src/utils/messages.js.map +1 -1
  45. package/dist/esm/src/utils/permission-scope.js +3 -9
  46. package/dist/esm/src/utils/permission-scope.js.map +1 -1
  47. package/dist/esm/src/utils/record-limit-occupancy.js +70 -202
  48. package/dist/esm/src/utils/record-limit-occupancy.js.map +1 -1
  49. package/dist/esm/src/utils/records.js +9 -9
  50. package/dist/esm/src/utils/records.js.map +1 -1
  51. package/dist/esm/src/utils/replication.js +14 -0
  52. package/dist/esm/src/utils/replication.js.map +1 -1
  53. package/dist/esm/tests/core/records-grant-authorization.spec.js +13 -0
  54. package/dist/esm/tests/core/records-grant-authorization.spec.js.map +1 -1
  55. package/dist/esm/tests/core/replication-replay-property.spec.js +2 -2
  56. package/dist/esm/tests/core/replication-replay-property.spec.js.map +1 -1
  57. package/dist/esm/tests/core/validation-read-closure.spec.js +1 -1
  58. package/dist/esm/tests/core/validation-read-closure.spec.js.map +1 -1
  59. package/dist/esm/tests/dwn.spec.js +2 -2
  60. package/dist/esm/tests/dwn.spec.js.map +1 -1
  61. package/dist/esm/tests/features/records-delivery.spec.js +2 -5
  62. package/dist/esm/tests/features/records-delivery.spec.js.map +1 -1
  63. package/dist/esm/tests/features/records-nested-query-scope.spec.js +138 -73
  64. package/dist/esm/tests/features/records-nested-query-scope.spec.js.map +1 -1
  65. package/dist/esm/tests/features/records-record-limit.spec.js +264 -95
  66. package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
  67. package/dist/esm/tests/fuzz/filter.fuzz.spec.js +19 -4
  68. package/dist/esm/tests/fuzz/filter.fuzz.spec.js.map +1 -1
  69. package/dist/esm/tests/handlers/messages-query.spec.js +7 -15
  70. package/dist/esm/tests/handlers/messages-query.spec.js.map +1 -1
  71. package/dist/esm/tests/handlers/messages-subscribe.spec.js +129 -1
  72. package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
  73. package/dist/esm/tests/handlers/records-write.spec.js +57 -3
  74. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  75. package/dist/esm/tests/store/index-level.spec.js +114 -1
  76. package/dist/esm/tests/store/index-level.spec.js.map +1 -1
  77. package/dist/esm/tests/store/message-store-cross-context.spec.js +46 -0
  78. package/dist/esm/tests/store/message-store-cross-context.spec.js.map +1 -0
  79. package/dist/esm/tests/store/message-store-level.spec.js +27 -0
  80. package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
  81. package/dist/esm/tests/store/message-store.spec.js +81 -0
  82. package/dist/esm/tests/store/message-store.spec.js.map +1 -1
  83. package/dist/esm/tests/utils/filters.spec.js +25 -4
  84. package/dist/esm/tests/utils/filters.spec.js.map +1 -1
  85. package/dist/esm/tests/utils/permission-scope.spec.js +4 -0
  86. package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -1
  87. package/dist/esm/tests/utils/records.spec.js +12 -0
  88. package/dist/esm/tests/utils/records.spec.js.map +1 -1
  89. package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js +3 -1
  90. package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js.map +1 -1
  91. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  92. package/dist/types/src/core/dwn-constant.d.ts +2 -2
  93. package/dist/types/src/core/dwn-error.d.ts +0 -1
  94. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  95. package/dist/types/src/core/protocol-authorization-validation.d.ts +0 -10
  96. package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
  97. package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
  98. package/dist/types/src/core/validation-state-reader.d.ts.map +1 -1
  99. package/dist/types/src/handlers/messages-query.d.ts +0 -2
  100. package/dist/types/src/handlers/messages-query.d.ts.map +1 -1
  101. package/dist/types/src/handlers/messages-subscribe.d.ts +16 -0
  102. package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
  103. package/dist/types/src/handlers/records-count.d.ts +1 -0
  104. package/dist/types/src/handlers/records-count.d.ts.map +1 -1
  105. package/dist/types/src/handlers/records-query.d.ts.map +1 -1
  106. package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
  107. package/dist/types/src/handlers/records-write.d.ts +3 -0
  108. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  109. package/dist/types/src/index.d.ts +3 -3
  110. package/dist/types/src/index.d.ts.map +1 -1
  111. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  112. package/dist/types/src/store/index-level.d.ts +37 -0
  113. package/dist/types/src/store/index-level.d.ts.map +1 -1
  114. package/dist/types/src/store/message-store-level.d.ts +23 -6
  115. package/dist/types/src/store/message-store-level.d.ts.map +1 -1
  116. package/dist/types/src/store/storage-controller.d.ts.map +1 -1
  117. package/dist/types/src/types/message-store.d.ts +23 -2
  118. package/dist/types/src/types/message-store.d.ts.map +1 -1
  119. package/dist/types/src/types/messages-types.d.ts +14 -0
  120. package/dist/types/src/types/messages-types.d.ts.map +1 -1
  121. package/dist/types/src/types/protocols-types.d.ts +7 -24
  122. package/dist/types/src/types/protocols-types.d.ts.map +1 -1
  123. package/dist/types/src/types/query-types.d.ts +8 -1
  124. package/dist/types/src/types/query-types.d.ts.map +1 -1
  125. package/dist/types/src/types/records-types.d.ts +2 -1
  126. package/dist/types/src/types/records-types.d.ts.map +1 -1
  127. package/dist/types/src/utils/filter.d.ts +14 -1
  128. package/dist/types/src/utils/filter.d.ts.map +1 -1
  129. package/dist/types/src/utils/grant-key-coverage.d.ts.map +1 -1
  130. package/dist/types/src/utils/messages.d.ts +8 -0
  131. package/dist/types/src/utils/messages.d.ts.map +1 -1
  132. package/dist/types/src/utils/permission-scope.d.ts +0 -2
  133. package/dist/types/src/utils/permission-scope.d.ts.map +1 -1
  134. package/dist/types/src/utils/record-limit-occupancy.d.ts +25 -12
  135. package/dist/types/src/utils/record-limit-occupancy.d.ts.map +1 -1
  136. package/dist/types/src/utils/records.d.ts +3 -2
  137. package/dist/types/src/utils/records.d.ts.map +1 -1
  138. package/dist/types/src/utils/replication.d.ts +8 -1
  139. package/dist/types/src/utils/replication.d.ts.map +1 -1
  140. package/dist/types/tests/features/records-delivery.spec.d.ts.map +1 -1
  141. package/dist/types/tests/features/records-nested-query-scope.spec.d.ts.map +1 -1
  142. package/dist/types/tests/features/records-record-limit.spec.d.ts.map +1 -1
  143. package/dist/types/tests/handlers/messages-query.spec.d.ts.map +1 -1
  144. package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
  145. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  146. package/dist/types/tests/store/message-store-cross-context.spec.d.ts +2 -0
  147. package/dist/types/tests/store/message-store-cross-context.spec.d.ts.map +1 -0
  148. package/dist/types/tests/store/message-store.spec.d.ts.map +1 -1
  149. package/package.json +4 -4
  150. package/src/core/dwn-constant.ts +2 -2
  151. package/src/core/dwn-error.ts +0 -2
  152. package/src/core/protocol-authorization-validation.ts +1 -33
  153. package/src/core/protocol-authorization.ts +0 -7
  154. package/src/core/validation-state-reader.ts +2 -3
  155. package/src/handlers/messages-query.ts +3 -41
  156. package/src/handlers/messages-subscribe.ts +67 -3
  157. package/src/handlers/records-count.ts +34 -13
  158. package/src/handlers/records-query.ts +14 -11
  159. package/src/handlers/records-subscribe.ts +18 -11
  160. package/src/handlers/records-write.ts +17 -32
  161. package/src/index.ts +3 -3
  162. package/src/interfaces/protocols-configure.ts +4 -13
  163. package/src/store/index-level.ts +197 -10
  164. package/src/store/message-store-level.ts +220 -27
  165. package/src/store/storage-controller.ts +1 -3
  166. package/src/types/message-store.ts +25 -2
  167. package/src/types/messages-types.ts +14 -0
  168. package/src/types/protocols-types.ts +7 -26
  169. package/src/types/query-types.ts +10 -2
  170. package/src/types/records-types.ts +2 -1
  171. package/src/utils/filter.ts +40 -3
  172. package/src/utils/grant-key-coverage.ts +4 -7
  173. package/src/utils/messages.ts +49 -14
  174. package/src/utils/permission-scope.ts +4 -11
  175. package/src/utils/record-limit-occupancy.ts +105 -293
  176. package/src/utils/records.ts +9 -9
  177. package/src/utils/replication.ts +19 -1
@@ -1,10 +1,11 @@
1
- import sinon from 'sinon';
2
1
  import { DataStream } from '../../src/utils/data-stream.js';
2
+ import { DateSort } from '../../src/types/records-types.js';
3
3
  import { DidKey } from '@enbox/dids';
4
4
  import { Dwn } from '../../src/dwn.js';
5
5
  import { DwnConstant } from '../../src/core/dwn-constant.js';
6
6
  import { DwnErrorCode } from '../../src/core/dwn-error.js';
7
7
  import { Jws } from '../../src/utils/jws.js';
8
+ import { Poller } from '../utils/poller.js';
8
9
  import { ProtocolsConfigure } from '../../src/interfaces/protocols-configure.js';
9
10
  import { RecordsRead } from '../../src/interfaces/records-read.js';
10
11
  import { sleep } from '@enbox/common';
@@ -34,7 +35,6 @@ export function testRecordsRecordLimit() {
34
35
  dwn = await Dwn.create({ didResolver, messageStore, dataStore, eventLog, resumableTaskStore });
35
36
  });
36
37
  beforeEach(async () => {
37
- sinon.restore();
38
38
  await messageStore.clear();
39
39
  await dataStore.clear();
40
40
  await resumableTaskStore.clear();
@@ -58,6 +58,7 @@ export function testRecordsRecordLimit() {
58
58
  protocol: input.protocol,
59
59
  protocolPath: input.protocolPath,
60
60
  parentContextId: input.parentContextId,
61
+ published: input.published,
61
62
  dateCreated: input.dateCreated,
62
63
  messageTimestamp: input.dateCreated,
63
64
  });
@@ -73,6 +74,7 @@ export function testRecordsRecordLimit() {
73
74
  protocolPath: input.protocolPath,
74
75
  ...(input.contextId === undefined ? {} : { contextId: input.contextId }),
75
76
  },
77
+ dateSort: input.dateSort,
76
78
  pagination: input.pagination,
77
79
  });
78
80
  const reply = await (input.targetDwn ?? dwn).processMessage(input.author.did, recordsQuery.message);
@@ -108,6 +110,7 @@ export function testRecordsRecordLimit() {
108
110
  filter: {
109
111
  protocol: input.protocol,
110
112
  protocolPath: input.protocolPath,
113
+ ...(input.contextId === undefined ? {} : { contextId: input.contextId }),
111
114
  },
112
115
  });
113
116
  const reply = await (input.targetDwn ?? dwn).processMessage(input.author.did, recordsSubscribe.message, { subscriptionHandler: () => { } });
@@ -124,7 +127,7 @@ export function testRecordsRecordLimit() {
124
127
  }
125
128
  }
126
129
  describe('ProtocolsConfigure validation', () => {
127
- it('should allow $recordLimit with valid max and strategy', async () => {
130
+ it('should allow $recordLimit with a valid max', async () => {
128
131
  const alice = await TestDataGenerator.generateDidKeyPersona();
129
132
  const definition = {
130
133
  protocol: 'http://example.com/record-limit',
@@ -132,7 +135,7 @@ export function testRecordsRecordLimit() {
132
135
  types: { message: {} },
133
136
  structure: {
134
137
  message: {
135
- $recordLimit: { max: 5, strategy: 'reject' },
138
+ $recordLimit: { max: 5 },
136
139
  }
137
140
  }
138
141
  };
@@ -141,10 +144,7 @@ export function testRecordsRecordLimit() {
141
144
  definition,
142
145
  });
143
146
  expect(protocolsConfigure.message.descriptor.definition).toBeDefined();
144
- expect(protocolsConfigure.message.descriptor.definition.structure.message.$recordLimit).toEqual({
145
- max: 5,
146
- strategy: 'reject',
147
- });
147
+ expect(protocolsConfigure.message.descriptor.definition.structure.message.$recordLimit).toEqual({ max: 5 });
148
148
  });
149
149
  it('should allow $recordLimit with max of 1', async () => {
150
150
  const alice = await TestDataGenerator.generateDidKeyPersona();
@@ -154,7 +154,7 @@ export function testRecordsRecordLimit() {
154
154
  types: { profile: {} },
155
155
  structure: {
156
156
  profile: {
157
- $recordLimit: { max: 1, strategy: 'reject' },
157
+ $recordLimit: { max: 1 },
158
158
  }
159
159
  }
160
160
  };
@@ -162,10 +162,7 @@ export function testRecordsRecordLimit() {
162
162
  signer: Jws.createSigner(alice),
163
163
  definition,
164
164
  });
165
- expect(protocolsConfigure.message.descriptor.definition.structure.profile.$recordLimit).toEqual({
166
- max: 1,
167
- strategy: 'reject',
168
- });
165
+ expect(protocolsConfigure.message.descriptor.definition.structure.profile.$recordLimit).toEqual({ max: 1 });
169
166
  });
170
167
  it('should reject $recordLimit above the supported max', async () => {
171
168
  const alice = await TestDataGenerator.generateDidKeyPersona();
@@ -175,7 +172,7 @@ export function testRecordsRecordLimit() {
175
172
  types: { message: {} },
176
173
  structure: {
177
174
  message: {
178
- $recordLimit: { max: DwnConstant.maxRecordLimit + 1, strategy: 'reject' },
175
+ $recordLimit: { max: DwnConstant.maxRecordLimit + 1 },
179
176
  }
180
177
  }
181
178
  };
@@ -185,27 +182,6 @@ export function testRecordsRecordLimit() {
185
182
  });
186
183
  await expect(promise).rejects.toThrow(DwnErrorCode.ProtocolsConfigureInvalidRecordLimit);
187
184
  });
188
- it('should allow $recordLimit with purgeOldest strategy (schema validation only)', async () => {
189
- const alice = await TestDataGenerator.generateDidKeyPersona();
190
- const definition = {
191
- protocol: 'http://example.com/record-limit',
192
- published: true,
193
- types: { status: {} },
194
- structure: {
195
- status: {
196
- $recordLimit: { max: 10, strategy: 'purgeOldest' },
197
- }
198
- }
199
- };
200
- const protocolsConfigure = await ProtocolsConfigure.create({
201
- signer: Jws.createSigner(alice),
202
- definition,
203
- });
204
- expect(protocolsConfigure.message.descriptor.definition.structure.status.$recordLimit).toEqual({
205
- max: 10,
206
- strategy: 'purgeOldest',
207
- });
208
- });
209
185
  it('should reject $recordLimit with max less than 1', async () => {
210
186
  const alice = await TestDataGenerator.generateDidKeyPersona();
211
187
  const definition = {
@@ -214,7 +190,7 @@ export function testRecordsRecordLimit() {
214
190
  types: { message: {} },
215
191
  structure: {
216
192
  message: {
217
- $recordLimit: { max: 0, strategy: 'reject' },
193
+ $recordLimit: { max: 0 },
218
194
  }
219
195
  }
220
196
  };
@@ -232,25 +208,7 @@ export function testRecordsRecordLimit() {
232
208
  types: { message: {} },
233
209
  structure: {
234
210
  message: {
235
- $recordLimit: { max: 2.5, strategy: 'reject' },
236
- }
237
- }
238
- };
239
- const promise = ProtocolsConfigure.create({
240
- signer: Jws.createSigner(alice),
241
- definition,
242
- });
243
- await expect(promise).rejects.toThrow('SchemaValidator');
244
- });
245
- it('should reject $recordLimit with missing strategy', async () => {
246
- const alice = await TestDataGenerator.generateDidKeyPersona();
247
- const definition = {
248
- protocol: 'http://example.com/record-limit',
249
- published: true,
250
- types: { message: {} },
251
- structure: {
252
- message: {
253
- $recordLimit: { max: 5 },
211
+ $recordLimit: { max: 2.5 },
254
212
  }
255
213
  }
256
214
  };
@@ -260,7 +218,7 @@ export function testRecordsRecordLimit() {
260
218
  });
261
219
  await expect(promise).rejects.toThrow('SchemaValidator');
262
220
  });
263
- it('should reject $recordLimit with invalid strategy', async () => {
221
+ it('should reject the removed $recordLimit strategy property', async () => {
264
222
  const alice = await TestDataGenerator.generateDidKeyPersona();
265
223
  const definition = {
266
224
  protocol: 'http://example.com/record-limit',
@@ -268,7 +226,7 @@ export function testRecordsRecordLimit() {
268
226
  types: { message: {} },
269
227
  structure: {
270
228
  message: {
271
- $recordLimit: { max: 5, strategy: 'invalidStrategy' },
229
+ $recordLimit: { max: 5, strategy: 'reject' },
272
230
  }
273
231
  }
274
232
  };
@@ -290,7 +248,7 @@ export function testRecordsRecordLimit() {
290
248
  structure: {
291
249
  room: {
292
250
  message: {
293
- $recordLimit: { max: 100, strategy: 'reject' },
251
+ $recordLimit: { max: 100 },
294
252
  }
295
253
  }
296
254
  }
@@ -300,13 +258,10 @@ export function testRecordsRecordLimit() {
300
258
  definition,
301
259
  });
302
260
  const roomRuleSet = protocolsConfigure.message.descriptor.definition.structure.room;
303
- expect(roomRuleSet.message.$recordLimit).toEqual({
304
- max: 100,
305
- strategy: 'reject',
306
- });
261
+ expect(roomRuleSet.message.$recordLimit).toEqual({ max: 100 });
307
262
  });
308
263
  });
309
- describe('read-time occupancy projection — reject strategy', () => {
264
+ describe('read-time occupancy projection', () => {
310
265
  it('should admit candidates and project Query, Read, and Count to top-ranked occupants', async () => {
311
266
  const alice = await TestDataGenerator.generateDidKeyPersona();
312
267
  const protocolDefinition = {
@@ -315,7 +270,7 @@ export function testRecordsRecordLimit() {
315
270
  types: { blob: {} },
316
271
  structure: {
317
272
  blob: {
318
- $recordLimit: { max: 2, strategy: 'reject' },
273
+ $recordLimit: { max: 2 },
319
274
  }
320
275
  }
321
276
  };
@@ -362,7 +317,7 @@ export function testRecordsRecordLimit() {
362
317
  types: { profile: {} },
363
318
  structure: {
364
319
  profile: {
365
- $recordLimit: { max: 1, strategy: 'reject' },
320
+ $recordLimit: { max: 1 },
366
321
  }
367
322
  }
368
323
  };
@@ -406,7 +361,7 @@ export function testRecordsRecordLimit() {
406
361
  structure: {
407
362
  room: {
408
363
  message: {
409
- $recordLimit: { max: 2, strategy: 'reject' },
364
+ $recordLimit: { max: 2 },
410
365
  }
411
366
  }
412
367
  }
@@ -489,6 +444,30 @@ export function testRecordsRecordLimit() {
489
444
  room2Messages[0].message.recordId,
490
445
  room2Messages[1].message.recordId,
491
446
  ]);
447
+ expect((await queryProtocolRecordIds({
448
+ author: alice,
449
+ protocol,
450
+ protocolPath: 'room/message',
451
+ contextId: room1Messages[0].message.contextId,
452
+ })).recordIds).toEqual([room1Messages[0].message.recordId]);
453
+ expect(await countProtocolRecords({
454
+ author: alice,
455
+ protocol,
456
+ protocolPath: 'room/message',
457
+ contextId: room1Messages[0].message.contextId,
458
+ })).toBe(1);
459
+ expect((await queryProtocolRecordIds({
460
+ author: alice,
461
+ protocol,
462
+ protocolPath: 'room/message',
463
+ contextId: room1Messages[2].message.contextId,
464
+ })).recordIds).toEqual([]);
465
+ expect(await countProtocolRecords({
466
+ author: alice,
467
+ protocol,
468
+ protocolPath: 'room/message',
469
+ contextId: room1Messages[2].message.contextId,
470
+ })).toBe(0);
492
471
  const broadQuery = await TestDataGenerator.generateRecordsQuery({
493
472
  author: alice,
494
473
  filter: {
@@ -500,6 +479,220 @@ export function testRecordsRecordLimit() {
500
479
  expect(broadReply.status.code).toBe(400);
501
480
  expect(broadReply.status.detail).toContain(DwnErrorCode.RecordsQueryNestedProtocolPathContextIdInvalid);
502
481
  });
482
+ it('should project ancestor queries independently across parent contexts', async () => {
483
+ const alice = await TestDataGenerator.generateDidKeyPersona();
484
+ const protocolDefinition = {
485
+ protocol: `http://record-limit-${TestDataGenerator.randomString(12)}.xyz`,
486
+ published: true,
487
+ types: {
488
+ channel: {},
489
+ community: {},
490
+ message: {},
491
+ },
492
+ structure: {
493
+ community: {
494
+ channel: {
495
+ message: {
496
+ $recordLimit: { max: 2 },
497
+ },
498
+ },
499
+ },
500
+ },
501
+ };
502
+ const protocol = protocolDefinition.protocol;
503
+ await installProtocol({ author: alice, definition: protocolDefinition });
504
+ const community = await writeProtocolRecord({
505
+ author: alice,
506
+ protocol,
507
+ protocolPath: 'community',
508
+ dateCreated: '2025-01-01T00:00:00.000000Z',
509
+ });
510
+ const channel1 = await writeProtocolRecord({
511
+ author: alice,
512
+ protocol,
513
+ protocolPath: 'community/channel',
514
+ parentContextId: community.message.contextId,
515
+ dateCreated: '2025-01-02T00:00:00.000000Z',
516
+ });
517
+ const channel2 = await writeProtocolRecord({
518
+ author: alice,
519
+ protocol,
520
+ protocolPath: 'community/channel',
521
+ parentContextId: community.message.contextId,
522
+ dateCreated: '2025-01-03T00:00:00.000000Z',
523
+ });
524
+ const { cursor: cursorBeforeMessages } = await eventLog.read(alice.did);
525
+ // Creation times are interleaved across channels so pagination proves
526
+ // that projection happens per direct parent before global ordering.
527
+ // Channel 2 arrives newest-first to prove occupancy ignores arrival order.
528
+ const channel1Messages = [
529
+ await writeProtocolRecord({
530
+ author: alice,
531
+ protocol,
532
+ protocolPath: 'community/channel/message',
533
+ parentContextId: channel1.message.contextId,
534
+ dateCreated: '2025-02-01T00:00:00.000000Z',
535
+ }),
536
+ await writeProtocolRecord({
537
+ author: alice,
538
+ protocol,
539
+ protocolPath: 'community/channel/message',
540
+ parentContextId: channel1.message.contextId,
541
+ published: true,
542
+ dateCreated: '2025-02-04T00:00:00.000000Z',
543
+ }),
544
+ await writeProtocolRecord({
545
+ author: alice,
546
+ protocol,
547
+ protocolPath: 'community/channel/message',
548
+ parentContextId: channel1.message.contextId,
549
+ published: true,
550
+ dateCreated: '2025-02-06T00:00:00.000000Z',
551
+ }),
552
+ ];
553
+ const channel2Tie1 = await writeProtocolRecord({
554
+ author: alice,
555
+ protocol,
556
+ protocolPath: 'community/channel/message',
557
+ parentContextId: channel2.message.contextId,
558
+ published: true,
559
+ dateCreated: '2025-02-03T00:00:00.000000Z',
560
+ });
561
+ const channel2Tie2 = await writeProtocolRecord({
562
+ author: alice,
563
+ protocol,
564
+ protocolPath: 'community/channel/message',
565
+ parentContextId: channel2.message.contextId,
566
+ published: true,
567
+ dateCreated: '2025-02-03T00:00:00.000000Z',
568
+ });
569
+ const channel2Oldest = await writeProtocolRecord({
570
+ author: alice,
571
+ protocol,
572
+ protocolPath: 'community/channel/message',
573
+ parentContextId: channel2.message.contextId,
574
+ published: true,
575
+ dateCreated: '2025-02-02T00:00:00.000000Z',
576
+ });
577
+ const channel2Messages = [channel2Oldest, channel2Tie1, channel2Tie2];
578
+ const channel2BoundaryOccupant = channel2Messages[1].message.recordId < channel2Messages[2].message.recordId
579
+ ? channel2Messages[1]
580
+ : channel2Messages[2];
581
+ const expectedRecordIds = [
582
+ channel1Messages[0].message.recordId,
583
+ channel2Messages[0].message.recordId,
584
+ channel2BoundaryOccupant.message.recordId,
585
+ channel1Messages[1].message.recordId,
586
+ ];
587
+ const queryInput = {
588
+ author: alice,
589
+ protocol,
590
+ protocolPath: 'community/channel/message',
591
+ contextId: community.message.contextId,
592
+ };
593
+ expect((await queryProtocolRecordIds(queryInput)).recordIds).toEqual(expectedRecordIds);
594
+ expect((await queryProtocolRecordIds({
595
+ ...queryInput,
596
+ dateSort: DateSort.CreatedDescending,
597
+ })).recordIds).toEqual([...expectedRecordIds].reverse());
598
+ expect(await countProtocolRecords(queryInput)).toBe(expectedRecordIds.length);
599
+ expect(await subscribeSnapshotRecordIds(queryInput)).toEqual(expectedRecordIds);
600
+ const page1 = await queryProtocolRecordIds({ ...queryInput, pagination: { limit: 2 } });
601
+ expect(page1.recordIds).toEqual(expectedRecordIds.slice(0, 2));
602
+ expect(page1.cursor).toBeDefined();
603
+ const page2 = await queryProtocolRecordIds({
604
+ ...queryInput,
605
+ pagination: { limit: 2, cursor: page1.cursor },
606
+ });
607
+ expect(page2.recordIds).toEqual(expectedRecordIds.slice(2));
608
+ expect(page2.cursor).toBeUndefined();
609
+ const descendingPage1 = await queryProtocolRecordIds({
610
+ ...queryInput,
611
+ dateSort: DateSort.CreatedDescending,
612
+ pagination: { limit: 2 },
613
+ });
614
+ expect(descendingPage1.recordIds).toEqual([...expectedRecordIds].reverse().slice(0, 2));
615
+ expect(descendingPage1.cursor).toBeDefined();
616
+ const descendingPage2 = await queryProtocolRecordIds({
617
+ ...queryInput,
618
+ dateSort: DateSort.CreatedDescending,
619
+ pagination: { limit: 2, cursor: descendingPage1.cursor },
620
+ });
621
+ expect(descendingPage2.recordIds).toEqual([...expectedRecordIds].reverse().slice(2));
622
+ expect(descendingPage2.cursor).toBeUndefined();
623
+ // Occupancy is ranked before requester visibility. The unpublished
624
+ // first channel-1 occupant still consumes a slot, so the later
625
+ // published over-limit candidate must remain hidden anonymously.
626
+ const publishedFilter = {
627
+ contextId: community.message.contextId,
628
+ protocol,
629
+ protocolPath: 'community/channel/message',
630
+ published: true,
631
+ };
632
+ const anonymousQuery = await TestDataGenerator.generateRecordsQuery({
633
+ anonymous: true,
634
+ filter: publishedFilter,
635
+ });
636
+ const anonymousReply = await dwn.processMessage(alice.did, anonymousQuery.message);
637
+ expect(anonymousReply.status.code).toBe(200);
638
+ expect(anonymousReply.entries?.map((entry) => entry.recordId)).toEqual([
639
+ channel2Messages[0].message.recordId,
640
+ channel2BoundaryOccupant.message.recordId,
641
+ channel1Messages[1].message.recordId,
642
+ ]);
643
+ const anonymousCount = await TestDataGenerator.generateRecordsCount({
644
+ anonymous: true,
645
+ filter: publishedFilter,
646
+ });
647
+ const anonymousCountReply = await dwn.processMessage(alice.did, anonymousCount.message);
648
+ expect(anonymousCountReply.status.code).toBe(200);
649
+ expect(anonymousCountReply.count).toBe(3);
650
+ const replayedRecordIds = [];
651
+ const cursorSubscribe = await TestDataGenerator.generateRecordsSubscribe({
652
+ author: alice,
653
+ cursor: cursorBeforeMessages,
654
+ filter: {
655
+ contextId: community.message.contextId,
656
+ protocol,
657
+ protocolPath: 'community/channel/message',
658
+ },
659
+ });
660
+ const cursorSubscribeReply = await dwn.processMessage(alice.did, cursorSubscribe.message, {
661
+ subscriptionHandler: (subscriptionMessage) => {
662
+ if (subscriptionMessage.type === 'event' && subscriptionMessage.event.message.descriptor.method === 'Write') {
663
+ replayedRecordIds.push(subscriptionMessage.event.message.recordId);
664
+ }
665
+ },
666
+ });
667
+ expect(cursorSubscribeReply.status.code).toBe(200);
668
+ await Poller.pollUntilSuccessOrTimeout(async () => {
669
+ expect(replayedRecordIds).toHaveLength(expectedRecordIds.length);
670
+ });
671
+ expect([...replayedRecordIds].sort()).toEqual([...expectedRecordIds].sort());
672
+ await cursorSubscribeReply.subscription?.close();
673
+ const deleteChannel1Occupant = await TestDataGenerator.generateRecordsDelete({
674
+ author: alice,
675
+ recordId: channel1Messages[0].message.recordId,
676
+ });
677
+ const deleteReply = await dwn.processMessage(alice.did, deleteChannel1Occupant.message);
678
+ expect(deleteReply.status.code).toBe(202);
679
+ const expectedAfterPromotion = [
680
+ channel2Messages[0].message.recordId,
681
+ channel2BoundaryOccupant.message.recordId,
682
+ channel1Messages[1].message.recordId,
683
+ channel1Messages[2].message.recordId,
684
+ ];
685
+ expect((await queryProtocolRecordIds(queryInput)).recordIds).toEqual(expectedAfterPromotion);
686
+ expect((await queryProtocolRecordIds({
687
+ author: alice,
688
+ protocol,
689
+ protocolPath: 'community/channel/message',
690
+ contextId: channel2.message.contextId,
691
+ })).recordIds).toEqual([
692
+ channel2Messages[0].message.recordId,
693
+ channel2BoundaryOccupant.message.recordId,
694
+ ]);
695
+ });
503
696
  it('should paginate over the projected occupant set for max:N scopes', async () => {
504
697
  const alice = await TestDataGenerator.generateDidKeyPersona();
505
698
  const protocolDefinition = {
@@ -508,7 +701,7 @@ export function testRecordsRecordLimit() {
508
701
  types: { item: {} },
509
702
  structure: {
510
703
  item: {
511
- $recordLimit: { max: 3, strategy: 'reject' },
704
+ $recordLimit: { max: 3 },
512
705
  }
513
706
  }
514
707
  };
@@ -551,7 +744,7 @@ export function testRecordsRecordLimit() {
551
744
  types: { status: {} },
552
745
  structure: {
553
746
  status: {
554
- $recordLimit: { max: 1, strategy: 'reject' },
747
+ $recordLimit: { max: 1 },
555
748
  }
556
749
  }
557
750
  };
@@ -633,10 +826,10 @@ export function testRecordsRecordLimit() {
633
826
  },
634
827
  structure: {
635
828
  note: {
636
- $recordLimit: { max: 2, strategy: 'reject' },
829
+ $recordLimit: { max: 2 },
637
830
  },
638
831
  profile: {
639
- $recordLimit: { max: 1, strategy: 'reject' },
832
+ $recordLimit: { max: 1 },
640
833
  }
641
834
  }
642
835
  };
@@ -712,7 +905,7 @@ export function testRecordsRecordLimit() {
712
905
  types: { blob: {} },
713
906
  structure: {
714
907
  blob: {
715
- $recordLimit: { max: 1, strategy: 'reject' },
908
+ $recordLimit: { max: 1 },
716
909
  }
717
910
  }
718
911
  };
@@ -734,30 +927,6 @@ export function testRecordsRecordLimit() {
734
927
  record.message.recordId,
735
928
  ]);
736
929
  });
737
- it('should reject purgeOldest strategy at write time with not-implemented error', async () => {
738
- const alice = await TestDataGenerator.generateDidKeyPersona();
739
- const protocolDefinition = {
740
- protocol: `http://record-limit-${TestDataGenerator.randomString(12)}.xyz`,
741
- published: true,
742
- types: { blob: {} },
743
- structure: {
744
- blob: {
745
- $recordLimit: { max: 1, strategy: 'purgeOldest' },
746
- }
747
- }
748
- };
749
- const protocol = protocolDefinition.protocol;
750
- await installProtocol({ author: alice, definition: protocolDefinition });
751
- const record = await TestDataGenerator.generateRecordsWrite({
752
- author: alice,
753
- recipient: alice.did,
754
- protocol,
755
- protocolPath: 'blob',
756
- });
757
- const reply = await dwn.processMessage(alice.did, record.message, { dataStream: record.dataStream });
758
- expect(reply.status.code).toBe(400);
759
- expect(reply.status.detail).toContain(DwnErrorCode.ProtocolAuthorizationRecordLimitStrategyNotImplemented);
760
- });
761
930
  });
762
931
  });
763
932
  }