@enbox/dwn-sdk-js 0.3.9 → 0.4.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 +4 -4
- package/dist/browser.mjs +11 -11
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +783 -1206
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/core/dwn-constant.js +5 -0
- package/dist/esm/src/core/dwn-constant.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +13 -7
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/grant-authorization.js +9 -18
- package/dist/esm/src/core/grant-authorization.js.map +1 -1
- package/dist/esm/src/core/message-reply.js.map +1 -1
- package/dist/esm/src/core/messages-grant-authorization.js +28 -61
- package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-action.js +25 -27
- package/dist/esm/src/core/protocol-authorization-action.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-validation.js +31 -69
- package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization.js +44 -118
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/core/protocols-grant-authorization.js +5 -5
- package/dist/esm/src/core/protocols-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/recording-validation-state-reader.js +84 -0
- package/dist/esm/src/core/recording-validation-state-reader.js.map +1 -0
- package/dist/esm/src/core/records-grant-authorization.js +11 -11
- package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/replication-apply.js +295 -0
- package/dist/esm/src/core/replication-apply.js.map +1 -0
- package/dist/esm/src/core/resumable-task-manager.js +5 -4
- package/dist/esm/src/core/resumable-task-manager.js.map +1 -1
- package/dist/esm/src/core/validation-state-reader.js +237 -0
- package/dist/esm/src/core/validation-state-reader.js.map +1 -0
- package/dist/esm/src/dwn.js +261 -16
- package/dist/esm/src/dwn.js.map +1 -1
- package/dist/esm/src/enums/dwn-interface-method.js +0 -1
- package/dist/esm/src/enums/dwn-interface-method.js.map +1 -1
- package/dist/esm/src/event-stream/durable-event-log.js +365 -0
- package/dist/esm/src/event-stream/durable-event-log.js.map +1 -0
- package/dist/esm/src/event-stream/event-emitter-wake-publisher.js +25 -0
- package/dist/esm/src/event-stream/event-emitter-wake-publisher.js.map +1 -0
- package/dist/esm/src/handlers/messages-query.js +159 -0
- package/dist/esm/src/handlers/messages-query.js.map +1 -0
- 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 +8 -8
- package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/protocols-configure.js +30 -49
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/protocols-query.js +1 -1
- package/dist/esm/src/handlers/protocols-query.js.map +1 -1
- package/dist/esm/src/handlers/records-count.js +20 -11
- package/dist/esm/src/handlers/records-count.js.map +1 -1
- package/dist/esm/src/handlers/records-delete.js +20 -16
- package/dist/esm/src/handlers/records-delete.js.map +1 -1
- package/dist/esm/src/handlers/records-query.js +35 -11
- package/dist/esm/src/handlers/records-query.js.map +1 -1
- package/dist/esm/src/handlers/records-read.js +52 -42
- package/dist/esm/src/handlers/records-read.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +107 -11
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +62 -116
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +7 -8
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/messages-query.js +49 -0
- package/dist/esm/src/interfaces/messages-query.js.map +1 -0
- package/dist/esm/src/interfaces/protocols-configure.js +7 -3
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-query.js +3 -4
- package/dist/esm/src/interfaces/protocols-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-count.js +4 -3
- package/dist/esm/src/interfaces/records-count.js.map +1 -1
- package/dist/esm/src/interfaces/records-delete.js +21 -4
- package/dist/esm/src/interfaces/records-delete.js.map +1 -1
- package/dist/esm/src/interfaces/records-query.js +4 -3
- package/dist/esm/src/interfaces/records-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-read.js +3 -3
- package/dist/esm/src/interfaces/records-read.js.map +1 -1
- package/dist/esm/src/interfaces/records-subscribe.js +4 -3
- package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/records-write.js +27 -13
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/protocols/permissions.js +27 -34
- package/dist/esm/src/protocols/permissions.js.map +1 -1
- package/dist/esm/src/store/index-level.js +24 -9
- package/dist/esm/src/store/index-level.js.map +1 -1
- package/dist/esm/src/store/level-wrapper.js +7 -0
- package/dist/esm/src/store/level-wrapper.js.map +1 -1
- package/dist/esm/src/store/message-store-level.js +536 -42
- package/dist/esm/src/store/message-store-level.js.map +1 -1
- package/dist/esm/src/store/storage-controller.js +58 -49
- package/dist/esm/src/store/storage-controller.js.map +1 -1
- package/dist/esm/src/types/message-types.js.map +1 -1
- package/dist/esm/src/types/validation-state-reader.js +2 -0
- package/dist/esm/src/types/validation-state-reader.js.map +1 -0
- package/dist/esm/src/utils/messages.js +17 -0
- package/dist/esm/src/utils/messages.js.map +1 -1
- package/dist/esm/src/utils/record-limit-occupancy.js +244 -0
- package/dist/esm/src/utils/record-limit-occupancy.js.map +1 -0
- package/dist/esm/src/utils/records.js +50 -14
- package/dist/esm/src/utils/records.js.map +1 -1
- package/dist/esm/src/utils/replication.js +85 -0
- package/dist/esm/src/utils/replication.js.map +1 -0
- package/dist/esm/tests/core/grant-authorization.spec.js +4 -4
- package/dist/esm/tests/core/grant-authorization.spec.js.map +1 -1
- package/dist/esm/tests/core/process-message-parity.spec.js +222 -0
- package/dist/esm/tests/core/process-message-parity.spec.js.map +1 -0
- package/dist/esm/tests/core/protocol-authorization.spec.js +5 -2
- package/dist/esm/tests/core/protocol-authorization.spec.js.map +1 -1
- package/dist/esm/tests/core/records-grant-authorization.spec.js +5 -5
- package/dist/esm/tests/core/records-grant-authorization.spec.js.map +1 -1
- package/dist/esm/tests/core/replication-apply.spec.js +274 -0
- package/dist/esm/tests/core/replication-apply.spec.js.map +1 -0
- package/dist/esm/tests/core/replication-replay-property.spec.js +350 -0
- package/dist/esm/tests/core/replication-replay-property.spec.js.map +1 -0
- package/dist/esm/tests/core/validation-read-closure.spec.js +469 -0
- package/dist/esm/tests/core/validation-read-closure.spec.js.map +1 -0
- package/dist/esm/tests/core/validation-state-reader.spec.js +716 -0
- package/dist/esm/tests/core/validation-state-reader.spec.js.map +1 -0
- package/dist/esm/tests/durable-event-log.spec.js +373 -0
- package/dist/esm/tests/durable-event-log.spec.js.map +1 -0
- package/dist/esm/tests/dwn.spec.js +620 -14
- package/dist/esm/tests/dwn.spec.js.map +1 -1
- package/dist/esm/tests/features/author-delegated-grant.spec.js +9 -6
- package/dist/esm/tests/features/author-delegated-grant.spec.js.map +1 -1
- package/dist/esm/tests/features/owner-delegated-grant.spec.js +1 -4
- package/dist/esm/tests/features/owner-delegated-grant.spec.js.map +1 -1
- package/dist/esm/tests/features/owner-signature.spec.js +1 -4
- package/dist/esm/tests/features/owner-signature.spec.js.map +1 -1
- package/dist/esm/tests/features/permissions.spec.js +165 -4
- package/dist/esm/tests/features/permissions.spec.js.map +1 -1
- package/dist/esm/tests/features/protocol-composition.spec.js +8 -11
- package/dist/esm/tests/features/protocol-composition.spec.js.map +1 -1
- package/dist/esm/tests/features/protocol-create-action.spec.js +1 -4
- package/dist/esm/tests/features/protocol-create-action.spec.js.map +1 -1
- package/dist/esm/tests/features/protocol-delete-action.spec.js +3 -5
- package/dist/esm/tests/features/protocol-delete-action.spec.js.map +1 -1
- package/dist/esm/tests/features/protocol-update-action.spec.js +3 -6
- package/dist/esm/tests/features/protocol-update-action.spec.js.map +1 -1
- package/dist/esm/tests/features/records-delivery.spec.js +1 -4
- package/dist/esm/tests/features/records-delivery.spec.js.map +1 -1
- package/dist/esm/tests/features/records-immutable.spec.js +1 -4
- package/dist/esm/tests/features/records-immutable.spec.js.map +1 -1
- package/dist/esm/tests/features/records-nested-query-scope.spec.js +281 -0
- package/dist/esm/tests/features/records-nested-query-scope.spec.js.map +1 -0
- package/dist/esm/tests/features/records-prune-cross-protocol.spec.js +3 -7
- package/dist/esm/tests/features/records-prune-cross-protocol.spec.js.map +1 -1
- package/dist/esm/tests/features/records-prune.spec.js +11 -22
- package/dist/esm/tests/features/records-prune.spec.js.map +1 -1
- package/dist/esm/tests/features/records-record-limit.spec.js +441 -231
- package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
- package/dist/esm/tests/features/records-squash.spec.js +6 -4
- package/dist/esm/tests/features/records-squash.spec.js.map +1 -1
- package/dist/esm/tests/features/records-tags.spec.js +1 -4
- package/dist/esm/tests/features/records-tags.spec.js.map +1 -1
- package/dist/esm/tests/features/resumable-tasks.spec.js +3 -5
- package/dist/esm/tests/features/resumable-tasks.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/message-store.fuzz.spec.js +1 -2
- package/dist/esm/tests/fuzz/message-store.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/process-message.fuzz.spec.js +2 -4
- package/dist/esm/tests/fuzz/process-message.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/schema-validation.fuzz.spec.js +1 -1
- package/dist/esm/tests/fuzz/schema-validation.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-query.spec.js +246 -0
- package/dist/esm/tests/handlers/messages-query.spec.js.map +1 -0
- package/dist/esm/tests/handlers/messages-read.spec.js +2 -5
- package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-subscribe.spec.js +3 -14
- package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-configure.spec.js +27 -26
- package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-query.spec.js +1 -4
- package/dist/esm/tests/handlers/protocols-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-count.spec.js +1 -4
- package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-delete.spec.js +312 -30
- package/dist/esm/tests/handlers/records-delete.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-query.spec.js +32 -9
- package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-read.spec.js +4 -4
- package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js +33 -14
- package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-write.spec.js +84 -38
- package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-delete.spec.js +69 -2
- package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-write.spec.js +4 -3
- package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
- package/dist/esm/tests/protocols/permissions.spec.js +55 -6
- package/dist/esm/tests/protocols/permissions.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/aggregator.spec.js +1 -4
- package/dist/esm/tests/scenarios/aggregator.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/deleted-record.spec.js +1 -4
- package/dist/esm/tests/scenarios/deleted-record.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/end-to-end-tests.spec.js +1 -4
- package/dist/esm/tests/scenarios/end-to-end-tests.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/nested-roles.spec.js +1 -4
- package/dist/esm/tests/scenarios/nested-roles.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/subscriptions.spec.js +1 -4
- package/dist/esm/tests/scenarios/subscriptions.spec.js.map +1 -1
- package/dist/esm/tests/store/message-store-level.spec.js +361 -5
- package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
- package/dist/esm/tests/store/message-store.spec.js +60 -0
- package/dist/esm/tests/store/message-store.spec.js.map +1 -1
- package/dist/esm/tests/test-event-stream.js +7 -3
- package/dist/esm/tests/test-event-stream.js.map +1 -1
- package/dist/esm/tests/test-stores.js +19 -9
- package/dist/esm/tests/test-stores.js.map +1 -1
- package/dist/esm/tests/test-suite.js +4 -4
- package/dist/esm/tests/test-suite.js.map +1 -1
- package/dist/esm/tests/utils/test-data-generator.js +25 -0
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/esm/tests/utils/test-stub-generator.js.map +1 -1
- package/dist/esm/tests/utils/test-validation-state-reader.js +16 -0
- package/dist/esm/tests/utils/test-validation-state-reader.js.map +1 -0
- package/dist/types/generated/precompiled-validators.d.ts +6 -6
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/core-protocol.d.ts +3 -3
- package/dist/types/src/core/core-protocol.d.ts.map +1 -1
- package/dist/types/src/core/dwn-constant.d.ts +5 -0
- package/dist/types/src/core/dwn-constant.d.ts.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +13 -7
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/grant-authorization.d.ts +5 -5
- package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/message-reply.d.ts +5 -4
- package/dist/types/src/core/message-reply.d.ts.map +1 -1
- package/dist/types/src/core/messages-grant-authorization.d.ts +12 -15
- package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-action.d.ts +4 -5
- package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-validation.d.ts +13 -16
- package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization.d.ts +8 -33
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/core/protocols-grant-authorization.d.ts +4 -4
- package/dist/types/src/core/protocols-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/recording-validation-state-reader.d.ts +75 -0
- package/dist/types/src/core/recording-validation-state-reader.d.ts.map +1 -0
- package/dist/types/src/core/records-grant-authorization.d.ts +8 -8
- package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/replication-apply.d.ts +129 -0
- package/dist/types/src/core/replication-apply.d.ts.map +1 -0
- package/dist/types/src/core/resumable-task-manager.d.ts +1 -1
- package/dist/types/src/core/resumable-task-manager.d.ts.map +1 -1
- package/dist/types/src/core/validation-state-reader.d.ts +79 -0
- package/dist/types/src/core/validation-state-reader.d.ts.map +1 -0
- package/dist/types/src/dwn.d.ts +47 -13
- package/dist/types/src/dwn.d.ts.map +1 -1
- package/dist/types/src/enums/dwn-interface-method.d.ts +0 -1
- package/dist/types/src/enums/dwn-interface-method.d.ts.map +1 -1
- package/dist/types/src/event-stream/durable-event-log.d.ts +69 -0
- package/dist/types/src/event-stream/durable-event-log.d.ts.map +1 -0
- package/dist/types/src/event-stream/event-emitter-wake-publisher.d.ts +13 -0
- package/dist/types/src/event-stream/event-emitter-wake-publisher.d.ts.map +1 -0
- package/dist/types/src/handlers/messages-query.d.ts +20 -0
- package/dist/types/src/handlers/messages-query.d.ts.map +1 -0
- package/dist/types/src/handlers/messages-read.d.ts +1 -1
- package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/protocols-configure.d.ts +0 -5
- package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/handlers/records-count.d.ts +2 -1
- package/dist/types/src/handlers/records-count.d.ts.map +1 -1
- package/dist/types/src/handlers/records-delete.d.ts +2 -2
- package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
- package/dist/types/src/handlers/records-query.d.ts +1 -1
- package/dist/types/src/handlers/records-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-read.d.ts +2 -1
- package/dist/types/src/handlers/records-read.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts +4 -5
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts +3 -11
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +16 -18
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-query.d.ts +23 -0
- package/dist/types/src/interfaces/messages-query.d.ts.map +1 -0
- package/dist/types/src/interfaces/protocols-configure.d.ts +3 -3
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-query.d.ts +2 -2
- package/dist/types/src/interfaces/protocols-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-count.d.ts +3 -3
- package/dist/types/src/interfaces/records-count.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-delete.d.ts +11 -3
- package/dist/types/src/interfaces/records-delete.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-query.d.ts +3 -3
- package/dist/types/src/interfaces/records-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-read.d.ts +3 -3
- package/dist/types/src/interfaces/records-read.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-subscribe.d.ts +3 -3
- package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write.d.ts +15 -7
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/protocols/permissions.d.ts +9 -12
- package/dist/types/src/protocols/permissions.d.ts.map +1 -1
- package/dist/types/src/store/index-level.d.ts +10 -1
- package/dist/types/src/store/index-level.d.ts.map +1 -1
- package/dist/types/src/store/level-wrapper.d.ts +5 -0
- package/dist/types/src/store/level-wrapper.d.ts.map +1 -1
- package/dist/types/src/store/message-store-level.d.ts +94 -14
- package/dist/types/src/store/message-store-level.d.ts.map +1 -1
- package/dist/types/src/store/storage-controller.d.ts +17 -14
- package/dist/types/src/store/storage-controller.d.ts.map +1 -1
- package/dist/types/src/types/message-store.d.ts +29 -1
- package/dist/types/src/types/message-store.d.ts.map +1 -1
- package/dist/types/src/types/message-types.d.ts +2 -0
- package/dist/types/src/types/message-types.d.ts.map +1 -1
- package/dist/types/src/types/messages-types.d.ts +21 -55
- package/dist/types/src/types/messages-types.d.ts.map +1 -1
- package/dist/types/src/types/method-handler.d.ts +2 -2
- package/dist/types/src/types/method-handler.d.ts.map +1 -1
- package/dist/types/src/types/permission-types.d.ts +1 -1
- package/dist/types/src/types/subscriptions.d.ts +50 -39
- package/dist/types/src/types/subscriptions.d.ts.map +1 -1
- package/dist/types/src/types/validation-state-reader.d.ts +116 -0
- package/dist/types/src/types/validation-state-reader.d.ts.map +1 -0
- package/dist/types/src/utils/messages.d.ts +10 -0
- package/dist/types/src/utils/messages.d.ts.map +1 -1
- package/dist/types/src/utils/record-limit-occupancy.d.ts +40 -0
- package/dist/types/src/utils/record-limit-occupancy.d.ts.map +1 -0
- package/dist/types/src/utils/records.d.ts +25 -3
- package/dist/types/src/utils/records.d.ts.map +1 -1
- package/dist/types/src/utils/replication.d.ts +22 -0
- package/dist/types/src/utils/replication.d.ts.map +1 -0
- package/dist/types/tests/core/process-message-parity.spec.d.ts +2 -0
- package/dist/types/tests/core/process-message-parity.spec.d.ts.map +1 -0
- package/dist/types/tests/core/replication-apply.spec.d.ts +2 -0
- package/dist/types/tests/core/replication-apply.spec.d.ts.map +1 -0
- package/dist/types/tests/core/replication-replay-property.spec.d.ts +2 -0
- package/dist/types/tests/core/replication-replay-property.spec.d.ts.map +1 -0
- package/dist/types/tests/core/validation-read-closure.spec.d.ts +2 -0
- package/dist/types/tests/core/validation-read-closure.spec.d.ts.map +1 -0
- package/dist/types/tests/core/validation-state-reader.spec.d.ts +2 -0
- package/dist/types/tests/core/validation-state-reader.spec.d.ts.map +1 -0
- package/dist/types/tests/durable-event-log.spec.d.ts +2 -0
- package/dist/types/tests/durable-event-log.spec.d.ts.map +1 -0
- package/dist/types/tests/dwn.spec.d.ts.map +1 -1
- package/dist/types/tests/features/author-delegated-grant.spec.d.ts.map +1 -1
- package/dist/types/tests/features/owner-delegated-grant.spec.d.ts.map +1 -1
- package/dist/types/tests/features/owner-signature.spec.d.ts.map +1 -1
- package/dist/types/tests/features/permissions.spec.d.ts.map +1 -1
- package/dist/types/tests/features/protocol-composition.spec.d.ts.map +1 -1
- package/dist/types/tests/features/protocol-create-action.spec.d.ts.map +1 -1
- package/dist/types/tests/features/protocol-delete-action.spec.d.ts.map +1 -1
- package/dist/types/tests/features/protocol-update-action.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-delivery.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-immutable.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-nested-query-scope.spec.d.ts +2 -0
- package/dist/types/tests/features/records-nested-query-scope.spec.d.ts.map +1 -0
- package/dist/types/tests/features/records-prune-cross-protocol.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-prune.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-record-limit.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-squash.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-tags.spec.d.ts.map +1 -1
- package/dist/types/tests/features/resumable-tasks.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-query.spec.d.ts +2 -0
- package/dist/types/tests/handlers/messages-query.spec.d.ts.map +1 -0
- 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/protocols-configure.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/protocols-query.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-delete.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-read.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/scenarios/deleted-record.spec.d.ts.map +1 -1
- package/dist/types/tests/scenarios/end-to-end-tests.spec.d.ts.map +1 -1
- package/dist/types/tests/scenarios/nested-roles.spec.d.ts.map +1 -1
- package/dist/types/tests/scenarios/subscriptions.spec.d.ts.map +1 -1
- package/dist/types/tests/store/message-store.spec.d.ts.map +1 -1
- package/dist/types/tests/test-event-stream.d.ts +1 -1
- package/dist/types/tests/test-event-stream.d.ts.map +1 -1
- package/dist/types/tests/test-stores.d.ts +5 -4
- package/dist/types/tests/test-stores.d.ts.map +1 -1
- package/dist/types/tests/test-suite.d.ts +1 -2
- package/dist/types/tests/test-suite.d.ts.map +1 -1
- package/dist/types/tests/utils/test-data-generator.d.ts +20 -1
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/dist/types/tests/utils/test-validation-state-reader.d.ts +15 -0
- package/dist/types/tests/utils/test-validation-state-reader.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/core/core-protocol.ts +3 -3
- package/src/core/dwn-constant.ts +7 -1
- package/src/core/dwn-error.ts +13 -7
- package/src/core/grant-authorization.ts +11 -20
- package/src/core/message-reply.ts +6 -5
- package/src/core/messages-grant-authorization.ts +37 -100
- package/src/core/protocol-authorization-action.ts +29 -38
- package/src/core/protocol-authorization-validation.ts +41 -98
- package/src/core/protocol-authorization.ts +56 -202
- package/src/core/protocols-grant-authorization.ts +9 -9
- package/src/core/recording-validation-state-reader.ts +130 -0
- package/src/core/records-grant-authorization.ts +16 -16
- package/src/core/replication-apply.ts +412 -0
- package/src/core/resumable-task-manager.ts +10 -8
- package/src/core/validation-state-reader.ts +350 -0
- package/src/dwn.ts +417 -30
- package/src/enums/dwn-interface-method.ts +0 -1
- package/src/event-stream/durable-event-log.ts +509 -0
- package/src/event-stream/event-emitter-wake-publisher.ts +34 -0
- package/src/handlers/messages-query.ts +203 -0
- package/src/handlers/messages-read.ts +9 -10
- package/src/handlers/messages-subscribe.ts +12 -13
- package/src/handlers/protocols-configure.ts +37 -58
- package/src/handlers/protocols-query.ts +1 -1
- package/src/handlers/records-count.ts +24 -17
- package/src/handlers/records-delete.ts +29 -27
- package/src/handlers/records-query.ts +38 -17
- package/src/handlers/records-read.ts +63 -50
- package/src/handlers/records-subscribe.ts +132 -19
- package/src/handlers/records-write.ts +77 -168
- package/src/index.ts +16 -20
- package/src/interfaces/messages-query.ts +70 -0
- package/src/interfaces/protocols-configure.ts +12 -4
- package/src/interfaces/protocols-query.ts +4 -5
- package/src/interfaces/records-count.ts +9 -4
- package/src/interfaces/records-delete.ts +25 -5
- package/src/interfaces/records-query.ts +9 -4
- package/src/interfaces/records-read.ts +4 -4
- package/src/interfaces/records-subscribe.ts +9 -4
- package/src/interfaces/records-write.ts +41 -13
- package/src/protocols/permissions.ts +32 -52
- package/src/store/index-level.ts +30 -9
- package/src/store/level-wrapper.ts +9 -1
- package/src/store/message-store-level.ts +757 -47
- package/src/store/storage-controller.ts +74 -63
- package/src/types/message-store.ts +45 -2
- package/src/types/message-types.ts +3 -1
- package/src/types/messages-types.ts +26 -65
- package/src/types/method-handler.ts +3 -3
- package/src/types/permission-types.ts +1 -1
- package/src/types/subscriptions.ts +53 -42
- package/src/types/validation-state-reader.ts +127 -0
- package/src/utils/messages.ts +25 -1
- package/src/utils/record-limit-occupancy.ts +377 -0
- package/src/utils/records.ts +69 -13
- package/src/utils/replication.ts +122 -0
- package/dist/esm/src/core/record-chain.js +0 -64
- package/dist/esm/src/core/record-chain.js.map +0 -1
- package/dist/esm/src/event-stream/event-emitter-event-log.js +0 -334
- package/dist/esm/src/event-stream/event-emitter-event-log.js.map +0 -1
- package/dist/esm/src/handlers/messages-sync.js +0 -581
- package/dist/esm/src/handlers/messages-sync.js.map +0 -1
- package/dist/esm/src/interfaces/messages-sync.js +0 -54
- package/dist/esm/src/interfaces/messages-sync.js.map +0 -1
- package/dist/esm/src/smt/smt-store-level.js +0 -103
- package/dist/esm/src/smt/smt-store-level.js.map +0 -1
- package/dist/esm/src/smt/smt-store-memory.js +0 -41
- package/dist/esm/src/smt/smt-store-memory.js.map +0 -1
- package/dist/esm/src/smt/smt-utils.js +0 -129
- package/dist/esm/src/smt/smt-utils.js.map +0 -1
- package/dist/esm/src/smt/sparse-merkle-tree.js +0 -577
- package/dist/esm/src/smt/sparse-merkle-tree.js.map +0 -1
- package/dist/esm/src/state-index/state-index-level.js +0 -191
- package/dist/esm/src/state-index/state-index-level.js.map +0 -1
- package/dist/esm/src/sync/records-projection.js +0 -228
- package/dist/esm/src/sync/records-projection.js.map +0 -1
- package/dist/esm/src/types/smt-types.js +0 -5
- package/dist/esm/src/types/smt-types.js.map +0 -1
- package/dist/esm/src/types/state-index.js +0 -2
- package/dist/esm/src/types/state-index.js.map +0 -1
- package/dist/esm/tests/event-emitter-event-log.spec.js +0 -499
- package/dist/esm/tests/event-emitter-event-log.spec.js.map +0 -1
- package/dist/esm/tests/handlers/messages-sync.spec.js +0 -1771
- package/dist/esm/tests/handlers/messages-sync.spec.js.map +0 -1
- package/dist/esm/tests/smt/smt-store-level.spec.js +0 -132
- package/dist/esm/tests/smt/smt-store-level.spec.js.map +0 -1
- package/dist/esm/tests/smt/sparse-merkle-tree.spec.js +0 -732
- package/dist/esm/tests/smt/sparse-merkle-tree.spec.js.map +0 -1
- package/dist/esm/tests/state-index/state-index-level.spec.js +0 -245
- package/dist/esm/tests/state-index/state-index-level.spec.js.map +0 -1
- package/dist/esm/tests/sync/records-projection.spec.js +0 -245
- package/dist/esm/tests/sync/records-projection.spec.js.map +0 -1
- package/dist/types/src/core/record-chain.d.ts +0 -24
- package/dist/types/src/core/record-chain.d.ts.map +0 -1
- package/dist/types/src/event-stream/event-emitter-event-log.d.ts +0 -80
- package/dist/types/src/event-stream/event-emitter-event-log.d.ts.map +0 -1
- package/dist/types/src/handlers/messages-sync.d.ts +0 -83
- package/dist/types/src/handlers/messages-sync.d.ts.map +0 -1
- package/dist/types/src/interfaces/messages-sync.d.ts +0 -23
- package/dist/types/src/interfaces/messages-sync.d.ts.map +0 -1
- package/dist/types/src/smt/smt-store-level.d.ts +0 -32
- package/dist/types/src/smt/smt-store-level.d.ts.map +0 -1
- package/dist/types/src/smt/smt-store-memory.d.ts +0 -22
- package/dist/types/src/smt/smt-store-memory.d.ts.map +0 -1
- package/dist/types/src/smt/smt-utils.d.ts +0 -58
- package/dist/types/src/smt/smt-utils.d.ts.map +0 -1
- package/dist/types/src/smt/sparse-merkle-tree.d.ts +0 -124
- package/dist/types/src/smt/sparse-merkle-tree.d.ts.map +0 -1
- package/dist/types/src/state-index/state-index-level.d.ts +0 -83
- package/dist/types/src/state-index/state-index-level.d.ts.map +0 -1
- package/dist/types/src/sync/records-projection.d.ts +0 -98
- package/dist/types/src/sync/records-projection.d.ts.map +0 -1
- package/dist/types/src/types/smt-types.d.ts +0 -81
- package/dist/types/src/types/smt-types.d.ts.map +0 -1
- package/dist/types/src/types/state-index.d.ts +0 -90
- package/dist/types/src/types/state-index.d.ts.map +0 -1
- package/dist/types/tests/event-emitter-event-log.spec.d.ts +0 -2
- package/dist/types/tests/event-emitter-event-log.spec.d.ts.map +0 -1
- package/dist/types/tests/handlers/messages-sync.spec.d.ts +0 -2
- package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +0 -1
- package/dist/types/tests/smt/smt-store-level.spec.d.ts +0 -2
- package/dist/types/tests/smt/smt-store-level.spec.d.ts.map +0 -1
- package/dist/types/tests/smt/sparse-merkle-tree.spec.d.ts +0 -2
- package/dist/types/tests/smt/sparse-merkle-tree.spec.d.ts.map +0 -1
- package/dist/types/tests/state-index/state-index-level.spec.d.ts +0 -2
- package/dist/types/tests/state-index/state-index-level.spec.d.ts.map +0 -1
- package/dist/types/tests/sync/records-projection.spec.d.ts +0 -2
- package/dist/types/tests/sync/records-projection.spec.d.ts.map +0 -1
- package/src/core/record-chain.ts +0 -99
- package/src/event-stream/event-emitter-event-log.ts +0 -430
- package/src/handlers/messages-sync.ts +0 -896
- package/src/interfaces/messages-sync.ts +0 -86
- package/src/smt/smt-store-level.ts +0 -143
- package/src/smt/smt-store-memory.ts +0 -53
- package/src/smt/smt-utils.ts +0 -149
- package/src/smt/sparse-merkle-tree.ts +0 -698
- package/src/state-index/state-index-level.ts +0 -239
- package/src/sync/records-projection.ts +0 -328
- package/src/types/smt-types.ts +0 -95
- package/src/types/state-index.ts +0 -100
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import sinon from 'sinon';
|
|
2
|
-
import
|
|
3
|
-
import { DataStoreLevel, EventEmitterEventLog, MessageStoreLevel, ResumableTaskStoreLevel, StateIndexLevel } from '../src/index.js';
|
|
2
|
+
import nestedProtocol from './vectors/protocol-definitions/nested.json' with { type: 'json' };
|
|
4
3
|
import { Dwn } from '../src/dwn.js';
|
|
5
|
-
import { TestDataGenerator } from './utils/test-data-generator.js';
|
|
6
4
|
import { TestEventLog } from './test-event-stream.js';
|
|
7
5
|
import { TestStores } from './test-stores.js';
|
|
6
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'bun:test';
|
|
7
|
+
import { DataStoreLevel, DataStream, DwnErrorCode, DwnMethodName, Jws, Message, MessageStoreLevel, RecordsDelete, RecordsRead, RecordsWrite, ResumableTaskStoreLevel, Time } from '../src/index.js';
|
|
8
|
+
import { defaultTestProtocolDefinition, TestDataGenerator } from './utils/test-data-generator.js';
|
|
8
9
|
import { DidKey, UniversalResolver } from '@enbox/dids';
|
|
9
10
|
export function testDwnClass() {
|
|
10
11
|
describe('DWN', () => {
|
|
@@ -12,7 +13,6 @@ export function testDwnClass() {
|
|
|
12
13
|
let messageStore;
|
|
13
14
|
let dataStore;
|
|
14
15
|
let resumableTaskStore;
|
|
15
|
-
let stateIndex;
|
|
16
16
|
let eventLog;
|
|
17
17
|
let dwn;
|
|
18
18
|
// important to follow the `beforeAll` and `afterAll` pattern to initialize and clean the stores in tests
|
|
@@ -23,9 +23,8 @@ export function testDwnClass() {
|
|
|
23
23
|
messageStore = stores.messageStore;
|
|
24
24
|
dataStore = stores.dataStore;
|
|
25
25
|
resumableTaskStore = stores.resumableTaskStore;
|
|
26
|
-
stateIndex = stores.stateIndex;
|
|
27
26
|
eventLog = TestEventLog.get();
|
|
28
|
-
dwn = await Dwn.create({ didResolver, messageStore, dataStore,
|
|
27
|
+
dwn = await Dwn.create({ didResolver, messageStore, dataStore, eventLog, resumableTaskStore });
|
|
29
28
|
});
|
|
30
29
|
beforeEach(async () => {
|
|
31
30
|
sinon.restore(); // wipe all stubs/spies/mocks/fakes from previous test
|
|
@@ -81,15 +80,11 @@ export function testDwnClass() {
|
|
|
81
80
|
const messageStoreStub = sinon.createStubInstance(MessageStoreLevel);
|
|
82
81
|
const dataStoreStub = sinon.createStubInstance(DataStoreLevel);
|
|
83
82
|
const resumableTaskStoreStub = sinon.createStubInstance(ResumableTaskStoreLevel);
|
|
84
|
-
const stateIndexStub = sinon.createStubInstance(StateIndexLevel);
|
|
85
|
-
const eventLogStub = sinon.createStubInstance(EventEmitterEventLog);
|
|
86
83
|
const dwnWithConfig = await Dwn.create({
|
|
87
84
|
tenantGate: blockAllTenantGate,
|
|
88
85
|
messageStore: messageStoreStub,
|
|
89
86
|
dataStore: dataStoreStub,
|
|
90
87
|
resumableTaskStore: resumableTaskStoreStub,
|
|
91
|
-
stateIndex: stateIndexStub,
|
|
92
|
-
eventLog: eventLogStub
|
|
93
88
|
});
|
|
94
89
|
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
95
90
|
const { author, message } = await TestDataGenerator.generateRecordsQuery({ author: alice });
|
|
@@ -109,15 +104,11 @@ export function testDwnClass() {
|
|
|
109
104
|
const messageStoreStub = sinon.createStubInstance(MessageStoreLevel);
|
|
110
105
|
const dataStoreStub = sinon.createStubInstance(DataStoreLevel);
|
|
111
106
|
const resumableTaskStoreStub = sinon.createStubInstance(ResumableTaskStoreLevel);
|
|
112
|
-
const stateIndexStub = sinon.createStubInstance(StateIndexLevel);
|
|
113
|
-
const eventLogStub = sinon.createStubInstance(EventEmitterEventLog);
|
|
114
107
|
const dwnWithConfig = await Dwn.create({
|
|
115
108
|
tenantGate: blockAllTenantGate,
|
|
116
109
|
messageStore: messageStoreStub,
|
|
117
110
|
dataStore: dataStoreStub,
|
|
118
111
|
resumableTaskStore: resumableTaskStoreStub,
|
|
119
|
-
stateIndex: stateIndexStub,
|
|
120
|
-
eventLog: eventLogStub
|
|
121
112
|
});
|
|
122
113
|
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
123
114
|
const { author, message } = await TestDataGenerator.generateRecordsQuery({ author: alice });
|
|
@@ -127,6 +118,621 @@ export function testDwnClass() {
|
|
|
127
118
|
expect(reply.status.detail).toBe(customMessage);
|
|
128
119
|
});
|
|
129
120
|
});
|
|
121
|
+
describe('applyReplicatedMessage()', () => {
|
|
122
|
+
it('returns Duplicate for an exact replay already in the message store', async () => {
|
|
123
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
124
|
+
await TestDataGenerator.installDefaultTestProtocol(dwn, alice);
|
|
125
|
+
const { message, dataStream } = await TestDataGenerator.generateRecordsWrite({ author: alice });
|
|
126
|
+
const initialReply = await dwn.processMessage(alice.did, message, { dataStream });
|
|
127
|
+
expect(initialReply.status.code).toBe(202);
|
|
128
|
+
const result = await dwn.applyReplicatedMessage(alice.did, message);
|
|
129
|
+
expect(result).toEqual({ kind: 'Duplicate' });
|
|
130
|
+
});
|
|
131
|
+
it('returns resolved cross-protocol role dependencies for replicated role-authorized queries', async () => {
|
|
132
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
133
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
134
|
+
const threadsProtocol = {
|
|
135
|
+
protocol: 'https://threads.example.com',
|
|
136
|
+
published: true,
|
|
137
|
+
types: {
|
|
138
|
+
participant: {},
|
|
139
|
+
thread: {},
|
|
140
|
+
},
|
|
141
|
+
structure: {
|
|
142
|
+
thread: {
|
|
143
|
+
participant: {
|
|
144
|
+
$role: true,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
const commentsProtocol = {
|
|
150
|
+
protocol: 'https://comments.example.com',
|
|
151
|
+
published: true,
|
|
152
|
+
uses: {
|
|
153
|
+
threads: threadsProtocol.protocol,
|
|
154
|
+
},
|
|
155
|
+
types: {
|
|
156
|
+
comment: {},
|
|
157
|
+
},
|
|
158
|
+
structure: {
|
|
159
|
+
thread: {
|
|
160
|
+
$ref: 'threads:thread',
|
|
161
|
+
comment: {},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
const threadsConfigure = await TestDataGenerator.generateProtocolsConfigure({
|
|
166
|
+
author: alice,
|
|
167
|
+
protocolDefinition: threadsProtocol,
|
|
168
|
+
});
|
|
169
|
+
expect((await dwn.processMessage(alice.did, threadsConfigure.message)).status.code).toBe(202);
|
|
170
|
+
const commentsConfigure = await TestDataGenerator.generateProtocolsConfigure({
|
|
171
|
+
author: alice,
|
|
172
|
+
protocolDefinition: commentsProtocol,
|
|
173
|
+
});
|
|
174
|
+
expect((await dwn.processMessage(alice.did, commentsConfigure.message)).status.code).toBe(202);
|
|
175
|
+
const query = await TestDataGenerator.generateRecordsQuery({
|
|
176
|
+
author: bob,
|
|
177
|
+
protocolRole: 'threads:thread/participant',
|
|
178
|
+
filter: {
|
|
179
|
+
contextId: 'thread-context',
|
|
180
|
+
protocol: commentsProtocol.protocol,
|
|
181
|
+
protocolPath: 'thread/comment',
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
const result = await dwn.applyReplicatedMessage(alice.did, query.message);
|
|
185
|
+
expect(result).toEqual({
|
|
186
|
+
kind: 'Incomplete',
|
|
187
|
+
missing: [{
|
|
188
|
+
type: 'Role',
|
|
189
|
+
contextPrefix: 'thread-context',
|
|
190
|
+
protocol: threadsProtocol.protocol,
|
|
191
|
+
protocolPath: 'thread/participant',
|
|
192
|
+
recipient: bob.did,
|
|
193
|
+
}],
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
it('classifies exact child replay as Duplicate before checking that the parent is still active', async () => {
|
|
197
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
198
|
+
const nestedProtocol = {
|
|
199
|
+
protocol: 'https://example.com/nested-duplicate',
|
|
200
|
+
published: false,
|
|
201
|
+
types: {
|
|
202
|
+
parent: {},
|
|
203
|
+
child: {},
|
|
204
|
+
},
|
|
205
|
+
structure: {
|
|
206
|
+
parent: {
|
|
207
|
+
child: {},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
const protocolsConfigure = await TestDataGenerator.generateProtocolsConfigure({
|
|
212
|
+
author: alice,
|
|
213
|
+
protocolDefinition: nestedProtocol,
|
|
214
|
+
});
|
|
215
|
+
expect((await dwn.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
216
|
+
const parent = await TestDataGenerator.generateRecordsWrite({
|
|
217
|
+
author: alice,
|
|
218
|
+
protocol: nestedProtocol.protocol,
|
|
219
|
+
protocolPath: 'parent',
|
|
220
|
+
});
|
|
221
|
+
expect((await dwn.processMessage(alice.did, parent.message, { dataStream: parent.dataStream })).status.code).toBe(202);
|
|
222
|
+
const child = await TestDataGenerator.generateRecordsWrite({
|
|
223
|
+
author: alice,
|
|
224
|
+
protocol: nestedProtocol.protocol,
|
|
225
|
+
protocolPath: 'parent/child',
|
|
226
|
+
parentContextId: parent.message.contextId,
|
|
227
|
+
});
|
|
228
|
+
expect((await dwn.processMessage(alice.did, child.message, { dataStream: child.dataStream })).status.code).toBe(202);
|
|
229
|
+
const parentDelete = await TestDataGenerator.generateRecordsDelete({
|
|
230
|
+
author: alice,
|
|
231
|
+
recordId: parent.message.recordId,
|
|
232
|
+
});
|
|
233
|
+
expect((await dwn.processMessage(alice.did, parentDelete.message)).status.code).toBe(202);
|
|
234
|
+
const result = await dwn.applyReplicatedMessage(alice.did, child.message);
|
|
235
|
+
expect(result).toEqual({ kind: 'Duplicate' });
|
|
236
|
+
});
|
|
237
|
+
it('classifies a replicated write older than the squash floor as Superseded', async () => {
|
|
238
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
239
|
+
const squashProtocol = {
|
|
240
|
+
protocol: 'https://example.com/replicated-squash',
|
|
241
|
+
published: true,
|
|
242
|
+
types: {
|
|
243
|
+
document: {},
|
|
244
|
+
patch: {},
|
|
245
|
+
},
|
|
246
|
+
structure: {
|
|
247
|
+
document: {
|
|
248
|
+
patch: {
|
|
249
|
+
$immutable: true,
|
|
250
|
+
$squash: true,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
const protocolsConfigure = await TestDataGenerator.generateProtocolsConfigure({
|
|
256
|
+
author: alice,
|
|
257
|
+
protocolDefinition: squashProtocol,
|
|
258
|
+
});
|
|
259
|
+
expect((await dwn.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
260
|
+
const document = await TestDataGenerator.generateRecordsWrite({
|
|
261
|
+
author: alice,
|
|
262
|
+
protocol: squashProtocol.protocol,
|
|
263
|
+
protocolPath: 'document',
|
|
264
|
+
});
|
|
265
|
+
expect((await dwn.processMessage(alice.did, document.message, { dataStream: document.dataStream })).status.code).toBe(202);
|
|
266
|
+
const squashTimestamp = Time.createOffsetTimestamp({ seconds: 10 });
|
|
267
|
+
const squashRecord = await TestDataGenerator.generateRecordsWrite({
|
|
268
|
+
author: alice,
|
|
269
|
+
protocol: squashProtocol.protocol,
|
|
270
|
+
protocolPath: 'document/patch',
|
|
271
|
+
parentContextId: document.message.contextId,
|
|
272
|
+
dateCreated: squashTimestamp,
|
|
273
|
+
messageTimestamp: squashTimestamp,
|
|
274
|
+
squash: true,
|
|
275
|
+
});
|
|
276
|
+
expect((await dwn.processMessage(alice.did, squashRecord.message, { dataStream: squashRecord.dataStream })).status.code).toBe(202);
|
|
277
|
+
// A replica replaying a pre-squash write is a normal multi-replica race: it must
|
|
278
|
+
// converge as a no-op, never surface as a terminal failure.
|
|
279
|
+
const olderTimestamp = Time.createOffsetTimestamp({ seconds: 5 });
|
|
280
|
+
const olderPatch = await TestDataGenerator.generateRecordsWrite({
|
|
281
|
+
author: alice,
|
|
282
|
+
protocol: squashProtocol.protocol,
|
|
283
|
+
protocolPath: 'document/patch',
|
|
284
|
+
parentContextId: document.message.contextId,
|
|
285
|
+
dateCreated: olderTimestamp,
|
|
286
|
+
messageTimestamp: olderTimestamp,
|
|
287
|
+
});
|
|
288
|
+
const result = await dwn.applyReplicatedMessage(alice.did, olderPatch.message, { dataStream: olderPatch.dataStream });
|
|
289
|
+
expect(result).toEqual({ kind: 'Superseded' });
|
|
290
|
+
});
|
|
291
|
+
it('converges to the same deleted state when a write and an older tombstone apply in either order', async () => {
|
|
292
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
293
|
+
// one shared ProtocolsConfigure message, processed on BOTH replicas so their
|
|
294
|
+
// state roots remain directly comparable
|
|
295
|
+
const protocolsConfigure = await TestDataGenerator.generateProtocolsConfigure({
|
|
296
|
+
author: alice,
|
|
297
|
+
protocolDefinition: defaultTestProtocolDefinition,
|
|
298
|
+
});
|
|
299
|
+
expect((await dwn.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
300
|
+
// initial write -> tombstone -> newer update, with strictly increasing timestamps
|
|
301
|
+
const datePublished = '2025-01-05T00:00:00.000000Z';
|
|
302
|
+
const initialWrite = await TestDataGenerator.generateRecordsWrite({ author: alice });
|
|
303
|
+
await Time.minimalSleep();
|
|
304
|
+
const recordsDelete = await RecordsDelete.create({
|
|
305
|
+
recordId: initialWrite.message.recordId,
|
|
306
|
+
signer: Jws.createSigner(alice),
|
|
307
|
+
});
|
|
308
|
+
await Time.minimalSleep();
|
|
309
|
+
const updateDataBytes = TestDataGenerator.randomBytes(32);
|
|
310
|
+
const update = await RecordsWrite.createFrom({
|
|
311
|
+
recordsWriteMessage: initialWrite.message,
|
|
312
|
+
data: updateDataBytes,
|
|
313
|
+
published: true,
|
|
314
|
+
datePublished,
|
|
315
|
+
tags: { team: 'blue' },
|
|
316
|
+
signer: Jws.createSigner(alice),
|
|
317
|
+
});
|
|
318
|
+
// replica A: initial write and the newer update land first, the older tombstone last
|
|
319
|
+
expect(await dwn.applyReplicatedMessage(alice.did, initialWrite.message, { dataStream: DataStream.fromBytes(initialWrite.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
320
|
+
expect(await dwn.applyReplicatedMessage(alice.did, update.message, { dataStream: DataStream.fromBytes(updateDataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
321
|
+
expect(await dwn.applyReplicatedMessage(alice.did, recordsDelete.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
322
|
+
// replica B: the tombstone lands before the newer update
|
|
323
|
+
const messageStoreB = new MessageStoreLevel({
|
|
324
|
+
location: 'TEST-MESSAGESTORE-DELETEWINS',
|
|
325
|
+
});
|
|
326
|
+
const dataStoreB = new DataStoreLevel({ blockstoreLocation: 'TEST-DATASTORE-DELETEWINS' });
|
|
327
|
+
const resumableTaskStoreB = new ResumableTaskStoreLevel({ location: 'TEST-RESUMABLE-TASK-STORE-DELETEWINS' });
|
|
328
|
+
const dwnB = await Dwn.create({
|
|
329
|
+
didResolver,
|
|
330
|
+
messageStore: messageStoreB,
|
|
331
|
+
dataStore: dataStoreB,
|
|
332
|
+
resumableTaskStore: resumableTaskStoreB,
|
|
333
|
+
});
|
|
334
|
+
try {
|
|
335
|
+
await messageStoreB.clear();
|
|
336
|
+
await dataStoreB.clear();
|
|
337
|
+
await resumableTaskStoreB.clear();
|
|
338
|
+
expect((await dwnB.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
339
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, initialWrite.message, { dataStream: DataStream.fromBytes(initialWrite.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
340
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, recordsDelete.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
341
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, update.message, { dataStream: DataStream.fromBytes(updateDataBytes) })).toEqual({ kind: 'Superseded' });
|
|
342
|
+
// both replicas read the record as deleted and retain the update-derived tombstone visibility
|
|
343
|
+
const readA = await RecordsRead.create({
|
|
344
|
+
signer: Jws.createSigner(alice),
|
|
345
|
+
filter: { recordId: initialWrite.message.recordId },
|
|
346
|
+
});
|
|
347
|
+
expect((await dwn.processMessage(alice.did, readA.message)).status.code).toBe(404);
|
|
348
|
+
const readB = await RecordsRead.create({
|
|
349
|
+
signer: Jws.createSigner(alice),
|
|
350
|
+
filter: { recordId: initialWrite.message.recordId },
|
|
351
|
+
});
|
|
352
|
+
expect((await dwnB.processMessage(alice.did, readB.message)).status.code).toBe(404);
|
|
353
|
+
const tombstoneFilter = {
|
|
354
|
+
'method': 'Delete',
|
|
355
|
+
'tag.team': 'blue',
|
|
356
|
+
'published': true,
|
|
357
|
+
'datePublished': datePublished,
|
|
358
|
+
};
|
|
359
|
+
const { messages: tombstonesA } = await messageStore.query(alice.did, [tombstoneFilter]);
|
|
360
|
+
const { messages: tombstonesB } = await messageStoreB.query(alice.did, [tombstoneFilter]);
|
|
361
|
+
expect(tombstonesA.length).toBe(1);
|
|
362
|
+
expect(tombstonesB.length).toBe(1);
|
|
363
|
+
expect(await Message.getCid(tombstonesA[0])).toBe(await Message.getCid(recordsDelete.message));
|
|
364
|
+
expect(await Message.getCid(tombstonesB[0])).toBe(await Message.getCid(recordsDelete.message));
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
await dwnB.close();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
it('rejects a tombstone-beaten replicated write when its data does not match its descriptor', async () => {
|
|
371
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
372
|
+
const protocolsConfigure = await TestDataGenerator.generateProtocolsConfigure({
|
|
373
|
+
author: alice,
|
|
374
|
+
protocolDefinition: defaultTestProtocolDefinition,
|
|
375
|
+
});
|
|
376
|
+
expect((await dwn.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
377
|
+
const initialWrite = await TestDataGenerator.generateRecordsWrite({ author: alice });
|
|
378
|
+
expect(await dwn.applyReplicatedMessage(alice.did, initialWrite.message, { dataStream: DataStream.fromBytes(initialWrite.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
379
|
+
const recordsDelete = await RecordsDelete.create({
|
|
380
|
+
recordId: initialWrite.message.recordId,
|
|
381
|
+
signer: Jws.createSigner(alice),
|
|
382
|
+
});
|
|
383
|
+
expect(await dwn.applyReplicatedMessage(alice.did, recordsDelete.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
384
|
+
await Time.minimalSleep();
|
|
385
|
+
const updateDataBytes = TestDataGenerator.randomBytes(32);
|
|
386
|
+
const update = await RecordsWrite.createFrom({
|
|
387
|
+
recordsWriteMessage: initialWrite.message,
|
|
388
|
+
data: updateDataBytes,
|
|
389
|
+
tags: { team: 'blue' },
|
|
390
|
+
signer: Jws.createSigner(alice),
|
|
391
|
+
});
|
|
392
|
+
const malformedDataBytes = TestDataGenerator.randomBytes(32);
|
|
393
|
+
const result = await dwn.applyReplicatedMessage(alice.did, update.message, { dataStream: DataStream.fromBytes(malformedDataBytes) });
|
|
394
|
+
expect(result.kind).toBe('Invalid');
|
|
395
|
+
expect(result.reason).toContain(DwnErrorCode.RecordsWriteDataCidMismatch);
|
|
396
|
+
const updateCid = await Message.getCid(update.message);
|
|
397
|
+
const { messages } = await messageStore.query(alice.did, [{ recordId: initialWrite.message.recordId }]);
|
|
398
|
+
const messageCids = await Promise.all(messages.map(message => Message.getCid(message)));
|
|
399
|
+
expect(messageCids).not.toContain(updateCid);
|
|
400
|
+
const { messages: reindexedTombstones } = await messageStore.query(alice.did, [{
|
|
401
|
+
method: DwnMethodName.Delete,
|
|
402
|
+
'tag.team': 'blue',
|
|
403
|
+
}]);
|
|
404
|
+
expect(reindexedTombstones.length).toBe(0);
|
|
405
|
+
});
|
|
406
|
+
it('reports missing data for tombstone-beaten replicated writes without data', async () => {
|
|
407
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
408
|
+
await TestDataGenerator.installDefaultTestProtocol(dwn, alice);
|
|
409
|
+
const scenarios = [
|
|
410
|
+
{ dataBytes: TestDataGenerator.randomBytes(32), storage: 'inline' },
|
|
411
|
+
{ dataBytes: TestDataGenerator.randomBytes(31_000), storage: 'data-store' },
|
|
412
|
+
];
|
|
413
|
+
for (const { dataBytes, storage } of scenarios) {
|
|
414
|
+
const initialWrite = await TestDataGenerator.generateRecordsWrite({ author: alice, data: dataBytes });
|
|
415
|
+
expect(await dwn.applyReplicatedMessage(alice.did, initialWrite.message, { dataStream: DataStream.fromBytes(dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
416
|
+
const recordsDelete = await RecordsDelete.create({
|
|
417
|
+
recordId: initialWrite.message.recordId,
|
|
418
|
+
signer: Jws.createSigner(alice),
|
|
419
|
+
});
|
|
420
|
+
expect(await dwn.applyReplicatedMessage(alice.did, recordsDelete.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
421
|
+
await Time.minimalSleep();
|
|
422
|
+
const update = await RecordsWrite.createFrom({
|
|
423
|
+
recordsWriteMessage: initialWrite.message,
|
|
424
|
+
tags: { storage },
|
|
425
|
+
signer: Jws.createSigner(alice),
|
|
426
|
+
});
|
|
427
|
+
const result = await dwn.applyReplicatedMessage(alice.did, update.message);
|
|
428
|
+
expect(result).toEqual({
|
|
429
|
+
kind: 'Incomplete',
|
|
430
|
+
missing: [{
|
|
431
|
+
type: 'RecordData',
|
|
432
|
+
recordId: update.message.recordId,
|
|
433
|
+
dataCid: update.message.descriptor.dataCid,
|
|
434
|
+
protocol: update.message.descriptor.protocol,
|
|
435
|
+
}],
|
|
436
|
+
});
|
|
437
|
+
const updateCid = await Message.getCid(update.message);
|
|
438
|
+
expect(await messageStore.get(alice.did, updateCid)).toBeUndefined();
|
|
439
|
+
const { messages: reindexedTombstones } = await messageStore.query(alice.did, [{
|
|
440
|
+
method: DwnMethodName.Delete,
|
|
441
|
+
'tag.storage': storage,
|
|
442
|
+
}]);
|
|
443
|
+
expect(reindexedTombstones.length).toBe(0);
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
it('accepts a tombstone-beaten replicated write with a valid large data stream', async () => {
|
|
447
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
448
|
+
await TestDataGenerator.installDefaultTestProtocol(dwn, alice);
|
|
449
|
+
const initialWrite = await TestDataGenerator.generateRecordsWrite({ author: alice });
|
|
450
|
+
expect(await dwn.applyReplicatedMessage(alice.did, initialWrite.message, { dataStream: DataStream.fromBytes(initialWrite.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
451
|
+
const recordsDelete = await RecordsDelete.create({
|
|
452
|
+
recordId: initialWrite.message.recordId,
|
|
453
|
+
signer: Jws.createSigner(alice),
|
|
454
|
+
});
|
|
455
|
+
expect(await dwn.applyReplicatedMessage(alice.did, recordsDelete.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
456
|
+
await Time.minimalSleep();
|
|
457
|
+
const updateDataBytes = TestDataGenerator.randomBytes(31_000);
|
|
458
|
+
const update = await RecordsWrite.createFrom({
|
|
459
|
+
recordsWriteMessage: initialWrite.message,
|
|
460
|
+
data: updateDataBytes,
|
|
461
|
+
tags: { storage: 'data-store' },
|
|
462
|
+
signer: Jws.createSigner(alice),
|
|
463
|
+
});
|
|
464
|
+
expect(await dwn.applyReplicatedMessage(alice.did, update.message, { dataStream: DataStream.fromBytes(updateDataBytes) })).toEqual({ kind: 'Superseded' });
|
|
465
|
+
const updateCid = await Message.getCid(update.message);
|
|
466
|
+
expect(await messageStore.get(alice.did, updateCid)).toBeDefined();
|
|
467
|
+
const { messages: reindexedTombstones } = await messageStore.query(alice.did, [{
|
|
468
|
+
method: DwnMethodName.Delete,
|
|
469
|
+
'tag.storage': 'data-store',
|
|
470
|
+
}]);
|
|
471
|
+
expect(reindexedTombstones.length).toBe(1);
|
|
472
|
+
expect(await Message.getCid(reindexedTombstones[0])).toBe(await Message.getCid(recordsDelete.message));
|
|
473
|
+
});
|
|
474
|
+
it('applies the handler stale-tombstone gate on resumable-task replay', async () => {
|
|
475
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
476
|
+
await TestDataGenerator.installDefaultTestProtocol(dwn, alice);
|
|
477
|
+
const initialWrite = await TestDataGenerator.generateRecordsWrite({ author: alice });
|
|
478
|
+
expect((await dwn.processMessage(alice.did, initialWrite.message, { dataStream: initialWrite.dataStream })).status.code).toBe(202);
|
|
479
|
+
// generate the losing delete first so it is older than the tombstone admitted below
|
|
480
|
+
const staleDelete = await RecordsDelete.create({
|
|
481
|
+
recordId: initialWrite.message.recordId,
|
|
482
|
+
signer: Jws.createSigner(alice),
|
|
483
|
+
});
|
|
484
|
+
await Time.minimalSleep();
|
|
485
|
+
const recordsDelete = await RecordsDelete.create({
|
|
486
|
+
recordId: initialWrite.message.recordId,
|
|
487
|
+
signer: Jws.createSigner(alice),
|
|
488
|
+
});
|
|
489
|
+
expect((await dwn.processMessage(alice.did, recordsDelete.message)).status.code).toBe(202);
|
|
490
|
+
// replaying the beaten delete through the task path must be a no-op — the same lattice
|
|
491
|
+
// gate as admission — leaving the canonical tombstone as the record's newest message
|
|
492
|
+
await dwn['storageController'].performRecordsDelete({ tenant: alice.did, message: staleDelete.message });
|
|
493
|
+
const tombstoneCid = await Message.getCid(recordsDelete.message);
|
|
494
|
+
const staleDeleteCid = await Message.getCid(staleDelete.message);
|
|
495
|
+
expect(await messageStore.get(alice.did, tombstoneCid)).toBeDefined();
|
|
496
|
+
expect(await messageStore.get(alice.did, staleDeleteCid)).toBeUndefined();
|
|
497
|
+
});
|
|
498
|
+
it('converges competing plain tombstones to one canonical winner in either order', async () => {
|
|
499
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
500
|
+
const protocolsConfigure = await TestDataGenerator.generateProtocolsConfigure({
|
|
501
|
+
author: alice,
|
|
502
|
+
protocolDefinition: defaultTestProtocolDefinition,
|
|
503
|
+
});
|
|
504
|
+
expect((await dwn.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
505
|
+
// two independently authored plain tombstones for the same record, d1 older than d2
|
|
506
|
+
const initialWrite = await TestDataGenerator.generateRecordsWrite({ author: alice });
|
|
507
|
+
const d1 = await RecordsDelete.create({
|
|
508
|
+
recordId: initialWrite.message.recordId,
|
|
509
|
+
signer: Jws.createSigner(alice),
|
|
510
|
+
});
|
|
511
|
+
await Time.minimalSleep();
|
|
512
|
+
const d2 = await RecordsDelete.create({
|
|
513
|
+
recordId: initialWrite.message.recordId,
|
|
514
|
+
signer: Jws.createSigner(alice),
|
|
515
|
+
});
|
|
516
|
+
// replica A: d1 lands first, then d2 displaces it
|
|
517
|
+
expect(await dwn.applyReplicatedMessage(alice.did, initialWrite.message, { dataStream: DataStream.fromBytes(initialWrite.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
518
|
+
expect(await dwn.applyReplicatedMessage(alice.did, d1.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
519
|
+
expect(await dwn.applyReplicatedMessage(alice.did, d2.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
520
|
+
// replica B: d2 lands first; the beaten d1 converges as a Superseded no-op
|
|
521
|
+
const messageStoreB = new MessageStoreLevel({
|
|
522
|
+
location: 'TEST-MESSAGESTORE-DELETEWINS',
|
|
523
|
+
});
|
|
524
|
+
const dataStoreB = new DataStoreLevel({ blockstoreLocation: 'TEST-DATASTORE-DELETEWINS' });
|
|
525
|
+
const resumableTaskStoreB = new ResumableTaskStoreLevel({ location: 'TEST-RESUMABLE-TASK-STORE-DELETEWINS' });
|
|
526
|
+
const dwnB = await Dwn.create({
|
|
527
|
+
didResolver,
|
|
528
|
+
messageStore: messageStoreB,
|
|
529
|
+
dataStore: dataStoreB,
|
|
530
|
+
resumableTaskStore: resumableTaskStoreB,
|
|
531
|
+
});
|
|
532
|
+
try {
|
|
533
|
+
await messageStoreB.clear();
|
|
534
|
+
await dataStoreB.clear();
|
|
535
|
+
await resumableTaskStoreB.clear();
|
|
536
|
+
expect((await dwnB.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
537
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, initialWrite.message, { dataStream: DataStream.fromBytes(initialWrite.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
538
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, d2.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
539
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, d1.message)).toEqual({ kind: 'Superseded' });
|
|
540
|
+
// both replicas hold d2 as the canonical tombstone
|
|
541
|
+
const d1Cid = await Message.getCid(d1.message);
|
|
542
|
+
const d2Cid = await Message.getCid(d2.message);
|
|
543
|
+
expect(await messageStore.get(alice.did, d2Cid)).toBeDefined();
|
|
544
|
+
expect(await messageStore.get(alice.did, d1Cid)).toBeUndefined();
|
|
545
|
+
expect(await messageStoreB.get(alice.did, d2Cid)).toBeDefined();
|
|
546
|
+
expect(await messageStoreB.get(alice.did, d1Cid)).toBeUndefined();
|
|
547
|
+
}
|
|
548
|
+
finally {
|
|
549
|
+
await dwnB.close();
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
it('a prune beats a newer plain delete in either order and purges descendants on both replicas', async () => {
|
|
553
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
554
|
+
const nestedProtocolDefinition = nestedProtocol;
|
|
555
|
+
const protocolsConfigure = await TestDataGenerator.generateProtocolsConfigure({
|
|
556
|
+
author: alice,
|
|
557
|
+
protocolDefinition: nestedProtocolDefinition,
|
|
558
|
+
});
|
|
559
|
+
expect((await dwn.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
560
|
+
// parent foo with child bar, then a prune of foo and a NEWER plain delete of foo
|
|
561
|
+
const foo = await TestDataGenerator.generateRecordsWrite({
|
|
562
|
+
author: alice,
|
|
563
|
+
protocol: nestedProtocolDefinition.protocol,
|
|
564
|
+
protocolPath: 'foo',
|
|
565
|
+
schema: nestedProtocolDefinition.types.foo.schema,
|
|
566
|
+
dataFormat: nestedProtocolDefinition.types.foo.dataFormats[0],
|
|
567
|
+
});
|
|
568
|
+
const bar = await TestDataGenerator.generateRecordsWrite({
|
|
569
|
+
author: alice,
|
|
570
|
+
protocol: nestedProtocolDefinition.protocol,
|
|
571
|
+
protocolPath: 'foo/bar',
|
|
572
|
+
parentContextId: foo.message.contextId,
|
|
573
|
+
schema: nestedProtocolDefinition.types.bar.schema,
|
|
574
|
+
dataFormat: nestedProtocolDefinition.types.bar.dataFormats[0],
|
|
575
|
+
});
|
|
576
|
+
const prune = await RecordsDelete.create({
|
|
577
|
+
recordId: foo.message.recordId,
|
|
578
|
+
prune: true,
|
|
579
|
+
signer: Jws.createSigner(alice),
|
|
580
|
+
});
|
|
581
|
+
await Time.minimalSleep();
|
|
582
|
+
const plainDelete = await RecordsDelete.create({
|
|
583
|
+
recordId: foo.message.recordId,
|
|
584
|
+
signer: Jws.createSigner(alice),
|
|
585
|
+
});
|
|
586
|
+
// replica A: prune first, then the newer plain delete loses on class
|
|
587
|
+
expect(await dwn.applyReplicatedMessage(alice.did, foo.message, { dataStream: DataStream.fromBytes(foo.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
588
|
+
expect(await dwn.applyReplicatedMessage(alice.did, bar.message, { dataStream: DataStream.fromBytes(bar.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
589
|
+
expect(await dwn.applyReplicatedMessage(alice.did, prune.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
590
|
+
expect(await dwn.applyReplicatedMessage(alice.did, plainDelete.message)).toEqual({ kind: 'Superseded' });
|
|
591
|
+
// replica B: the newer plain delete lands first; the prune still wins and cascades
|
|
592
|
+
const messageStoreB = new MessageStoreLevel({
|
|
593
|
+
location: 'TEST-MESSAGESTORE-DELETEWINS',
|
|
594
|
+
});
|
|
595
|
+
const dataStoreB = new DataStoreLevel({ blockstoreLocation: 'TEST-DATASTORE-DELETEWINS' });
|
|
596
|
+
const resumableTaskStoreB = new ResumableTaskStoreLevel({ location: 'TEST-RESUMABLE-TASK-STORE-DELETEWINS' });
|
|
597
|
+
const dwnB = await Dwn.create({
|
|
598
|
+
didResolver,
|
|
599
|
+
messageStore: messageStoreB,
|
|
600
|
+
dataStore: dataStoreB,
|
|
601
|
+
resumableTaskStore: resumableTaskStoreB,
|
|
602
|
+
});
|
|
603
|
+
try {
|
|
604
|
+
await messageStoreB.clear();
|
|
605
|
+
await dataStoreB.clear();
|
|
606
|
+
await resumableTaskStoreB.clear();
|
|
607
|
+
expect((await dwnB.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
608
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, foo.message, { dataStream: DataStream.fromBytes(foo.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
609
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, bar.message, { dataStream: DataStream.fromBytes(bar.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
610
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, plainDelete.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
611
|
+
expect(await dwnB.applyReplicatedMessage(alice.did, prune.message)).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
612
|
+
// the child is purged on both replicas
|
|
613
|
+
const barCid = await Message.getCid(bar.message);
|
|
614
|
+
expect(await messageStore.get(alice.did, barCid)).toBeUndefined();
|
|
615
|
+
expect(await messageStoreB.get(alice.did, barCid)).toBeUndefined();
|
|
616
|
+
}
|
|
617
|
+
finally {
|
|
618
|
+
await dwnB.close();
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
const deepNestedProtocol = {
|
|
622
|
+
protocol: 'https://example.com/deep-nested',
|
|
623
|
+
published: false,
|
|
624
|
+
types: {
|
|
625
|
+
foo: {},
|
|
626
|
+
bar: {},
|
|
627
|
+
baz: {},
|
|
628
|
+
qux: {},
|
|
629
|
+
},
|
|
630
|
+
structure: {
|
|
631
|
+
foo: {
|
|
632
|
+
bar: {
|
|
633
|
+
baz: {
|
|
634
|
+
qux: {},
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
};
|
|
640
|
+
// Installs `deepNestedProtocol` on the local DWN and builds a foo -> bar -> baz -> qux
|
|
641
|
+
// record chain under it. None of the generated records are stored; each test applies the
|
|
642
|
+
// subset it needs to shape local ancestor presence.
|
|
643
|
+
async function generateDeepNestedChain(alice) {
|
|
644
|
+
const protocolsConfigure = await TestDataGenerator.generateProtocolsConfigure({
|
|
645
|
+
author: alice,
|
|
646
|
+
protocolDefinition: deepNestedProtocol,
|
|
647
|
+
});
|
|
648
|
+
expect((await dwn.processMessage(alice.did, protocolsConfigure.message)).status.code).toBe(202);
|
|
649
|
+
const foo = await TestDataGenerator.generateRecordsWrite({
|
|
650
|
+
author: alice,
|
|
651
|
+
protocol: deepNestedProtocol.protocol,
|
|
652
|
+
protocolPath: 'foo',
|
|
653
|
+
});
|
|
654
|
+
const bar = await TestDataGenerator.generateRecordsWrite({
|
|
655
|
+
author: alice,
|
|
656
|
+
protocol: deepNestedProtocol.protocol,
|
|
657
|
+
protocolPath: 'foo/bar',
|
|
658
|
+
parentContextId: foo.message.contextId,
|
|
659
|
+
});
|
|
660
|
+
const baz = await TestDataGenerator.generateRecordsWrite({
|
|
661
|
+
author: alice,
|
|
662
|
+
protocol: deepNestedProtocol.protocol,
|
|
663
|
+
protocolPath: 'foo/bar/baz',
|
|
664
|
+
parentContextId: bar.message.contextId,
|
|
665
|
+
});
|
|
666
|
+
const qux = await TestDataGenerator.generateRecordsWrite({
|
|
667
|
+
author: alice,
|
|
668
|
+
protocol: deepNestedProtocol.protocol,
|
|
669
|
+
protocolPath: 'foo/bar/baz/qux',
|
|
670
|
+
parentContextId: baz.message.contextId,
|
|
671
|
+
});
|
|
672
|
+
return { foo, bar, baz, qux };
|
|
673
|
+
}
|
|
674
|
+
it('emits one ref per missing contextId segment in a single Incomplete', async () => {
|
|
675
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
676
|
+
const { foo, bar, baz, qux } = await generateDeepNestedChain(alice);
|
|
677
|
+
// no ancestors are present locally: a single apply names every missing segment,
|
|
678
|
+
// root-first, with the immediate parent keeping its existing Parent ref shape
|
|
679
|
+
const result = await dwn.applyReplicatedMessage(alice.did, qux.message, { dataStream: DataStream.fromBytes(qux.dataBytes) });
|
|
680
|
+
expect(result).toEqual({
|
|
681
|
+
kind: 'Incomplete',
|
|
682
|
+
missing: [
|
|
683
|
+
{ type: 'Ancestor', recordId: foo.message.recordId, protocol: deepNestedProtocol.protocol },
|
|
684
|
+
{ type: 'Ancestor', recordId: bar.message.recordId, protocol: deepNestedProtocol.protocol },
|
|
685
|
+
{ type: 'Parent', recordId: baz.message.recordId, protocol: deepNestedProtocol.protocol },
|
|
686
|
+
],
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
it('lists only the locally-missing ancestor segments when part of the chain is present', async () => {
|
|
690
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
691
|
+
const { foo, bar, baz, qux } = await generateDeepNestedChain(alice);
|
|
692
|
+
// the root of the chain is already present locally
|
|
693
|
+
expect(await dwn.applyReplicatedMessage(alice.did, foo.message, { dataStream: DataStream.fromBytes(foo.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
694
|
+
const result = await dwn.applyReplicatedMessage(alice.did, qux.message, { dataStream: DataStream.fromBytes(qux.dataBytes) });
|
|
695
|
+
expect(result).toEqual({
|
|
696
|
+
kind: 'Incomplete',
|
|
697
|
+
missing: [
|
|
698
|
+
{ type: 'Ancestor', recordId: bar.message.recordId, protocol: deepNestedProtocol.protocol },
|
|
699
|
+
{ type: 'Parent', recordId: baz.message.recordId, protocol: deepNestedProtocol.protocol },
|
|
700
|
+
],
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
it('resolves a deep ancestor chain in a bounded number of fetch-and-retry passes', async () => {
|
|
704
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
705
|
+
const { foo, bar, baz, qux } = await generateDeepNestedChain(alice);
|
|
706
|
+
const chainWritesByRecordId = new Map([
|
|
707
|
+
[foo.message.recordId, foo],
|
|
708
|
+
[bar.message.recordId, bar],
|
|
709
|
+
[baz.message.recordId, baz],
|
|
710
|
+
]);
|
|
711
|
+
// engine loop: apply the deepest record, fetch whatever the Incomplete names, retry
|
|
712
|
+
let applyAttempts = 0;
|
|
713
|
+
let result;
|
|
714
|
+
do {
|
|
715
|
+
applyAttempts += 1;
|
|
716
|
+
result = await dwn.applyReplicatedMessage(alice.did, qux.message, { dataStream: DataStream.fromBytes(qux.dataBytes) });
|
|
717
|
+
if (result.kind === 'Incomplete') {
|
|
718
|
+
for (const ref of result.missing) {
|
|
719
|
+
if (ref.type !== 'Ancestor' && ref.type !== 'Parent') {
|
|
720
|
+
throw new Error(`Incomplete named an unexpected dependency ref type: ${ref.type}`);
|
|
721
|
+
}
|
|
722
|
+
const ancestor = chainWritesByRecordId.get(ref.recordId);
|
|
723
|
+
if (ancestor === undefined) {
|
|
724
|
+
throw new Error(`Incomplete named an unexpected ancestor: ${ref.recordId}`);
|
|
725
|
+
}
|
|
726
|
+
expect(await dwn.applyReplicatedMessage(alice.did, ancestor.message, { dataStream: DataStream.fromBytes(ancestor.dataBytes) })).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
} while (result.kind === 'Incomplete' && applyAttempts < 4);
|
|
730
|
+
// the full 4-level chain resolves in two passes — one Incomplete naming all three
|
|
731
|
+
// ancestors, then a clean apply — never one round trip per ancestry level
|
|
732
|
+
expect(result).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
733
|
+
expect(applyAttempts).toBe(2);
|
|
734
|
+
});
|
|
735
|
+
});
|
|
130
736
|
});
|
|
131
737
|
}
|
|
132
738
|
//# sourceMappingURL=dwn.spec.js.map
|