@enbox/dwn-sdk-js 0.4.0 → 0.4.2
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 +3 -10
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +799 -885
- 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 +12 -4
- 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 -45
- 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 +34 -89
- 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 +123 -28
- package/dist/esm/src/core/replication-apply.js.map +1 -1
- 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 +165 -132
- 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 +6 -7
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/{messages-sync.js → messages-query.js} +21 -15
- package/dist/esm/src/interfaces/messages-query.js.map +1 -0
- package/dist/esm/src/interfaces/protocols-configure.js +12 -9
- 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/protocol-tags.js +262 -0
- package/dist/esm/src/utils/protocol-tags.js.map +1 -0
- 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 +55 -1
- package/dist/esm/tests/core/replication-apply.spec.js.map +1 -1
- 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 +504 -35
- 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 +82 -36
- 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 -2
- package/dist/esm/tests/test-suite.js.map +1 -1
- package/dist/esm/tests/utils/protocol-tags.spec.js +96 -0
- package/dist/esm/tests/utils/protocol-tags.spec.js.map +1 -0
- 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 +12 -4
- 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 -14
- 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 +14 -17
- 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 +36 -0
- package/dist/types/src/core/replication-apply.d.ts.map +1 -1
- 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 +33 -20
- 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 +14 -16
- 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 -37
- 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/protocol-tags.d.ts +15 -0
- package/dist/types/src/utils/protocol-tags.d.ts.map +1 -0
- 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-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/protocol-tags.spec.d.ts +2 -0
- package/dist/types/tests/utils/protocol-tags.spec.d.ts.map +1 -0
- 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 +12 -4
- package/src/core/grant-authorization.ts +11 -20
- package/src/core/message-reply.ts +6 -5
- package/src/core/messages-grant-authorization.ts +37 -70
- package/src/core/protocol-authorization-action.ts +29 -38
- package/src/core/protocol-authorization-validation.ts +47 -121
- 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 +172 -32
- package/src/core/resumable-task-manager.ts +10 -8
- package/src/core/validation-state-reader.ts +350 -0
- package/src/dwn.ts +285 -192
- 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 +14 -17
- package/src/interfaces/messages-query.ts +70 -0
- package/src/interfaces/protocols-configure.ts +20 -10
- 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 -45
- 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/protocol-tags.ts +366 -0
- 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 -278
- package/dist/esm/src/handlers/messages-sync.js.map +0 -1
- 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/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 -1088
- 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/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 -39
- package/dist/types/src/handlers/messages-sync.d.ts.map +0 -1
- package/dist/types/src/interfaces/messages-sync.d.ts +0 -20
- 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/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/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 -403
- package/src/interfaces/messages-sync.ts +0 -69
- 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/types/smt-types.ts +0 -95
- package/src/types/state-index.ts +0 -100
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
|
|
2
2
|
import type { CompoundIndexDefinition } from './index-level.js';
|
|
3
|
+
import type {
|
|
4
|
+
EventLogEntry,
|
|
5
|
+
EventLogReadOptions,
|
|
6
|
+
EventLogReadResult,
|
|
7
|
+
ProgressGapInfo,
|
|
8
|
+
ProgressGapReason,
|
|
9
|
+
ProgressToken,
|
|
10
|
+
ReplicationFeedReader,
|
|
11
|
+
WakePublisher,
|
|
12
|
+
} from '../types/subscriptions.js';
|
|
3
13
|
import type { Filter, KeyValues, PaginationCursor, QueryOptions } from '../types/query-types.js';
|
|
4
14
|
import type { GenericMessage, MessageSort, Pagination } from '../types/message-types.js';
|
|
5
|
-
import type {
|
|
15
|
+
import type { LevelDatabase, LevelWrapperBatchOperation } from './level-wrapper.js';
|
|
16
|
+
import type { MessageStore, MessageStoreOptions, MessageStorePutResult } from '../types/message-store.js';
|
|
6
17
|
|
|
7
18
|
import * as block from 'multiformats/block';
|
|
8
19
|
import * as cbor from '@ipld/dag-cbor';
|
|
9
20
|
|
|
10
|
-
import { BlockstoreLevel } from './blockstore-level.js';
|
|
11
21
|
import { Cid } from '../utils/cid.js';
|
|
12
22
|
import { CID } from 'multiformats/cid';
|
|
13
|
-
import { createLevelDatabase } from './level-wrapper.js';
|
|
14
23
|
import { executeUnlessAborted } from '../utils/abort.js';
|
|
24
|
+
import { FilterUtility } from '../utils/filter.js';
|
|
15
25
|
import { IndexLevel } from './index-level.js';
|
|
16
26
|
import { Message } from '../core/message.js';
|
|
27
|
+
import { Replication } from '../utils/replication.js';
|
|
17
28
|
import { sha256 } from 'multiformats/hashes/sha2';
|
|
18
29
|
import { SortDirection } from '../types/query-types.js';
|
|
30
|
+
import { createLevelDatabase, LevelWrapper } from './level-wrapper.js';
|
|
31
|
+
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
19
32
|
|
|
20
33
|
|
|
21
34
|
/**
|
|
@@ -64,61 +77,217 @@ const DEFAULT_COMPOUND_INDEXES: CompoundIndexDefinition[] = [
|
|
|
64
77
|
];
|
|
65
78
|
|
|
66
79
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
80
|
+
* Sublevel names under the single Level root. Every mutation touching multiple partitions is
|
|
81
|
+
* committed as one atomic batch on the root, so store↔log divergence is impossible by construction.
|
|
69
82
|
*/
|
|
70
|
-
|
|
83
|
+
const BLOCKS_PARTITION = 'blocks';
|
|
84
|
+
const INDEX_PARTITION = 'idx';
|
|
85
|
+
const LOG_PARTITION = 'log';
|
|
86
|
+
const CID_TO_SEQ_PARTITION = 'cid';
|
|
87
|
+
const FINGERPRINT_PARTITION = 'fp';
|
|
88
|
+
const HEADS_PARTITION = 'heads';
|
|
89
|
+
const META_PARTITION = 'meta';
|
|
90
|
+
|
|
91
|
+
const EPOCH_KEY = 'epoch';
|
|
92
|
+
const CURRENT_PARTITIONS = new Set([
|
|
93
|
+
BLOCKS_PARTITION,
|
|
94
|
+
INDEX_PARTITION,
|
|
95
|
+
LOG_PARTITION,
|
|
96
|
+
CID_TO_SEQ_PARTITION,
|
|
97
|
+
FINGERPRINT_PARTITION,
|
|
98
|
+
HEADS_PARTITION,
|
|
99
|
+
META_PARTITION,
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The persisted shape of a replication log row (the value at key `log!<tenant>!<paddedSeq>`).
|
|
104
|
+
*/
|
|
105
|
+
type LogEntryValue = {
|
|
106
|
+
/** The row's sequence number as a decimal string — also encoded in the row key. */
|
|
107
|
+
seq: string;
|
|
108
|
+
/** The CID of the stored message. */
|
|
109
|
+
messageCid: string;
|
|
110
|
+
/** The row's current query indexes (replaced by `updateIndexes`). */
|
|
111
|
+
indexes: KeyValues;
|
|
112
|
+
/**
|
|
113
|
+
* The row's fingerprint-domain membership, computed once from the insert-time indexes and
|
|
114
|
+
* persisted so deletion can fold the fingerprints without recomputing from mutated state.
|
|
115
|
+
*/
|
|
116
|
+
fingerprintScopes: string[];
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The partition handles over the single Level root, created once per store instance.
|
|
121
|
+
*/
|
|
122
|
+
type StorePartitions = {
|
|
123
|
+
/** The root wrapper — all batches execute here. */
|
|
124
|
+
root: LevelWrapper<string>;
|
|
125
|
+
/** Message blocks (binary values), tenant-partitioned: `blocks!<tenant>!<messageCid>`. */
|
|
126
|
+
blocks: LevelWrapper<Uint8Array>;
|
|
127
|
+
/** Replication log rows, tenant-partitioned: `log!<tenant>!<paddedSeq>`. */
|
|
128
|
+
log: LevelWrapper<string>;
|
|
129
|
+
/** messageCid → seq lookup, tenant-partitioned: `cid!<tenant>!<messageCid>`. */
|
|
130
|
+
cidToSeq: LevelWrapper<string>;
|
|
131
|
+
/** Fingerprint domain values, tenant-partitioned: `fp!<tenant>!<domainKey>` → 64-char hex. */
|
|
132
|
+
fingerprints: LevelWrapper<string>;
|
|
133
|
+
/** Per-tenant counter high-water: `heads!<tenant>` → decimal string. */
|
|
134
|
+
heads: LevelWrapper<string>;
|
|
135
|
+
/** Store-level metadata: `meta!epoch` → persisted store generation. */
|
|
136
|
+
meta: LevelWrapper<string>;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* A {@link MessageStore} and {@link ReplicationFeedReader} implementation that works in both the
|
|
141
|
+
* browser and server-side, backed by a SINGLE LevelDB root: message blocks, query indexes, the
|
|
142
|
+
* per-tenant replication log, the cid→seq index, fingerprint domains, the tenant counters, and
|
|
143
|
+
* the store epoch are all sublevels of one Level instance, so every mutation commits as one fully
|
|
144
|
+
* atomic batch.
|
|
145
|
+
*
|
|
146
|
+
* A per-tenant async write mutex spans seq assignment through batch write, so commit order equals
|
|
147
|
+
* seq order by construction. Seq assignment is gap-free (a failed batch never persists the head),
|
|
148
|
+
* while the readable log stays sparse after compaction and under filters — readers never assume
|
|
149
|
+
* contiguity.
|
|
150
|
+
*/
|
|
151
|
+
export class MessageStoreLevel implements MessageStore, ReplicationFeedReader {
|
|
71
152
|
config: MessageStoreLevelConfig;
|
|
72
153
|
|
|
73
|
-
|
|
74
|
-
|
|
154
|
+
private readonly wakePublisher?: WakePublisher;
|
|
155
|
+
private partitionsPromise?: Promise<StorePartitions>;
|
|
156
|
+
private epochPromise?: Promise<string>;
|
|
157
|
+
private readonly writeLocks: Map<string, Promise<void>> = new Map();
|
|
75
158
|
|
|
76
159
|
/**
|
|
77
160
|
* @param {MessageStoreLevelConfig} config
|
|
78
|
-
* @param {string} config.
|
|
161
|
+
* @param {string} config.location - must be a directory path (relative or absolute) where
|
|
79
162
|
* LevelDB will store its files, or in browsers, the name of the
|
|
80
163
|
* {@link https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase IDBDatabase} to be opened.
|
|
81
|
-
* @param {string} config.indexLocation - same as config.blockstoreLocation
|
|
82
164
|
* @param {CompoundIndexDefinition[]} config.compoundIndexes - compound indexes to register.
|
|
83
165
|
* Defaults to DEFAULT_COMPOUND_INDEXES which cover the most common DWN query patterns.
|
|
166
|
+
* @param {WakePublisher} config.wakePublisher - optional bus for store-owned wake publication.
|
|
84
167
|
*/
|
|
85
168
|
constructor(config: MessageStoreLevelConfig = {}) {
|
|
86
169
|
this.config = {
|
|
87
|
-
|
|
88
|
-
indexLocation : 'INDEX',
|
|
170
|
+
location: 'MESSAGESTORE',
|
|
89
171
|
createLevelDatabase,
|
|
90
172
|
...config
|
|
91
173
|
};
|
|
92
174
|
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
|
|
175
|
+
this.wakePublisher = this.config.wakePublisher;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async open(): Promise<void> {
|
|
179
|
+
const partitions = await this.partitions();
|
|
180
|
+
await partitions.root.open();
|
|
181
|
+
await this.assertNoPreSubstrateLayout(partitions);
|
|
182
|
+
await this.getEpoch();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async close(): Promise<void> {
|
|
186
|
+
if (this.partitionsPromise === undefined) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const partitions = await this.partitionsPromise;
|
|
191
|
+
await partitions.root.close();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Lazily creates the single Level root and its partition handles.
|
|
196
|
+
*/
|
|
197
|
+
private async partitions(): Promise<StorePartitions> {
|
|
198
|
+
this.partitionsPromise ??= this.createPartitions().catch((error) => {
|
|
199
|
+
this.partitionsPromise = undefined;
|
|
200
|
+
throw error;
|
|
96
201
|
});
|
|
202
|
+
return this.partitionsPromise;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async createPartitions(): Promise<StorePartitions> {
|
|
206
|
+
const location = this.config.location!;
|
|
207
|
+
const db = await this.config.createLevelDatabase!<string>(location, { keyEncoding: 'utf8', valueEncoding: 'utf8' });
|
|
208
|
+
|
|
209
|
+
const root = new LevelWrapper<string>(
|
|
210
|
+
{ location, createLevelDatabase: this.config.createLevelDatabase, keyEncoding: 'utf8', valueEncoding: 'utf8' },
|
|
211
|
+
db
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// A binary-valued view over the same underlying Level instance — its partitions carry their
|
|
215
|
+
// own value encoding, so block bytes and JSON strings coexist in one atomic batch domain.
|
|
216
|
+
const binaryView = new LevelWrapper<Uint8Array>(
|
|
217
|
+
{ location, createLevelDatabase: this.config.createLevelDatabase as never, keyEncoding: 'utf8', valueEncoding: 'binary' },
|
|
218
|
+
db as unknown as LevelDatabase<Uint8Array>
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
root,
|
|
223
|
+
blocks : await binaryView.partition(BLOCKS_PARTITION),
|
|
224
|
+
log : await root.partition(LOG_PARTITION),
|
|
225
|
+
cidToSeq : await root.partition(CID_TO_SEQ_PARTITION),
|
|
226
|
+
fingerprints : await root.partition(FINGERPRINT_PARTITION),
|
|
227
|
+
heads : await root.partition(HEADS_PARTITION),
|
|
228
|
+
meta : await root.partition(META_PARTITION),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
97
231
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
232
|
+
/**
|
|
233
|
+
* The query index over the shared root. Constructed lazily alongside the partitions.
|
|
234
|
+
*/
|
|
235
|
+
private indexPromise?: Promise<IndexLevel>;
|
|
236
|
+
private async index(): Promise<IndexLevel> {
|
|
237
|
+
this.indexPromise ??= (async (): Promise<IndexLevel> => {
|
|
238
|
+
const partitions = await this.partitions();
|
|
239
|
+
const indexRoot = await partitions.root.partition(INDEX_PARTITION);
|
|
240
|
+
return new IndexLevel(
|
|
241
|
+
{
|
|
242
|
+
location : this.config.location!,
|
|
243
|
+
createLevelDatabase : this.config.createLevelDatabase,
|
|
244
|
+
compoundIndexes : this.config.compoundIndexes ?? DEFAULT_COMPOUND_INDEXES,
|
|
245
|
+
},
|
|
246
|
+
indexRoot
|
|
247
|
+
);
|
|
248
|
+
})().catch((error) => {
|
|
249
|
+
this.indexPromise = undefined;
|
|
250
|
+
throw error;
|
|
102
251
|
});
|
|
252
|
+
return this.indexPromise;
|
|
103
253
|
}
|
|
104
254
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Returns the persisted store epoch, generating and persisting a fresh `crypto.randomUUID()`
|
|
257
|
+
* at first open. Replaced only by `clear()` — a full local reset with no surviving cursors.
|
|
258
|
+
*/
|
|
259
|
+
public async epoch(): Promise<string> {
|
|
260
|
+
return this.getEpoch();
|
|
108
261
|
}
|
|
109
262
|
|
|
110
|
-
async
|
|
111
|
-
|
|
112
|
-
|
|
263
|
+
private async getEpoch(): Promise<string> {
|
|
264
|
+
this.epochPromise ??= this.initializeEpoch().catch((error) => {
|
|
265
|
+
this.epochPromise = undefined;
|
|
266
|
+
throw error;
|
|
267
|
+
});
|
|
268
|
+
return this.epochPromise;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private async initializeEpoch(): Promise<string> {
|
|
272
|
+
const partitions = await this.partitions();
|
|
273
|
+
const existing = await partitions.meta.get(EPOCH_KEY);
|
|
274
|
+
if (existing !== undefined) {
|
|
275
|
+
return existing;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const freshEpoch = crypto.randomUUID();
|
|
279
|
+
await partitions.meta.put(EPOCH_KEY, freshEpoch);
|
|
280
|
+
return freshEpoch;
|
|
113
281
|
}
|
|
114
282
|
|
|
115
283
|
async get(tenant: string, cidString: string, options?: MessageStoreOptions): Promise<GenericMessage | undefined> {
|
|
116
284
|
options?.signal?.throwIfAborted();
|
|
117
285
|
|
|
118
|
-
const
|
|
286
|
+
const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
|
|
287
|
+
const tenantBlocks = await executeUnlessAborted(partitions.blocks.partition(tenant), options?.signal);
|
|
119
288
|
|
|
120
289
|
const cid = CID.parse(cidString);
|
|
121
|
-
const bytes = await
|
|
290
|
+
const bytes = await tenantBlocks.get(cid.toString(), options);
|
|
122
291
|
|
|
123
292
|
if (!bytes) {
|
|
124
293
|
return undefined;
|
|
@@ -142,7 +311,8 @@ export class MessageStoreLevel implements MessageStore {
|
|
|
142
311
|
// creates the query options including sorting and pagination.
|
|
143
312
|
// this adds 1 to the limit if provided, that way we can check to see if there are additional results and provide a return cursor.
|
|
144
313
|
const queryOptions = MessageStoreLevel.buildQueryOptions(messageSort, pagination);
|
|
145
|
-
const
|
|
314
|
+
const index = await this.index();
|
|
315
|
+
const results = await index.query(tenant, filters, queryOptions, options);
|
|
146
316
|
|
|
147
317
|
let cursor: PaginationCursor | undefined;
|
|
148
318
|
// checks to see if the returned results are greater than the limit, which would indicate additional results.
|
|
@@ -173,7 +343,8 @@ export class MessageStoreLevel implements MessageStore {
|
|
|
173
343
|
options?.signal?.throwIfAborted();
|
|
174
344
|
|
|
175
345
|
const queryOptions = MessageStoreLevel.buildQueryOptions(messageSort);
|
|
176
|
-
|
|
346
|
+
const index = await this.index();
|
|
347
|
+
return index.count(tenant, filters, queryOptions, options);
|
|
177
348
|
}
|
|
178
349
|
|
|
179
350
|
/**
|
|
@@ -208,50 +379,589 @@ export class MessageStoreLevel implements MessageStore {
|
|
|
208
379
|
return { sortDirection, sortProperty, limit, cursor };
|
|
209
380
|
}
|
|
210
381
|
|
|
211
|
-
async
|
|
382
|
+
async put(
|
|
383
|
+
tenant: string,
|
|
384
|
+
message: GenericMessage,
|
|
385
|
+
indexes: KeyValues,
|
|
386
|
+
options?: MessageStoreOptions
|
|
387
|
+
): Promise<MessageStorePutResult> {
|
|
212
388
|
options?.signal?.throwIfAborted();
|
|
213
389
|
|
|
214
|
-
const
|
|
390
|
+
const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
|
|
391
|
+
const index = await executeUnlessAborted(this.index(), options?.signal);
|
|
215
392
|
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
393
|
+
const encodedMessageBlock = await executeUnlessAborted(block.encode({ value: message, codec: cbor, hasher: sha256 }), options?.signal);
|
|
394
|
+
|
|
395
|
+
// MessageStore data may contain `encodedData` which is not taken into account when calculating the blockCID as it is optional data.
|
|
396
|
+
const messageCid = Cid.parseCid(await Message.getCid(message)).toString();
|
|
397
|
+
|
|
398
|
+
return this.withTenantWriteLock(tenant, async () => {
|
|
399
|
+
options?.signal?.throwIfAborted();
|
|
400
|
+
|
|
401
|
+
const tenantCidToSeq = await partitions.cidToSeq.partition(tenant);
|
|
402
|
+
const existingSeq = await tenantCidToSeq.get(messageCid, options);
|
|
403
|
+
if (existingSeq !== undefined) {
|
|
404
|
+
// Duplicates mutate nothing and publish no wake.
|
|
405
|
+
return { status: 'duplicate' as const };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const head = await this.getHead(partitions, tenant);
|
|
409
|
+
const seq = head + 1n;
|
|
410
|
+
const fingerprintScopes = Replication.computeFingerprintScopes(message, indexes);
|
|
411
|
+
|
|
412
|
+
const logEntry: LogEntryValue = {
|
|
413
|
+
seq: seq.toString(),
|
|
414
|
+
messageCid,
|
|
415
|
+
indexes,
|
|
416
|
+
fingerprintScopes,
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const tenantBlocks = await partitions.blocks.partition(tenant);
|
|
420
|
+
const blockOperation = tenantBlocks.createOperation({ type: 'put', key: messageCid, value: encodedMessageBlock.bytes });
|
|
421
|
+
|
|
422
|
+
const tenantLog = await partitions.log.partition(tenant);
|
|
423
|
+
const operations: LevelWrapperBatchOperation<string>[] = [
|
|
424
|
+
blockOperation as unknown as LevelWrapperBatchOperation<string>,
|
|
425
|
+
...await index.createPutOperations(tenant, messageCid, indexes),
|
|
426
|
+
tenantLog.createOperation({ type: 'put', key: Replication.encodePositionKey(seq), value: JSON.stringify(logEntry) }),
|
|
427
|
+
tenantCidToSeq.createOperation({ type: 'put', key: messageCid, value: seq.toString() }),
|
|
428
|
+
partitions.heads.createOperation({ type: 'put', key: tenant, value: seq.toString() }),
|
|
429
|
+
...await this.createFingerprintFoldOperations(partitions, tenant, messageCid, fingerprintScopes),
|
|
430
|
+
];
|
|
431
|
+
|
|
432
|
+
await partitions.root.batch(operations);
|
|
433
|
+
|
|
434
|
+
// Store-owned wake, post-commit, for `inserted` only.
|
|
435
|
+
this.publishWake(tenant, seq);
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
status : 'inserted' as const,
|
|
439
|
+
position : await this.buildToken(tenant, seq, messageCid),
|
|
440
|
+
};
|
|
441
|
+
});
|
|
219
442
|
}
|
|
220
443
|
|
|
221
|
-
async
|
|
444
|
+
async updateIndexes(
|
|
445
|
+
tenant: string,
|
|
446
|
+
messageCid: string,
|
|
447
|
+
indexes: KeyValues,
|
|
448
|
+
options?: MessageStoreOptions
|
|
449
|
+
): Promise<void> {
|
|
450
|
+
options?.signal?.throwIfAborted();
|
|
451
|
+
|
|
452
|
+
const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
|
|
453
|
+
const index = await executeUnlessAborted(this.index(), options?.signal);
|
|
454
|
+
|
|
455
|
+
await this.withTenantWriteLock(tenant, async () => {
|
|
456
|
+
const { entry, positionKey, tenantLog } = await this.getLogEntryForMutation(
|
|
457
|
+
partitions, tenant, messageCid, DwnErrorCode.MessageStoreUpdateIndexesMessageNotFound
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const storedMessage = await this.readStoredMessage(
|
|
461
|
+
partitions, tenant, messageCid, DwnErrorCode.MessageStoreUpdateIndexesMessageNotFound, options
|
|
462
|
+
);
|
|
463
|
+
Replication.assertFingerprintScopesUntouched(entry.fingerprintScopes, storedMessage, messageCid, indexes);
|
|
464
|
+
|
|
465
|
+
// Same row, same seq: indexes replaced; fingerprint scopes carried forward verbatim;
|
|
466
|
+
// fingerprints and head stay untouched.
|
|
467
|
+
const updatedEntry: LogEntryValue = { ...entry, indexes };
|
|
468
|
+
|
|
469
|
+
const operations: LevelWrapperBatchOperation<string>[] = [
|
|
470
|
+
...await index.createDeleteOperations(tenant, messageCid),
|
|
471
|
+
...await index.createPutOperations(tenant, messageCid, indexes),
|
|
472
|
+
tenantLog.createOperation({ type: 'put', key: positionKey, value: JSON.stringify(updatedEntry) }),
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
await partitions.root.batch(operations);
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async updateMessageAndIndexes(
|
|
222
480
|
tenant: string,
|
|
481
|
+
messageCid: string,
|
|
223
482
|
message: GenericMessage,
|
|
224
483
|
indexes: KeyValues,
|
|
225
484
|
options?: MessageStoreOptions
|
|
226
485
|
): Promise<void> {
|
|
227
486
|
options?.signal?.throwIfAborted();
|
|
228
487
|
|
|
229
|
-
const
|
|
488
|
+
const computedMessageCid = Cid.parseCid(await Message.getCid(message)).toString();
|
|
489
|
+
const normalizedMessageCid = Cid.parseCid(messageCid).toString();
|
|
490
|
+
if (computedMessageCid !== normalizedMessageCid) {
|
|
491
|
+
throw new DwnError(
|
|
492
|
+
DwnErrorCode.MessageStoreUpdateMessageAndIndexesCidMismatch,
|
|
493
|
+
`replacement message CID ${computedMessageCid} does not match target CID ${normalizedMessageCid}`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
230
496
|
|
|
497
|
+
const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
|
|
498
|
+
const index = await executeUnlessAborted(this.index(), options?.signal);
|
|
231
499
|
const encodedMessageBlock = await executeUnlessAborted(block.encode({ value: message, codec: cbor, hasher: sha256 }), options?.signal);
|
|
232
500
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
501
|
+
await this.withTenantWriteLock(tenant, async () => {
|
|
502
|
+
const { entry, positionKey, tenantLog } = await this.getLogEntryForMutation(
|
|
503
|
+
partitions, tenant, normalizedMessageCid, DwnErrorCode.MessageStoreUpdateMessageAndIndexesMessageNotFound
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
Replication.assertFingerprintScopesUntouched(entry.fingerprintScopes, message, normalizedMessageCid, indexes);
|
|
507
|
+
|
|
508
|
+
const updatedEntry: LogEntryValue = { ...entry, indexes };
|
|
236
509
|
|
|
237
|
-
|
|
510
|
+
const tenantBlocks = await partitions.blocks.partition(tenant);
|
|
511
|
+
const blockOperation = tenantBlocks.createOperation({ type: 'put', key: normalizedMessageCid, value: encodedMessageBlock.bytes });
|
|
512
|
+
const operations: LevelWrapperBatchOperation<string>[] = [
|
|
513
|
+
blockOperation as unknown as LevelWrapperBatchOperation<string>,
|
|
514
|
+
...await index.createDeleteOperations(tenant, normalizedMessageCid),
|
|
515
|
+
...await index.createPutOperations(tenant, normalizedMessageCid, indexes),
|
|
516
|
+
tenantLog.createOperation({ type: 'put', key: positionKey, value: JSON.stringify(updatedEntry) }),
|
|
517
|
+
];
|
|
238
518
|
|
|
239
|
-
|
|
519
|
+
await partitions.root.batch(operations);
|
|
520
|
+
});
|
|
240
521
|
}
|
|
241
522
|
|
|
523
|
+
async delete(tenant: string, cidString: string, options?: MessageStoreOptions): Promise<void> {
|
|
524
|
+
options?.signal?.throwIfAborted();
|
|
525
|
+
|
|
526
|
+
const partitions = await executeUnlessAborted(this.partitions(), options?.signal);
|
|
527
|
+
const index = await executeUnlessAborted(this.index(), options?.signal);
|
|
528
|
+
|
|
529
|
+
const messageCid = CID.parse(cidString).toString();
|
|
530
|
+
|
|
531
|
+
await this.withTenantWriteLock(tenant, async () => {
|
|
532
|
+
const tenantCidToSeq = await partitions.cidToSeq.partition(tenant);
|
|
533
|
+
const seqString = await tenantCidToSeq.get(messageCid, options);
|
|
534
|
+
if (seqString === undefined) {
|
|
535
|
+
// Idempotent no-op — the row does not exist.
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const seq = BigInt(seqString);
|
|
540
|
+
const positionKey = Replication.encodePositionKey(seq);
|
|
541
|
+
const tenantLog = await partitions.log.partition(tenant);
|
|
542
|
+
const serializedEntry = await tenantLog.get(positionKey);
|
|
543
|
+
if (serializedEntry === undefined) {
|
|
544
|
+
throw new DwnError(
|
|
545
|
+
DwnErrorCode.MessageStoreDeleteLogEntryMissing,
|
|
546
|
+
`cid index for tenant ${tenant} points to missing log entry at seq ${seqString} (CID ${messageCid})`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const entry = JSON.parse(serializedEntry) as LogEntryValue;
|
|
551
|
+
|
|
552
|
+
const tenantBlocks = await partitions.blocks.partition(tenant);
|
|
553
|
+
const blockOperation = tenantBlocks.createOperation({ type: 'del', key: messageCid });
|
|
554
|
+
|
|
555
|
+
// XOR is self-inverse: folding the persisted scopes again removes the row's contribution.
|
|
556
|
+
const operations: LevelWrapperBatchOperation<string>[] = [
|
|
557
|
+
blockOperation as unknown as LevelWrapperBatchOperation<string>,
|
|
558
|
+
...await index.createDeleteOperations(tenant, messageCid),
|
|
559
|
+
tenantLog.createOperation({ type: 'del', key: positionKey }),
|
|
560
|
+
tenantCidToSeq.createOperation({ type: 'del', key: messageCid }),
|
|
561
|
+
...await this.createFingerprintFoldOperations(partitions, tenant, messageCid, entry.fingerprintScopes),
|
|
562
|
+
];
|
|
563
|
+
|
|
564
|
+
await partitions.root.batch(operations);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
242
567
|
|
|
243
568
|
/**
|
|
244
|
-
* deletes everything in the underlying
|
|
569
|
+
* deletes everything in the underlying store, then persists a fresh epoch — a full local
|
|
570
|
+
* reset after which no previously issued cursor is valid.
|
|
245
571
|
*/
|
|
246
572
|
async clear(): Promise<void> {
|
|
247
|
-
await this.
|
|
248
|
-
await
|
|
573
|
+
const partitions = await this.partitions();
|
|
574
|
+
await partitions.root.clear();
|
|
575
|
+
|
|
576
|
+
this.epochPromise = undefined;
|
|
577
|
+
await this.getEpoch();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// ---------------------------------------------------------------------------
|
|
581
|
+
// ReplicationFeedReader
|
|
582
|
+
// ---------------------------------------------------------------------------
|
|
583
|
+
|
|
584
|
+
async logRead(tenant: string, options: EventLogReadOptions = {}): Promise<EventLogReadResult> {
|
|
585
|
+
const partitions = await this.partitions();
|
|
586
|
+
const { cursor, limit, filters } = options;
|
|
587
|
+
|
|
588
|
+
// Head-captured-first: the per-tenant write mutex serializes commits in position order, so an
|
|
589
|
+
// observed head H is a visibility barrier — every position <= H is already committed when the
|
|
590
|
+
// range scans below run, and anything committed afterward has a position > H and waits for
|
|
591
|
+
// the next page.
|
|
592
|
+
const head = await this.getHead(partitions, tenant);
|
|
593
|
+
if (cursor !== undefined) {
|
|
594
|
+
await this.validateCursor(partitions, tenant, cursor, head);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const startPosition = cursor === undefined ? 0n : BigInt(cursor.position);
|
|
598
|
+
|
|
599
|
+
if (head === 0n) {
|
|
600
|
+
// Nothing to scan — caught up at the input position.
|
|
601
|
+
return { events: [], cursor, drained: true };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const maxResults = limit ?? Number.MAX_SAFE_INTEGER;
|
|
605
|
+
if (maxResults <= 0) {
|
|
606
|
+
return { events: [], cursor, drained: startPosition >= head };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (startPosition >= head) {
|
|
610
|
+
// Nothing to scan — caught up at the input position.
|
|
611
|
+
return { events: [], cursor, drained: true };
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const tenantLog = await partitions.log.partition(tenant);
|
|
615
|
+
const tenantBlocks = await partitions.blocks.partition(tenant);
|
|
616
|
+
|
|
617
|
+
const iteratorRange = { gt: Replication.encodePositionKey(startPosition), lte: Replication.encodePositionKey(head) };
|
|
618
|
+
const logIterator = tenantLog.iterator(iteratorRange);
|
|
619
|
+
|
|
620
|
+
const events: EventLogEntry[] = [];
|
|
621
|
+
let drained = true;
|
|
622
|
+
let lastScannedPosition = startPosition;
|
|
623
|
+
let lastDeliveredPosition: bigint | undefined;
|
|
624
|
+
let lastDeliveredMessageCid: string | undefined;
|
|
625
|
+
|
|
626
|
+
for await (const [positionKey, serializedEntry] of logIterator) {
|
|
627
|
+
const position = BigInt(positionKey);
|
|
628
|
+
lastScannedPosition = position;
|
|
629
|
+
const entry = JSON.parse(serializedEntry) as LogEntryValue;
|
|
630
|
+
|
|
631
|
+
const event = await this.readEventFromLogEntry(tenantBlocks, position, entry, filters);
|
|
632
|
+
if (event === undefined) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
events.push(event);
|
|
637
|
+
lastDeliveredPosition = position;
|
|
638
|
+
lastDeliveredMessageCid = event.messageCid;
|
|
639
|
+
|
|
640
|
+
if (events.length >= maxResults) {
|
|
641
|
+
drained = position >= head;
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// High-water cursor: the highest position scanned — head when the scan completed, the stop
|
|
647
|
+
// position otherwise. `messageCid` is set only when the cursor position is a delivered row.
|
|
648
|
+
const cursorPosition = drained ? head : lastScannedPosition;
|
|
649
|
+
const cursorMessageCid = lastDeliveredPosition === cursorPosition ? lastDeliveredMessageCid : undefined;
|
|
650
|
+
const resultCursor = await this.buildToken(tenant, cursorPosition, cursorMessageCid);
|
|
651
|
+
|
|
652
|
+
return { events, cursor: resultCursor, drained };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
private async readEventFromLogEntry(
|
|
656
|
+
tenantBlocks: LevelWrapper<Uint8Array>,
|
|
657
|
+
position: bigint,
|
|
658
|
+
entry: LogEntryValue,
|
|
659
|
+
filters: Filter[] | undefined,
|
|
660
|
+
): Promise<EventLogEntry | undefined> {
|
|
661
|
+
if (filters !== undefined && filters.length > 0 && !FilterUtility.matchAnyFilter(entry.indexes, filters)) {
|
|
662
|
+
return undefined;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const bytes = await tenantBlocks.get(entry.messageCid);
|
|
666
|
+
if (bytes === undefined) {
|
|
667
|
+
// The row was deleted after the head capture; skip — its positions are gone with it.
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const decodedBlock = await block.decode({ bytes, codec: cbor, hasher: sha256 });
|
|
672
|
+
const message = decodedBlock.value as GenericMessage;
|
|
673
|
+
return {
|
|
674
|
+
seq : entry.seq,
|
|
675
|
+
position : position.toString(),
|
|
676
|
+
event : { message },
|
|
677
|
+
indexes : entry.indexes,
|
|
678
|
+
messageCid : entry.messageCid,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async logBounds(tenant: string): Promise<{ oldest: ProgressToken; latest: ProgressToken } | undefined> {
|
|
683
|
+
const partitions = await this.partitions();
|
|
684
|
+
const head = await this.getHead(partitions, tenant);
|
|
685
|
+
if (head === 0n) {
|
|
686
|
+
return undefined;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// The log is the live set — replay from zero is always available, so the oldest resumable
|
|
690
|
+
// position is always 0 regardless of compaction.
|
|
691
|
+
const oldest = await this.buildToken(tenant, 0n);
|
|
692
|
+
|
|
693
|
+
// Resolve the head position for the latest token's messageCid.
|
|
694
|
+
const headKey = Replication.encodePositionKey(head);
|
|
695
|
+
const tenantLog = await partitions.log.partition(tenant);
|
|
696
|
+
|
|
697
|
+
let headMessageCid: string | undefined;
|
|
698
|
+
const headLogEntry = await tenantLog.get(headKey);
|
|
699
|
+
if (headLogEntry !== undefined) {
|
|
700
|
+
headMessageCid = (JSON.parse(headLogEntry) as LogEntryValue).messageCid;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const latest = await this.buildToken(tenant, head, headMessageCid);
|
|
704
|
+
return { oldest, latest };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async fingerprint(tenant: string, scopes: string[]): Promise<string> {
|
|
708
|
+
const partitions = await this.partitions();
|
|
709
|
+
const tenantFingerprints = await partitions.fingerprints.partition(tenant);
|
|
710
|
+
|
|
711
|
+
let composed = Replication.emptyFingerprint();
|
|
712
|
+
for (const scope of scopes) {
|
|
713
|
+
const storedHex = await tenantFingerprints.get(MessageStoreLevel.fingerprintKey(scope));
|
|
714
|
+
if (storedHex !== undefined) {
|
|
715
|
+
composed = Replication.xorFingerprint(composed, Replication.hexToFingerprint(storedHex));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return Replication.fingerprintToHex(composed);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// ---------------------------------------------------------------------------
|
|
723
|
+
// Internals
|
|
724
|
+
// ---------------------------------------------------------------------------
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Serializes all log-mutating operations for a tenant: the lock spans seq assignment through
|
|
728
|
+
* batch write, so commit order equals seq order (synchronous assignment alone is insufficient —
|
|
729
|
+
* a later batch could land first).
|
|
730
|
+
*/
|
|
731
|
+
private async withTenantWriteLock<T>(tenant: string, task: () => Promise<T>): Promise<T> {
|
|
732
|
+
const previous = this.writeLocks.get(tenant) ?? Promise.resolve();
|
|
733
|
+
let release!: () => void;
|
|
734
|
+
const current = new Promise<void>((resolve) => { release = resolve; });
|
|
735
|
+
this.writeLocks.set(tenant, current);
|
|
736
|
+
|
|
737
|
+
await previous;
|
|
738
|
+
try {
|
|
739
|
+
return await task();
|
|
740
|
+
} finally {
|
|
741
|
+
release();
|
|
742
|
+
if (this.writeLocks.get(tenant) === current) {
|
|
743
|
+
this.writeLocks.delete(tenant);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Reads the tenant's counter high-water (the highest position ever issued), `0n` when unused.
|
|
750
|
+
*/
|
|
751
|
+
private async getHead(partitions: StorePartitions, tenant: string): Promise<bigint> {
|
|
752
|
+
const headString = await partitions.heads.get(tenant);
|
|
753
|
+
return headString === undefined ? 0n : BigInt(headString);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
private async assertNoPreSubstrateLayout(partitions: StorePartitions): Promise<void> {
|
|
757
|
+
if (await partitions.meta.get(EPOCH_KEY) !== undefined) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
for await (const key of partitions.root.keys()) {
|
|
762
|
+
const partition = MessageStoreLevel.parseSublevelPartition(key);
|
|
763
|
+
if (partition === undefined || CURRENT_PARTITIONS.has(partition)) {
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
throw new DwnError(
|
|
768
|
+
DwnErrorCode.MessageStorePreSubstrateLayout,
|
|
769
|
+
`message store location ${this.config.location} contains pre-substrate Level data; reset the store before opening it`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
private static parseSublevelPartition(key: string): string | undefined {
|
|
775
|
+
if (!key.startsWith('!')) {
|
|
776
|
+
return undefined;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const end = key.indexOf('!', 1);
|
|
780
|
+
return end === -1 ? undefined : key.slice(1, end);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
private async readStoredMessage(
|
|
784
|
+
partitions: StorePartitions,
|
|
785
|
+
tenant: string,
|
|
786
|
+
messageCid: string,
|
|
787
|
+
notFoundErrorCode: DwnErrorCode,
|
|
788
|
+
options?: MessageStoreOptions,
|
|
789
|
+
): Promise<GenericMessage> {
|
|
790
|
+
const tenantBlocks = await partitions.blocks.partition(tenant);
|
|
791
|
+
const bytes = await tenantBlocks.get(messageCid, options);
|
|
792
|
+
if (bytes === undefined) {
|
|
793
|
+
throw new DwnError(notFoundErrorCode, `no message block found for tenant ${tenant} with CID ${messageCid}`);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const decodedBlock = await block.decode({ bytes, codec: cbor, hasher: sha256 });
|
|
797
|
+
return decodedBlock.value as GenericMessage;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Validates a replication cursor against the tenant stream and persisted store epoch.
|
|
802
|
+
* Throws `EventLogProgressGap` with bounds metadata when the cursor cannot be replayed.
|
|
803
|
+
*/
|
|
804
|
+
private async validateCursor(partitions: StorePartitions, tenant: string, cursor: ProgressToken, head: bigint): Promise<void> {
|
|
805
|
+
const expectedStreamId = await Replication.deriveStreamId(tenant);
|
|
806
|
+
|
|
807
|
+
const reason = await this.validateCursorPosition(partitions, tenant, cursor, head, expectedStreamId);
|
|
808
|
+
if (reason === undefined) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const bounds = await this.logBounds(tenant);
|
|
813
|
+
const gapInfo: ProgressGapInfo = {
|
|
814
|
+
requested : cursor,
|
|
815
|
+
oldestAvailable : bounds?.oldest ?? cursor,
|
|
816
|
+
latestAvailable : bounds?.latest ?? cursor,
|
|
817
|
+
reason,
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
const error = new DwnError(
|
|
821
|
+
DwnErrorCode.EventLogProgressGap,
|
|
822
|
+
`progress token gap: ${reason}`
|
|
823
|
+
);
|
|
824
|
+
(error as any).gapInfo = gapInfo;
|
|
825
|
+
throw error;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
private async validateCursorPosition(
|
|
829
|
+
partitions: StorePartitions,
|
|
830
|
+
tenant: string,
|
|
831
|
+
cursor: ProgressToken,
|
|
832
|
+
head: bigint,
|
|
833
|
+
expectedStreamId: string,
|
|
834
|
+
): Promise<ProgressGapReason | undefined> {
|
|
835
|
+
if (cursor.streamId !== expectedStreamId) {
|
|
836
|
+
return 'stream_mismatch';
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (cursor.epoch !== await this.getEpoch()) {
|
|
840
|
+
return 'epoch_mismatch';
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const cursorPosition = BigInt(cursor.position);
|
|
844
|
+
if (cursorPosition > head) {
|
|
845
|
+
return 'token_too_new';
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (cursor.messageCid === undefined) {
|
|
849
|
+
return undefined;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const positionMessageCid = await this.getMessageCidAtPosition(partitions, tenant, cursorPosition);
|
|
853
|
+
if (positionMessageCid !== undefined && positionMessageCid !== cursor.messageCid) {
|
|
854
|
+
return 'message_mismatch';
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return undefined;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
private async getMessageCidAtPosition(partitions: StorePartitions, tenant: string, position: bigint): Promise<string | undefined> {
|
|
861
|
+
if (position <= 0n) {
|
|
862
|
+
return undefined;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const positionKey = Replication.encodePositionKey(position);
|
|
866
|
+
const tenantLog = await partitions.log.partition(tenant);
|
|
867
|
+
const serializedEntry = await tenantLog.get(positionKey);
|
|
868
|
+
if (serializedEntry !== undefined) {
|
|
869
|
+
return (JSON.parse(serializedEntry) as LogEntryValue).messageCid;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return undefined;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Resolves the log row for a same-CID mutation, throwing the given code when no row exists.
|
|
877
|
+
*/
|
|
878
|
+
private async getLogEntryForMutation(
|
|
879
|
+
partitions: StorePartitions,
|
|
880
|
+
tenant: string,
|
|
881
|
+
messageCid: string,
|
|
882
|
+
notFoundErrorCode: DwnErrorCode,
|
|
883
|
+
): Promise<{ entry: LogEntryValue, positionKey: string, tenantLog: LevelWrapper<string> }> {
|
|
884
|
+
const tenantCidToSeq = await partitions.cidToSeq.partition(tenant);
|
|
885
|
+
const seqString = await tenantCidToSeq.get(messageCid);
|
|
886
|
+
if (seqString === undefined) {
|
|
887
|
+
throw new DwnError(notFoundErrorCode, `no message found for tenant ${tenant} with CID ${messageCid}`);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const positionKey = Replication.encodePositionKey(BigInt(seqString));
|
|
891
|
+
const tenantLog = await partitions.log.partition(tenant);
|
|
892
|
+
const serializedEntry = await tenantLog.get(positionKey);
|
|
893
|
+
if (serializedEntry === undefined) {
|
|
894
|
+
throw new DwnError(notFoundErrorCode, `no log entry found for tenant ${tenant} at seq ${seqString} (CID ${messageCid})`);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return { entry: JSON.parse(serializedEntry) as LogEntryValue, positionKey, tenantLog };
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Creates the batch operations that fold a message CID's contribution into the given
|
|
902
|
+
* fingerprint domains. XOR is self-inverse, so the same operations serve insert and delete.
|
|
903
|
+
*/
|
|
904
|
+
private async createFingerprintFoldOperations(
|
|
905
|
+
partitions: StorePartitions,
|
|
906
|
+
tenant: string,
|
|
907
|
+
messageCid: string,
|
|
908
|
+
scopes: string[],
|
|
909
|
+
): Promise<LevelWrapperBatchOperation<string>[]> {
|
|
910
|
+
const contribution = await Replication.hashMessageCid(messageCid);
|
|
911
|
+
const tenantFingerprints = await partitions.fingerprints.partition(tenant);
|
|
912
|
+
|
|
913
|
+
const operations: LevelWrapperBatchOperation<string>[] = [];
|
|
914
|
+
for (const scope of scopes) {
|
|
915
|
+
const key = MessageStoreLevel.fingerprintKey(scope);
|
|
916
|
+
const storedHex = await tenantFingerprints.get(key);
|
|
917
|
+
const current = storedHex === undefined ? Replication.emptyFingerprint() : Replication.hexToFingerprint(storedHex);
|
|
918
|
+
const folded = Replication.xorFingerprint(current, contribution);
|
|
919
|
+
operations.push(tenantFingerprints.createOperation({ type: 'put', key, value: Replication.fingerprintToHex(folded) }));
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
return operations;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Encodes a fingerprint domain name as a Level key. The uniform prefix keeps the global
|
|
927
|
+
* domain (the empty string) a valid key.
|
|
928
|
+
*/
|
|
929
|
+
private static fingerprintKey(scope: string): string {
|
|
930
|
+
return `d${scope}`;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
private async buildToken(tenant: string, position: bigint, messageCid?: string): Promise<ProgressToken> {
|
|
934
|
+
const token: ProgressToken = {
|
|
935
|
+
streamId : await Replication.deriveStreamId(tenant),
|
|
936
|
+
epoch : await this.getEpoch(),
|
|
937
|
+
position : position.toString(),
|
|
938
|
+
};
|
|
939
|
+
if (messageCid !== undefined) {
|
|
940
|
+
token.messageCid = messageCid;
|
|
941
|
+
}
|
|
942
|
+
return token;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Publishes a wake post-commit. Best-effort by contract — never throws into the write path.
|
|
947
|
+
*/
|
|
948
|
+
private publishWake(tenant: string, position: bigint): void {
|
|
949
|
+
try {
|
|
950
|
+
this.wakePublisher?.publish({ tenant, seq: position.toString() });
|
|
951
|
+
} catch {
|
|
952
|
+
// A lost wake only delays delivery; consumers' idle re-drain bounds the latency.
|
|
953
|
+
}
|
|
249
954
|
}
|
|
250
955
|
}
|
|
251
956
|
|
|
252
957
|
export type MessageStoreLevelConfig = {
|
|
253
|
-
|
|
254
|
-
|
|
958
|
+
/**
|
|
959
|
+
* The single directory path (or IndexedDB database name in browsers) backing the whole store —
|
|
960
|
+
* message blocks, query indexes, the replication log, fingerprints, counters, and epoch are all
|
|
961
|
+
* sublevels of one Level instance.
|
|
962
|
+
*/
|
|
963
|
+
location?: string,
|
|
255
964
|
createLevelDatabase?: typeof createLevelDatabase,
|
|
256
965
|
compoundIndexes?: CompoundIndexDefinition[],
|
|
257
|
-
|
|
966
|
+
wakePublisher?: WakePublisher,
|
|
967
|
+
};
|