@noy-db/hub 0.1.0-pre.3
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/LICENSE +21 -0
- package/README.md +197 -0
- package/dist/aggregate/index.cjs +476 -0
- package/dist/aggregate/index.cjs.map +1 -0
- package/dist/aggregate/index.d.cts +38 -0
- package/dist/aggregate/index.d.ts +38 -0
- package/dist/aggregate/index.js +53 -0
- package/dist/aggregate/index.js.map +1 -0
- package/dist/blobs/index.cjs +1480 -0
- package/dist/blobs/index.cjs.map +1 -0
- package/dist/blobs/index.d.cts +45 -0
- package/dist/blobs/index.d.ts +45 -0
- package/dist/blobs/index.js +48 -0
- package/dist/blobs/index.js.map +1 -0
- package/dist/bundle/index.cjs +436 -0
- package/dist/bundle/index.cjs.map +1 -0
- package/dist/bundle/index.d.cts +7 -0
- package/dist/bundle/index.d.ts +7 -0
- package/dist/bundle/index.js +40 -0
- package/dist/bundle/index.js.map +1 -0
- package/dist/chunk-2QR2PQTT.js +217 -0
- package/dist/chunk-2QR2PQTT.js.map +1 -0
- package/dist/chunk-4OWFYIDQ.js +79 -0
- package/dist/chunk-4OWFYIDQ.js.map +1 -0
- package/dist/chunk-5AATM2M2.js +90 -0
- package/dist/chunk-5AATM2M2.js.map +1 -0
- package/dist/chunk-ACLDOTNQ.js +543 -0
- package/dist/chunk-ACLDOTNQ.js.map +1 -0
- package/dist/chunk-BTDCBVJW.js +160 -0
- package/dist/chunk-BTDCBVJW.js.map +1 -0
- package/dist/chunk-CIMZBAZB.js +72 -0
- package/dist/chunk-CIMZBAZB.js.map +1 -0
- package/dist/chunk-E445ICYI.js +365 -0
- package/dist/chunk-E445ICYI.js.map +1 -0
- package/dist/chunk-EXQRC2L4.js +722 -0
- package/dist/chunk-EXQRC2L4.js.map +1 -0
- package/dist/chunk-FZU343FL.js +32 -0
- package/dist/chunk-FZU343FL.js.map +1 -0
- package/dist/chunk-GJILMRPO.js +354 -0
- package/dist/chunk-GJILMRPO.js.map +1 -0
- package/dist/chunk-GOUT6DND.js +1285 -0
- package/dist/chunk-GOUT6DND.js.map +1 -0
- package/dist/chunk-J66GRPNH.js +111 -0
- package/dist/chunk-J66GRPNH.js.map +1 -0
- package/dist/chunk-M2F2JAWB.js +464 -0
- package/dist/chunk-M2F2JAWB.js.map +1 -0
- package/dist/chunk-M5INGEFC.js +84 -0
- package/dist/chunk-M5INGEFC.js.map +1 -0
- package/dist/chunk-M62XNWRA.js +72 -0
- package/dist/chunk-M62XNWRA.js.map +1 -0
- package/dist/chunk-MR4424N3.js +275 -0
- package/dist/chunk-MR4424N3.js.map +1 -0
- package/dist/chunk-NPC4LFV5.js +132 -0
- package/dist/chunk-NPC4LFV5.js.map +1 -0
- package/dist/chunk-NXFEYLVG.js +311 -0
- package/dist/chunk-NXFEYLVG.js.map +1 -0
- package/dist/chunk-R36SIKES.js +79 -0
- package/dist/chunk-R36SIKES.js.map +1 -0
- package/dist/chunk-TDR6T5CJ.js +381 -0
- package/dist/chunk-TDR6T5CJ.js.map +1 -0
- package/dist/chunk-UF3BUNQZ.js +1 -0
- package/dist/chunk-UF3BUNQZ.js.map +1 -0
- package/dist/chunk-UQFSPSWG.js +1109 -0
- package/dist/chunk-UQFSPSWG.js.map +1 -0
- package/dist/chunk-USKYUS74.js +793 -0
- package/dist/chunk-USKYUS74.js.map +1 -0
- package/dist/chunk-XCL3WP6J.js +121 -0
- package/dist/chunk-XCL3WP6J.js.map +1 -0
- package/dist/chunk-XHFOENR2.js +680 -0
- package/dist/chunk-XHFOENR2.js.map +1 -0
- package/dist/chunk-ZFKD4QMV.js +430 -0
- package/dist/chunk-ZFKD4QMV.js.map +1 -0
- package/dist/chunk-ZLMV3TUA.js +490 -0
- package/dist/chunk-ZLMV3TUA.js.map +1 -0
- package/dist/chunk-ZRG4V3F5.js +17 -0
- package/dist/chunk-ZRG4V3F5.js.map +1 -0
- package/dist/consent/index.cjs +204 -0
- package/dist/consent/index.cjs.map +1 -0
- package/dist/consent/index.d.cts +24 -0
- package/dist/consent/index.d.ts +24 -0
- package/dist/consent/index.js +23 -0
- package/dist/consent/index.js.map +1 -0
- package/dist/crdt/index.cjs +152 -0
- package/dist/crdt/index.cjs.map +1 -0
- package/dist/crdt/index.d.cts +30 -0
- package/dist/crdt/index.d.ts +30 -0
- package/dist/crdt/index.js +24 -0
- package/dist/crdt/index.js.map +1 -0
- package/dist/crypto-IVKU7YTT.js +44 -0
- package/dist/crypto-IVKU7YTT.js.map +1 -0
- package/dist/delegation-XDJCBTI2.js +16 -0
- package/dist/delegation-XDJCBTI2.js.map +1 -0
- package/dist/dev-unlock-CeXic1xC.d.cts +263 -0
- package/dist/dev-unlock-KrKkcqD3.d.ts +263 -0
- package/dist/hash-9KO1BGxh.d.cts +63 -0
- package/dist/hash-ChfJjRjQ.d.ts +63 -0
- package/dist/history/index.cjs +1215 -0
- package/dist/history/index.cjs.map +1 -0
- package/dist/history/index.d.cts +62 -0
- package/dist/history/index.d.ts +62 -0
- package/dist/history/index.js +79 -0
- package/dist/history/index.js.map +1 -0
- package/dist/i18n/index.cjs +746 -0
- package/dist/i18n/index.cjs.map +1 -0
- package/dist/i18n/index.d.cts +38 -0
- package/dist/i18n/index.d.ts +38 -0
- package/dist/i18n/index.js +55 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index-BRHBCmLt.d.ts +1940 -0
- package/dist/index-C8kQtmOk.d.ts +380 -0
- package/dist/index-DN-J-5wT.d.cts +1940 -0
- package/dist/index-DhjMjz7L.d.cts +380 -0
- package/dist/index.cjs +14756 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +269 -0
- package/dist/index.d.ts +269 -0
- package/dist/index.js +6085 -0
- package/dist/index.js.map +1 -0
- package/dist/indexing/index.cjs +736 -0
- package/dist/indexing/index.cjs.map +1 -0
- package/dist/indexing/index.d.cts +36 -0
- package/dist/indexing/index.d.ts +36 -0
- package/dist/indexing/index.js +77 -0
- package/dist/indexing/index.js.map +1 -0
- package/dist/lazy-builder-BwEoBQZ9.d.ts +304 -0
- package/dist/lazy-builder-CZVLKh0Z.d.cts +304 -0
- package/dist/ledger-2NX4L7PN.js +33 -0
- package/dist/ledger-2NX4L7PN.js.map +1 -0
- package/dist/mime-magic-CBBSOkjm.d.cts +50 -0
- package/dist/mime-magic-CBBSOkjm.d.ts +50 -0
- package/dist/periods/index.cjs +1035 -0
- package/dist/periods/index.cjs.map +1 -0
- package/dist/periods/index.d.cts +21 -0
- package/dist/periods/index.d.ts +21 -0
- package/dist/periods/index.js +25 -0
- package/dist/periods/index.js.map +1 -0
- package/dist/predicate-SBHmi6D0.d.cts +161 -0
- package/dist/predicate-SBHmi6D0.d.ts +161 -0
- package/dist/query/index.cjs +1957 -0
- package/dist/query/index.cjs.map +1 -0
- package/dist/query/index.d.cts +3 -0
- package/dist/query/index.d.ts +3 -0
- package/dist/query/index.js +62 -0
- package/dist/query/index.js.map +1 -0
- package/dist/session/index.cjs +487 -0
- package/dist/session/index.cjs.map +1 -0
- package/dist/session/index.d.cts +45 -0
- package/dist/session/index.d.ts +45 -0
- package/dist/session/index.js +44 -0
- package/dist/session/index.js.map +1 -0
- package/dist/shadow/index.cjs +133 -0
- package/dist/shadow/index.cjs.map +1 -0
- package/dist/shadow/index.d.cts +16 -0
- package/dist/shadow/index.d.ts +16 -0
- package/dist/shadow/index.js +20 -0
- package/dist/shadow/index.js.map +1 -0
- package/dist/store/index.cjs +1069 -0
- package/dist/store/index.cjs.map +1 -0
- package/dist/store/index.d.cts +491 -0
- package/dist/store/index.d.ts +491 -0
- package/dist/store/index.js +34 -0
- package/dist/store/index.js.map +1 -0
- package/dist/strategy-BSxFXGzb.d.cts +110 -0
- package/dist/strategy-BSxFXGzb.d.ts +110 -0
- package/dist/strategy-D-SrOLCl.d.cts +548 -0
- package/dist/strategy-D-SrOLCl.d.ts +548 -0
- package/dist/sync/index.cjs +1062 -0
- package/dist/sync/index.cjs.map +1 -0
- package/dist/sync/index.d.cts +42 -0
- package/dist/sync/index.d.ts +42 -0
- package/dist/sync/index.js +28 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/team/index.cjs +1233 -0
- package/dist/team/index.cjs.map +1 -0
- package/dist/team/index.d.cts +117 -0
- package/dist/team/index.d.ts +117 -0
- package/dist/team/index.js +39 -0
- package/dist/team/index.js.map +1 -0
- package/dist/tx/index.cjs +212 -0
- package/dist/tx/index.cjs.map +1 -0
- package/dist/tx/index.d.cts +20 -0
- package/dist/tx/index.d.ts +20 -0
- package/dist/tx/index.js +20 -0
- package/dist/tx/index.js.map +1 -0
- package/dist/types-BZpCZB8N.d.ts +7526 -0
- package/dist/types-Bfs0qr5F.d.cts +7526 -0
- package/dist/ulid-COREQ2RQ.js +9 -0
- package/dist/ulid-COREQ2RQ.js.map +1 -0
- package/dist/util/index.cjs +230 -0
- package/dist/util/index.cjs.map +1 -0
- package/dist/util/index.d.cts +77 -0
- package/dist/util/index.d.ts +77 -0
- package/dist/util/index.js +190 -0
- package/dist/util/index.js.map +1 -0
- package/package.json +244 -0
|
@@ -0,0 +1,1069 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/store/index.ts
|
|
21
|
+
var store_exports = {};
|
|
22
|
+
__export(store_exports, {
|
|
23
|
+
BUNDLE_STORE_POLICY: () => BUNDLE_STORE_POLICY,
|
|
24
|
+
INDEXED_STORE_POLICY: () => INDEXED_STORE_POLICY,
|
|
25
|
+
SyncScheduler: () => SyncScheduler,
|
|
26
|
+
createBundleStore: () => createBundleStore,
|
|
27
|
+
routeStore: () => routeStore,
|
|
28
|
+
withCache: () => withCache,
|
|
29
|
+
withCircuitBreaker: () => withCircuitBreaker,
|
|
30
|
+
withHealthCheck: () => withHealthCheck,
|
|
31
|
+
withLogging: () => withLogging,
|
|
32
|
+
withMetrics: () => withMetrics,
|
|
33
|
+
withRetry: () => withRetry,
|
|
34
|
+
wrapBundleStore: () => wrapBundleStore,
|
|
35
|
+
wrapStore: () => wrapStore
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(store_exports);
|
|
38
|
+
|
|
39
|
+
// src/store/route-store.ts
|
|
40
|
+
var BLOB_CHUNKS = "_blob_chunks";
|
|
41
|
+
var BLOB_INDEX = "_blob_index";
|
|
42
|
+
var BLOB_SLOTS = "_blob_slots_";
|
|
43
|
+
var BLOB_VERSIONS = "_blob_versions_";
|
|
44
|
+
function routeStore(opts) {
|
|
45
|
+
const primary = opts.default;
|
|
46
|
+
const blobsIsSimple = opts.blobs && "get" in opts.blobs;
|
|
47
|
+
const simpleBlobStore = blobsIsSimple ? opts.blobs : void 0;
|
|
48
|
+
const tieredBlobs = !blobsIsSimple ? opts.blobs : void 0;
|
|
49
|
+
const blobThreshold = tieredBlobs?.threshold ?? 400 * 1024;
|
|
50
|
+
const allStores = /* @__PURE__ */ new Set([primary]);
|
|
51
|
+
if (simpleBlobStore) allStores.add(simpleBlobStore);
|
|
52
|
+
if (tieredBlobs?.large) allStores.add(tieredBlobs.large);
|
|
53
|
+
if (tieredBlobs?.small) allStores.add(tieredBlobs.small);
|
|
54
|
+
if (opts.age?.cold) allStores.add(opts.age.cold);
|
|
55
|
+
if (opts.routes) for (const s of Object.values(opts.routes)) allStores.add(s);
|
|
56
|
+
if (opts.vaultRoutes) for (const s of Object.values(opts.vaultRoutes)) allStores.add(s);
|
|
57
|
+
if (opts.blobRoutes) for (const s of Object.values(opts.blobRoutes)) allStores.add(s);
|
|
58
|
+
if (opts.overflow) allStores.add(opts.overflow);
|
|
59
|
+
if (opts.blobLifecycle?.archiveStore) allStores.add(opts.blobLifecycle.archiveStore);
|
|
60
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
61
|
+
const suspended = /* @__PURE__ */ new Set();
|
|
62
|
+
const writeQueues = /* @__PURE__ */ new Map();
|
|
63
|
+
const NULL_STORE = {
|
|
64
|
+
name: "suspended",
|
|
65
|
+
async get() {
|
|
66
|
+
return null;
|
|
67
|
+
},
|
|
68
|
+
async put() {
|
|
69
|
+
},
|
|
70
|
+
async delete() {
|
|
71
|
+
},
|
|
72
|
+
async list() {
|
|
73
|
+
return [];
|
|
74
|
+
},
|
|
75
|
+
async loadAll() {
|
|
76
|
+
return {};
|
|
77
|
+
},
|
|
78
|
+
async saveAll() {
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
function routeNameFor(vault, collection) {
|
|
82
|
+
if (opts.vaultRoutes) {
|
|
83
|
+
for (const prefix of Object.keys(opts.vaultRoutes)) {
|
|
84
|
+
if (vault.startsWith(prefix)) return prefix;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (opts.routes && !collection.startsWith("_") && opts.routes[collection]) {
|
|
88
|
+
return collection;
|
|
89
|
+
}
|
|
90
|
+
if (isBlobChunks(collection) && (simpleBlobStore || tieredBlobs)) return "blobs";
|
|
91
|
+
if (opts.routeBlobMeta && isBlobMeta(collection) && (simpleBlobStore || tieredBlobs)) return "blobs";
|
|
92
|
+
if (opts.age && !collection.startsWith("_")) {
|
|
93
|
+
}
|
|
94
|
+
return "default";
|
|
95
|
+
}
|
|
96
|
+
const quotaExceeded = false;
|
|
97
|
+
function resolveOriginalStore(route) {
|
|
98
|
+
if (route === "blobs") return simpleBlobStore ?? tieredBlobs?.large ?? primary;
|
|
99
|
+
if (route === "cold") return opts.age?.cold ?? primary;
|
|
100
|
+
if (opts.routes?.[route]) return opts.routes[route];
|
|
101
|
+
if (opts.vaultRoutes?.[route]) return opts.vaultRoutes[route];
|
|
102
|
+
return primary;
|
|
103
|
+
}
|
|
104
|
+
function maybeQueueWrite(routeName, method, vault, collection, id, envelope, expectedVersion) {
|
|
105
|
+
if (!suspended.has(routeName)) return false;
|
|
106
|
+
const queue = writeQueues.get(routeName);
|
|
107
|
+
if (!queue) return false;
|
|
108
|
+
if (queue.writes.length >= queue.maxSize) {
|
|
109
|
+
queue.writes.shift();
|
|
110
|
+
}
|
|
111
|
+
queue.writes.push({
|
|
112
|
+
method,
|
|
113
|
+
vault,
|
|
114
|
+
collection,
|
|
115
|
+
id,
|
|
116
|
+
...envelope !== void 0 ? { envelope } : {},
|
|
117
|
+
...expectedVersion !== void 0 ? { expectedVersion } : {}
|
|
118
|
+
});
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
function isBlobChunks(collection) {
|
|
122
|
+
return collection === BLOB_CHUNKS;
|
|
123
|
+
}
|
|
124
|
+
function isBlobMeta(collection) {
|
|
125
|
+
return collection === BLOB_INDEX || collection.startsWith(BLOB_SLOTS) || collection.startsWith(BLOB_VERSIONS);
|
|
126
|
+
}
|
|
127
|
+
function isInternal(collection) {
|
|
128
|
+
return collection.startsWith("_");
|
|
129
|
+
}
|
|
130
|
+
function storeFor(vault, collection) {
|
|
131
|
+
const rName = routeNameFor(vault, collection);
|
|
132
|
+
if (suspended.has(rName)) return NULL_STORE;
|
|
133
|
+
if (overrides.has(rName)) return overrides.get(rName);
|
|
134
|
+
if (opts.vaultRoutes) {
|
|
135
|
+
for (const [prefix, store2] of Object.entries(opts.vaultRoutes)) {
|
|
136
|
+
if (vault.startsWith(prefix)) return store2;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (opts.routes && !isInternal(collection) && opts.routes[collection]) {
|
|
140
|
+
return opts.routes[collection];
|
|
141
|
+
}
|
|
142
|
+
if (isBlobChunks(collection)) {
|
|
143
|
+
if (simpleBlobStore) return simpleBlobStore;
|
|
144
|
+
if (tieredBlobs) return tieredBlobs.large;
|
|
145
|
+
}
|
|
146
|
+
if (opts.routeBlobMeta && isBlobMeta(collection)) {
|
|
147
|
+
if (simpleBlobStore) return simpleBlobStore;
|
|
148
|
+
if (tieredBlobs) return tieredBlobs.large;
|
|
149
|
+
}
|
|
150
|
+
if (quotaExceeded && opts.overflow) return opts.overflow;
|
|
151
|
+
return primary;
|
|
152
|
+
}
|
|
153
|
+
function blobStoreForSize(dataSize) {
|
|
154
|
+
if (!tieredBlobs) return simpleBlobStore ?? primary;
|
|
155
|
+
if (dataSize <= blobThreshold) {
|
|
156
|
+
return tieredBlobs.small ?? primary;
|
|
157
|
+
}
|
|
158
|
+
return tieredBlobs.large;
|
|
159
|
+
}
|
|
160
|
+
function isCold(collection, envelope) {
|
|
161
|
+
if (!opts.age) return false;
|
|
162
|
+
if (isInternal(collection)) return false;
|
|
163
|
+
if (opts.age.collections && opts.age.collections.length > 0) {
|
|
164
|
+
if (!opts.age.collections.includes(collection)) return false;
|
|
165
|
+
}
|
|
166
|
+
const cutoff = Date.now() - opts.age.coldAfterDays * 24 * 60 * 60 * 1e3;
|
|
167
|
+
const ts = new Date(envelope._ts).getTime();
|
|
168
|
+
return ts < cutoff;
|
|
169
|
+
}
|
|
170
|
+
const store = {
|
|
171
|
+
name: buildName(),
|
|
172
|
+
async get(vault, collection, id) {
|
|
173
|
+
const s = storeFor(vault, collection);
|
|
174
|
+
const result = await s.get(vault, collection, id);
|
|
175
|
+
if (result === null && opts.age && !isInternal(collection)) {
|
|
176
|
+
if (!opts.age.collections?.length || opts.age.collections.includes(collection)) {
|
|
177
|
+
return opts.age.cold.get(vault, collection, id);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
},
|
|
182
|
+
async put(vault, collection, id, envelope, expectedVersion) {
|
|
183
|
+
const rn = routeNameFor(vault, collection);
|
|
184
|
+
if (maybeQueueWrite(rn, "put", vault, collection, id, envelope, expectedVersion)) return;
|
|
185
|
+
if (isBlobChunks(collection) && tieredBlobs) {
|
|
186
|
+
const dataSize = envelope._data.length;
|
|
187
|
+
const s2 = blobStoreForSize(dataSize);
|
|
188
|
+
return s2.put(vault, collection, id, envelope, expectedVersion);
|
|
189
|
+
}
|
|
190
|
+
const s = storeFor(vault, collection);
|
|
191
|
+
if (opts.age && !isInternal(collection)) {
|
|
192
|
+
opts.age.cold.delete(vault, collection, id).catch(() => {
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return s.put(vault, collection, id, envelope, expectedVersion);
|
|
196
|
+
},
|
|
197
|
+
async delete(vault, collection, id) {
|
|
198
|
+
const rn = routeNameFor(vault, collection);
|
|
199
|
+
if (maybeQueueWrite(rn, "delete", vault, collection, id)) return;
|
|
200
|
+
const s = storeFor(vault, collection);
|
|
201
|
+
await s.delete(vault, collection, id);
|
|
202
|
+
if (opts.age && !isInternal(collection)) {
|
|
203
|
+
await opts.age.cold.delete(vault, collection, id).catch(() => {
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
async list(vault, collection) {
|
|
208
|
+
const s = storeFor(vault, collection);
|
|
209
|
+
const ids = await s.list(vault, collection);
|
|
210
|
+
if (opts.age && !isInternal(collection)) {
|
|
211
|
+
if (!opts.age.collections?.length || opts.age.collections.includes(collection)) {
|
|
212
|
+
const coldIds = await opts.age.cold.list(vault, collection).catch(() => []);
|
|
213
|
+
if (coldIds.length > 0) {
|
|
214
|
+
const merged = new Set(ids);
|
|
215
|
+
for (const id of coldIds) merged.add(id);
|
|
216
|
+
return [...merged];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return ids;
|
|
221
|
+
},
|
|
222
|
+
async loadAll(vault) {
|
|
223
|
+
const stores = getStoresForVault(vault);
|
|
224
|
+
const snapshots = await Promise.all(
|
|
225
|
+
stores.map((s) => s.loadAll(vault).catch(() => ({})))
|
|
226
|
+
);
|
|
227
|
+
return mergeSnapshots(snapshots);
|
|
228
|
+
},
|
|
229
|
+
async saveAll(vault, data) {
|
|
230
|
+
const partitioned = /* @__PURE__ */ new Map();
|
|
231
|
+
for (const [collection, records] of Object.entries(data)) {
|
|
232
|
+
const s = storeFor(vault, collection);
|
|
233
|
+
if (!partitioned.has(s)) partitioned.set(s, {});
|
|
234
|
+
partitioned.get(s)[collection] = records;
|
|
235
|
+
}
|
|
236
|
+
await Promise.all(
|
|
237
|
+
[...partitioned.entries()].map(([s, snap]) => s.saveAll(vault, snap))
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
async compact(vault) {
|
|
241
|
+
if (!opts.age) return 0;
|
|
242
|
+
let migrated = 0;
|
|
243
|
+
const collections = opts.age.collections?.length ? opts.age.collections : await primary.list(vault, "").catch(() => []);
|
|
244
|
+
for (const collection of collections) {
|
|
245
|
+
const ids = await primary.list(vault, collection).catch(() => []);
|
|
246
|
+
for (const id of ids) {
|
|
247
|
+
const envelope = await primary.get(vault, collection, id);
|
|
248
|
+
if (!envelope) continue;
|
|
249
|
+
if (isCold(collection, envelope)) {
|
|
250
|
+
await opts.age.cold.put(vault, collection, id, envelope);
|
|
251
|
+
await primary.delete(vault, collection, id);
|
|
252
|
+
migrated++;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return migrated;
|
|
257
|
+
},
|
|
258
|
+
// ── Runtime override / suspend ──────────────────────
|
|
259
|
+
override(route, overrideStore, overrideOpts) {
|
|
260
|
+
if (overrideOpts?.hydrate) {
|
|
261
|
+
return (async () => {
|
|
262
|
+
overrides.set(route, overrideStore);
|
|
263
|
+
})();
|
|
264
|
+
}
|
|
265
|
+
overrides.set(route, overrideStore);
|
|
266
|
+
},
|
|
267
|
+
clearOverride(route) {
|
|
268
|
+
overrides.delete(route);
|
|
269
|
+
},
|
|
270
|
+
suspend(route, suspendOpts) {
|
|
271
|
+
suspended.add(route);
|
|
272
|
+
if (suspendOpts?.queue) {
|
|
273
|
+
writeQueues.set(route, {
|
|
274
|
+
writes: [],
|
|
275
|
+
maxSize: suspendOpts.maxQueueSize ?? 1e4
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
async resume(route) {
|
|
280
|
+
suspended.delete(route);
|
|
281
|
+
const queue = writeQueues.get(route);
|
|
282
|
+
if (!queue || queue.writes.length === 0) {
|
|
283
|
+
writeQueues.delete(route);
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
let replayed = 0;
|
|
287
|
+
const target = overrides.get(route) ?? resolveOriginalStore(route);
|
|
288
|
+
for (const write of queue.writes) {
|
|
289
|
+
try {
|
|
290
|
+
if (write.method === "put" && write.envelope) {
|
|
291
|
+
await target.put(write.vault, write.collection, write.id, write.envelope, write.expectedVersion);
|
|
292
|
+
} else if (write.method === "delete") {
|
|
293
|
+
await target.delete(write.vault, write.collection, write.id);
|
|
294
|
+
}
|
|
295
|
+
replayed++;
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
writeQueues.delete(route);
|
|
300
|
+
return replayed;
|
|
301
|
+
},
|
|
302
|
+
routeStatus() {
|
|
303
|
+
const ov = {};
|
|
304
|
+
for (const [k, v] of overrides) ov[k] = v.name ?? "unnamed";
|
|
305
|
+
const q = {};
|
|
306
|
+
for (const [k, v] of writeQueues) q[k] = v.writes.length;
|
|
307
|
+
return { overrides: ov, suspended: [...suspended], queued: q };
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
if (anyHas("listVaults")) {
|
|
311
|
+
store.listVaults = async () => {
|
|
312
|
+
const results = await Promise.all(
|
|
313
|
+
[...allStores].filter((s) => s.listVaults !== void 0).map((s) => s.listVaults().catch(() => []))
|
|
314
|
+
);
|
|
315
|
+
return [...new Set(results.flat())];
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (anyHas("ping")) {
|
|
319
|
+
store.ping = async () => {
|
|
320
|
+
const results = await Promise.all(
|
|
321
|
+
[...allStores].filter((s) => s.ping !== void 0).map((s) => s.ping().catch(() => false))
|
|
322
|
+
);
|
|
323
|
+
return results.some(Boolean);
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return store;
|
|
327
|
+
function buildName() {
|
|
328
|
+
const names = [...allStores].map((s) => s.name ?? "?").join("+");
|
|
329
|
+
return `route(${names})`;
|
|
330
|
+
}
|
|
331
|
+
function anyHas(method) {
|
|
332
|
+
return [...allStores].some((s) => s[method]);
|
|
333
|
+
}
|
|
334
|
+
function getStoresForVault(vault) {
|
|
335
|
+
const stores = /* @__PURE__ */ new Set();
|
|
336
|
+
if (opts.vaultRoutes) {
|
|
337
|
+
for (const [prefix, s] of Object.entries(opts.vaultRoutes)) {
|
|
338
|
+
if (vault.startsWith(prefix)) {
|
|
339
|
+
stores.add(s);
|
|
340
|
+
return [...stores];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
stores.add(primary);
|
|
345
|
+
if (simpleBlobStore) stores.add(simpleBlobStore);
|
|
346
|
+
if (tieredBlobs?.large) stores.add(tieredBlobs.large);
|
|
347
|
+
if (tieredBlobs?.small && tieredBlobs.small !== primary) stores.add(tieredBlobs.small);
|
|
348
|
+
if (opts.age?.cold) stores.add(opts.age.cold);
|
|
349
|
+
if (opts.routes) {
|
|
350
|
+
for (const s of Object.values(opts.routes)) stores.add(s);
|
|
351
|
+
}
|
|
352
|
+
return [...stores];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function mergeSnapshots(snapshots) {
|
|
356
|
+
const merged = {};
|
|
357
|
+
for (const snap of snapshots) {
|
|
358
|
+
for (const [collection, records] of Object.entries(snap)) {
|
|
359
|
+
if (!merged[collection]) {
|
|
360
|
+
merged[collection] = { ...records };
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
for (const [id, envelope] of Object.entries(records)) {
|
|
364
|
+
const existing = merged[collection][id];
|
|
365
|
+
if (!existing || envelope._ts >= existing._ts) {
|
|
366
|
+
merged[collection][id] = envelope;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return merged;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/store/store-middleware.ts
|
|
375
|
+
function wrapStore(store, ...middlewares) {
|
|
376
|
+
let result = store;
|
|
377
|
+
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
378
|
+
result = middlewares[i](result);
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
function withRetry(opts = {}) {
|
|
383
|
+
const maxRetries = opts.maxRetries ?? 3;
|
|
384
|
+
const backoffMs = opts.backoffMs ?? 500;
|
|
385
|
+
const jitter = opts.jitter ?? 0.3;
|
|
386
|
+
const retryOn = opts.retryOn ? new Set(opts.retryOn) : null;
|
|
387
|
+
function shouldRetry(err) {
|
|
388
|
+
if (!retryOn) return true;
|
|
389
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
390
|
+
return retryOn.has(err.code);
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
async function retryable(fn) {
|
|
395
|
+
let lastError;
|
|
396
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
397
|
+
try {
|
|
398
|
+
return await fn();
|
|
399
|
+
} catch (err) {
|
|
400
|
+
lastError = err;
|
|
401
|
+
if (attempt >= maxRetries || !shouldRetry(err)) throw err;
|
|
402
|
+
const delay = backoffMs * Math.pow(2, attempt) * (1 + Math.random() * jitter);
|
|
403
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
throw lastError;
|
|
407
|
+
}
|
|
408
|
+
return (next) => ({
|
|
409
|
+
...next,
|
|
410
|
+
name: next.name ? `retry(${next.name})` : "retry",
|
|
411
|
+
get: (v, c, id) => retryable(() => next.get(v, c, id)),
|
|
412
|
+
put: (v, c, id, env, ev) => retryable(() => next.put(v, c, id, env, ev)),
|
|
413
|
+
delete: (v, c, id) => retryable(() => next.delete(v, c, id)),
|
|
414
|
+
list: (v, c) => retryable(() => next.list(v, c)),
|
|
415
|
+
loadAll: (v) => retryable(() => next.loadAll(v)),
|
|
416
|
+
saveAll: (v, d) => retryable(() => next.saveAll(v, d))
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
420
|
+
function withLogging(opts = {}) {
|
|
421
|
+
const minLevel = LOG_LEVELS[opts.level ?? "info"];
|
|
422
|
+
const logger = opts.logger ?? console;
|
|
423
|
+
const logData = opts.logData ?? false;
|
|
424
|
+
function log(level, method, args, durationMs) {
|
|
425
|
+
if (LOG_LEVELS[level] < minLevel) return;
|
|
426
|
+
const parts = [`[noydb:${method}]`, ...Object.entries(args).map(([k, v]) => `${k}=${String(v)}`)];
|
|
427
|
+
if (durationMs !== void 0) parts.push(`${durationMs}ms`);
|
|
428
|
+
logger[level](parts.join(" "));
|
|
429
|
+
}
|
|
430
|
+
function timed(method, args, fn) {
|
|
431
|
+
const start = Date.now();
|
|
432
|
+
return fn().then(
|
|
433
|
+
(result) => {
|
|
434
|
+
log("debug", method, args, Date.now() - start);
|
|
435
|
+
return result;
|
|
436
|
+
},
|
|
437
|
+
(err) => {
|
|
438
|
+
log("error", method, { ...args, error: err.message }, Date.now() - start);
|
|
439
|
+
throw err;
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
return (next) => ({
|
|
444
|
+
...next,
|
|
445
|
+
name: next.name ? `log(${next.name})` : "log",
|
|
446
|
+
get: (v, c, id) => timed("get", { vault: v, collection: c, id }, () => next.get(v, c, id)),
|
|
447
|
+
put: (v, c, id, env, ev) => timed("put", {
|
|
448
|
+
vault: v,
|
|
449
|
+
collection: c,
|
|
450
|
+
id,
|
|
451
|
+
version: env._v,
|
|
452
|
+
...logData ? { data: env._data.slice(0, 40) + "..." } : {}
|
|
453
|
+
}, () => next.put(v, c, id, env, ev)),
|
|
454
|
+
delete: (v, c, id) => timed("delete", { vault: v, collection: c, id }, () => next.delete(v, c, id)),
|
|
455
|
+
list: (v, c) => timed("list", { vault: v, collection: c }, () => next.list(v, c)),
|
|
456
|
+
loadAll: (v) => timed("loadAll", { vault: v }, () => next.loadAll(v)),
|
|
457
|
+
saveAll: (v, d) => timed("saveAll", { vault: v }, () => next.saveAll(v, d))
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
function withMetrics(opts) {
|
|
461
|
+
function tracked(method, vault, fn, collection, id) {
|
|
462
|
+
const start = Date.now();
|
|
463
|
+
return fn().then(
|
|
464
|
+
(result) => {
|
|
465
|
+
opts.onOperation({
|
|
466
|
+
method,
|
|
467
|
+
vault,
|
|
468
|
+
...collection !== void 0 ? { collection } : {},
|
|
469
|
+
...id !== void 0 ? { id } : {},
|
|
470
|
+
durationMs: Date.now() - start,
|
|
471
|
+
success: true
|
|
472
|
+
});
|
|
473
|
+
return result;
|
|
474
|
+
},
|
|
475
|
+
(err) => {
|
|
476
|
+
opts.onOperation({
|
|
477
|
+
method,
|
|
478
|
+
vault,
|
|
479
|
+
...collection !== void 0 ? { collection } : {},
|
|
480
|
+
...id !== void 0 ? { id } : {},
|
|
481
|
+
durationMs: Date.now() - start,
|
|
482
|
+
success: false,
|
|
483
|
+
error: err
|
|
484
|
+
});
|
|
485
|
+
throw err;
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return (next) => ({
|
|
490
|
+
...next,
|
|
491
|
+
name: next.name ? `metrics(${next.name})` : "metrics",
|
|
492
|
+
get: (v, c, id) => tracked("get", v, () => next.get(v, c, id), c, id),
|
|
493
|
+
put: (v, c, id, env, ev) => tracked("put", v, () => next.put(v, c, id, env, ev), c, id),
|
|
494
|
+
delete: (v, c, id) => tracked("delete", v, () => next.delete(v, c, id), c, id),
|
|
495
|
+
list: (v, c) => tracked("list", v, () => next.list(v, c), c),
|
|
496
|
+
loadAll: (v) => tracked("loadAll", v, () => next.loadAll(v)),
|
|
497
|
+
saveAll: (v, d) => tracked("saveAll", v, () => next.saveAll(v, d))
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
function withCircuitBreaker(opts = {}) {
|
|
501
|
+
const threshold = opts.failureThreshold ?? 5;
|
|
502
|
+
const resetMs = opts.resetTimeoutMs ?? 3e4;
|
|
503
|
+
let state = "closed";
|
|
504
|
+
let failures = 0;
|
|
505
|
+
let lastFailureTime = 0;
|
|
506
|
+
function recordSuccess() {
|
|
507
|
+
if (state === "half-open") {
|
|
508
|
+
state = "closed";
|
|
509
|
+
failures = 0;
|
|
510
|
+
opts.onClose?.();
|
|
511
|
+
}
|
|
512
|
+
failures = 0;
|
|
513
|
+
}
|
|
514
|
+
function recordFailure() {
|
|
515
|
+
failures++;
|
|
516
|
+
lastFailureTime = Date.now();
|
|
517
|
+
if (failures >= threshold && state === "closed") {
|
|
518
|
+
state = "open";
|
|
519
|
+
opts.onOpen?.();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function canAttempt() {
|
|
523
|
+
if (state === "closed") return true;
|
|
524
|
+
if (state === "open") {
|
|
525
|
+
if (Date.now() - lastFailureTime >= resetMs) {
|
|
526
|
+
state = "half-open";
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
async function guarded(fn, fallback) {
|
|
534
|
+
if (!canAttempt()) return fallback;
|
|
535
|
+
try {
|
|
536
|
+
const result = await fn();
|
|
537
|
+
recordSuccess();
|
|
538
|
+
return result;
|
|
539
|
+
} catch (err) {
|
|
540
|
+
recordFailure();
|
|
541
|
+
throw err;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return (next) => ({
|
|
545
|
+
...next,
|
|
546
|
+
name: next.name ? `cb(${next.name})` : "cb",
|
|
547
|
+
get: (v, c, id) => guarded(() => next.get(v, c, id), null),
|
|
548
|
+
put: (v, c, id, env, ev) => guarded(() => next.put(v, c, id, env, ev), void 0),
|
|
549
|
+
delete: (v, c, id) => guarded(() => next.delete(v, c, id), void 0),
|
|
550
|
+
list: (v, c) => guarded(() => next.list(v, c), []),
|
|
551
|
+
loadAll: (v) => guarded(() => next.loadAll(v), {}),
|
|
552
|
+
saveAll: (v, d) => guarded(() => next.saveAll(v, d), void 0)
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
function withCache(opts = {}) {
|
|
556
|
+
const maxEntries = opts.maxEntries ?? 500;
|
|
557
|
+
const ttlMs = opts.ttlMs ?? 6e4;
|
|
558
|
+
const cache = /* @__PURE__ */ new Map();
|
|
559
|
+
function cacheKey(vault, collection, id) {
|
|
560
|
+
return `${vault}\0${collection}\0${id}`;
|
|
561
|
+
}
|
|
562
|
+
function getFromCache(key) {
|
|
563
|
+
const entry = cache.get(key);
|
|
564
|
+
if (!entry) return void 0;
|
|
565
|
+
if (ttlMs > 0 && Date.now() - entry.cachedAt > ttlMs) {
|
|
566
|
+
cache.delete(key);
|
|
567
|
+
return void 0;
|
|
568
|
+
}
|
|
569
|
+
cache.delete(key);
|
|
570
|
+
cache.set(key, entry);
|
|
571
|
+
return entry.envelope;
|
|
572
|
+
}
|
|
573
|
+
function setInCache(key, envelope) {
|
|
574
|
+
if (cache.size >= maxEntries) {
|
|
575
|
+
const oldest = cache.keys().next().value;
|
|
576
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
577
|
+
}
|
|
578
|
+
cache.set(key, { envelope, cachedAt: Date.now() });
|
|
579
|
+
}
|
|
580
|
+
function invalidate(key) {
|
|
581
|
+
cache.delete(key);
|
|
582
|
+
}
|
|
583
|
+
return (next) => ({
|
|
584
|
+
...next,
|
|
585
|
+
name: next.name ? `cache(${next.name})` : "cache",
|
|
586
|
+
async get(vault, collection, id) {
|
|
587
|
+
const key = cacheKey(vault, collection, id);
|
|
588
|
+
const cached = getFromCache(key);
|
|
589
|
+
if (cached !== void 0) return cached;
|
|
590
|
+
const result = await next.get(vault, collection, id);
|
|
591
|
+
setInCache(key, result);
|
|
592
|
+
return result;
|
|
593
|
+
},
|
|
594
|
+
async put(vault, collection, id, env, ev) {
|
|
595
|
+
invalidate(cacheKey(vault, collection, id));
|
|
596
|
+
await next.put(vault, collection, id, env, ev);
|
|
597
|
+
setInCache(cacheKey(vault, collection, id), env);
|
|
598
|
+
},
|
|
599
|
+
async delete(vault, collection, id) {
|
|
600
|
+
invalidate(cacheKey(vault, collection, id));
|
|
601
|
+
await next.delete(vault, collection, id);
|
|
602
|
+
},
|
|
603
|
+
list: (v, c) => next.list(v, c),
|
|
604
|
+
loadAll: (v) => next.loadAll(v),
|
|
605
|
+
saveAll: (v, d) => next.saveAll(v, d)
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
function withHealthCheck(opts = {}) {
|
|
609
|
+
const intervalMs = opts.checkIntervalMs ?? 3e4;
|
|
610
|
+
const failThreshold = opts.suspendAfterFailures ?? 3;
|
|
611
|
+
const successThreshold = opts.resumeAfterSuccess ?? 1;
|
|
612
|
+
let isSuspended = false;
|
|
613
|
+
let consecutiveFailures = 0;
|
|
614
|
+
let consecutiveSuccesses = 0;
|
|
615
|
+
return (next) => {
|
|
616
|
+
const checkFn = opts.check ?? (next.ping ? () => next.ping() : async () => {
|
|
617
|
+
await next.list("__health__", "__ping__");
|
|
618
|
+
return true;
|
|
619
|
+
});
|
|
620
|
+
async function doCheck() {
|
|
621
|
+
try {
|
|
622
|
+
const ok = await checkFn();
|
|
623
|
+
if (ok) {
|
|
624
|
+
consecutiveFailures = 0;
|
|
625
|
+
consecutiveSuccesses++;
|
|
626
|
+
if (isSuspended && consecutiveSuccesses >= successThreshold) {
|
|
627
|
+
isSuspended = false;
|
|
628
|
+
consecutiveSuccesses = 0;
|
|
629
|
+
opts.onResume?.();
|
|
630
|
+
}
|
|
631
|
+
} else {
|
|
632
|
+
throw new Error("Health check returned false");
|
|
633
|
+
}
|
|
634
|
+
} catch {
|
|
635
|
+
consecutiveSuccesses = 0;
|
|
636
|
+
consecutiveFailures++;
|
|
637
|
+
if (!isSuspended && consecutiveFailures >= failThreshold) {
|
|
638
|
+
isSuspended = true;
|
|
639
|
+
consecutiveFailures = 0;
|
|
640
|
+
opts.onSuspend?.();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
setInterval(() => {
|
|
645
|
+
void doCheck();
|
|
646
|
+
}, intervalMs);
|
|
647
|
+
const wrapped = {
|
|
648
|
+
...next,
|
|
649
|
+
name: next.name ? `health(${next.name})` : "health",
|
|
650
|
+
async get(v, c, id) {
|
|
651
|
+
return isSuspended ? null : next.get(v, c, id);
|
|
652
|
+
},
|
|
653
|
+
async put(v, c, id, env, ev) {
|
|
654
|
+
if (!isSuspended) await next.put(v, c, id, env, ev);
|
|
655
|
+
},
|
|
656
|
+
async delete(v, c, id) {
|
|
657
|
+
if (!isSuspended) await next.delete(v, c, id);
|
|
658
|
+
},
|
|
659
|
+
async list(v, c) {
|
|
660
|
+
return isSuspended ? [] : next.list(v, c);
|
|
661
|
+
},
|
|
662
|
+
async loadAll(v) {
|
|
663
|
+
return isSuspended ? {} : next.loadAll(v);
|
|
664
|
+
},
|
|
665
|
+
async saveAll(v, d) {
|
|
666
|
+
if (!isSuspended) await next.saveAll(v, d);
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
return wrapped;
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/errors.ts
|
|
674
|
+
var NoydbError = class extends Error {
|
|
675
|
+
/** Machine-readable error code. Stable across library versions. */
|
|
676
|
+
code;
|
|
677
|
+
constructor(code, message) {
|
|
678
|
+
super(message);
|
|
679
|
+
this.name = "NoydbError";
|
|
680
|
+
this.code = code;
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
var ConflictError = class extends NoydbError {
|
|
684
|
+
/** The actual stored version at the time of conflict. */
|
|
685
|
+
version;
|
|
686
|
+
constructor(version, message = "Version conflict") {
|
|
687
|
+
super("CONFLICT", message);
|
|
688
|
+
this.name = "ConflictError";
|
|
689
|
+
this.version = version;
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
var BundleVersionConflictError = class extends NoydbError {
|
|
693
|
+
/** The bundle handle of the newer remote version that rejected the push. */
|
|
694
|
+
remoteVersion;
|
|
695
|
+
constructor(remoteVersion, message = "Bundle version conflict \u2014 remote has been updated") {
|
|
696
|
+
super("BUNDLE_VERSION_CONFLICT", message);
|
|
697
|
+
this.name = "BundleVersionConflictError";
|
|
698
|
+
this.remoteVersion = remoteVersion;
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
// src/store/bundle-store.ts
|
|
703
|
+
var BUNDLE_STORE_VERSION = 1;
|
|
704
|
+
var MAX_CONFLICT_RETRIES = 3;
|
|
705
|
+
function wrapBundleStore(bundle, options) {
|
|
706
|
+
const autoFlush = options?.autoFlush !== false;
|
|
707
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
708
|
+
const versions = /* @__PURE__ */ new Map();
|
|
709
|
+
const loaded = /* @__PURE__ */ new Set();
|
|
710
|
+
let batchDepth = 0;
|
|
711
|
+
async function load(vault) {
|
|
712
|
+
if (loaded.has(vault)) return snapshots.get(vault);
|
|
713
|
+
const result = await bundle.readBundle(vault);
|
|
714
|
+
if (result) {
|
|
715
|
+
const text = new TextDecoder().decode(result.bytes);
|
|
716
|
+
const format = JSON.parse(text);
|
|
717
|
+
snapshots.set(vault, format.data);
|
|
718
|
+
versions.set(vault, result.version);
|
|
719
|
+
} else {
|
|
720
|
+
snapshots.set(vault, {});
|
|
721
|
+
versions.set(vault, null);
|
|
722
|
+
}
|
|
723
|
+
loaded.add(vault);
|
|
724
|
+
return snapshots.get(vault);
|
|
725
|
+
}
|
|
726
|
+
async function flush(vault) {
|
|
727
|
+
const snapshot = snapshots.get(vault) ?? {};
|
|
728
|
+
const format = {
|
|
729
|
+
_noydb_bundle_store: BUNDLE_STORE_VERSION,
|
|
730
|
+
vault,
|
|
731
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
732
|
+
data: snapshot
|
|
733
|
+
};
|
|
734
|
+
const bytes = new TextEncoder().encode(JSON.stringify(format));
|
|
735
|
+
const expectedVersion = versions.get(vault) ?? null;
|
|
736
|
+
for (let attempt = 0; attempt < MAX_CONFLICT_RETRIES; attempt++) {
|
|
737
|
+
try {
|
|
738
|
+
const { version: newVersion } = await bundle.writeBundle(vault, bytes, expectedVersion);
|
|
739
|
+
versions.set(vault, newVersion);
|
|
740
|
+
return;
|
|
741
|
+
} catch (err) {
|
|
742
|
+
if (err instanceof BundleVersionConflictError && attempt < MAX_CONFLICT_RETRIES - 1) {
|
|
743
|
+
const remote = await bundle.readBundle(vault);
|
|
744
|
+
if (remote) {
|
|
745
|
+
const remoteText = new TextDecoder().decode(remote.bytes);
|
|
746
|
+
const remoteFormat = JSON.parse(remoteText);
|
|
747
|
+
const localSnap = snapshots.get(vault) ?? {};
|
|
748
|
+
const mergedSnap = mergeSnapshots2(remoteFormat.data, localSnap);
|
|
749
|
+
snapshots.set(vault, mergedSnap);
|
|
750
|
+
versions.set(vault, remote.version);
|
|
751
|
+
}
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
throw err;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
async function maybeFlush(vault) {
|
|
759
|
+
if (autoFlush && batchDepth === 0) {
|
|
760
|
+
await flush(vault);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const store = {
|
|
764
|
+
name: bundle.name ?? "bundle",
|
|
765
|
+
async flush(vaultId) {
|
|
766
|
+
await flush(vaultId);
|
|
767
|
+
},
|
|
768
|
+
async batch(vaultId, fn) {
|
|
769
|
+
await load(vaultId);
|
|
770
|
+
batchDepth++;
|
|
771
|
+
try {
|
|
772
|
+
await fn();
|
|
773
|
+
} finally {
|
|
774
|
+
batchDepth--;
|
|
775
|
+
}
|
|
776
|
+
await flush(vaultId);
|
|
777
|
+
},
|
|
778
|
+
async get(vault, collection, id) {
|
|
779
|
+
const snap = await load(vault);
|
|
780
|
+
return snap[collection]?.[id] ?? null;
|
|
781
|
+
},
|
|
782
|
+
async put(vault, collection, id, envelope, expectedVersion) {
|
|
783
|
+
const snap = await load(vault);
|
|
784
|
+
if (expectedVersion !== void 0) {
|
|
785
|
+
const current = snap[collection]?.[id];
|
|
786
|
+
const currentVersion = current?._v ?? 0;
|
|
787
|
+
if (currentVersion !== expectedVersion) {
|
|
788
|
+
throw new ConflictError(
|
|
789
|
+
currentVersion,
|
|
790
|
+
`Expected version ${expectedVersion} but found ${currentVersion} on ${collection}/${id}`
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
snap[collection] ??= {};
|
|
795
|
+
snap[collection][id] = envelope;
|
|
796
|
+
await maybeFlush(vault);
|
|
797
|
+
},
|
|
798
|
+
async delete(vault, collection, id) {
|
|
799
|
+
const snap = await load(vault);
|
|
800
|
+
if (snap[collection]) {
|
|
801
|
+
delete snap[collection][id];
|
|
802
|
+
await maybeFlush(vault);
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
async list(vault, collection) {
|
|
806
|
+
const snap = await load(vault);
|
|
807
|
+
return Object.keys(snap[collection] ?? {});
|
|
808
|
+
},
|
|
809
|
+
async loadAll(vault) {
|
|
810
|
+
return await load(vault);
|
|
811
|
+
},
|
|
812
|
+
async saveAll(vault, data) {
|
|
813
|
+
snapshots.set(vault, data);
|
|
814
|
+
loaded.add(vault);
|
|
815
|
+
await flush(vault);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
return store;
|
|
819
|
+
}
|
|
820
|
+
function mergeSnapshots2(remote, local) {
|
|
821
|
+
const merged = {};
|
|
822
|
+
for (const [coll, records] of Object.entries(remote)) {
|
|
823
|
+
merged[coll] = { ...records };
|
|
824
|
+
}
|
|
825
|
+
for (const [coll, records] of Object.entries(local)) {
|
|
826
|
+
if (!merged[coll]) {
|
|
827
|
+
merged[coll] = { ...records };
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
for (const [id, envelope] of Object.entries(records)) {
|
|
831
|
+
const existing = merged[coll][id];
|
|
832
|
+
if (!existing || envelope._ts >= existing._ts) {
|
|
833
|
+
merged[coll][id] = envelope;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return merged;
|
|
838
|
+
}
|
|
839
|
+
function createBundleStore(factory) {
|
|
840
|
+
return factory;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/store/sync-policy.ts
|
|
844
|
+
var INDEXED_STORE_POLICY = {
|
|
845
|
+
push: { mode: "on-change", minIntervalMs: 0, onUnload: true },
|
|
846
|
+
pull: { mode: "manual" }
|
|
847
|
+
};
|
|
848
|
+
var BUNDLE_STORE_POLICY = {
|
|
849
|
+
push: { mode: "debounce", debounceMs: 3e4, minIntervalMs: 12e4, onUnload: true },
|
|
850
|
+
pull: { mode: "interval", intervalMs: 6e4 }
|
|
851
|
+
};
|
|
852
|
+
var SyncScheduler = class {
|
|
853
|
+
policy;
|
|
854
|
+
callbacks;
|
|
855
|
+
_state = "idle";
|
|
856
|
+
_lastPushAt = null;
|
|
857
|
+
_lastPullAt = null;
|
|
858
|
+
_lastError = null;
|
|
859
|
+
_lastPushTime = 0;
|
|
860
|
+
// monotonic ms for minIntervalMs enforcement
|
|
861
|
+
// Timers
|
|
862
|
+
debounceTimer = null;
|
|
863
|
+
pushIntervalTimer = null;
|
|
864
|
+
pullIntervalTimer = null;
|
|
865
|
+
// Bound handlers for cleanup
|
|
866
|
+
boundOnVisibilityChange = null;
|
|
867
|
+
boundOnBeforeExit = null;
|
|
868
|
+
boundOnPageHide = null;
|
|
869
|
+
started = false;
|
|
870
|
+
constructor(policy, callbacks) {
|
|
871
|
+
this.policy = policy;
|
|
872
|
+
this.callbacks = callbacks;
|
|
873
|
+
if (this.shouldRegisterUnload()) {
|
|
874
|
+
this.boundOnVisibilityChange = this.handleVisibilityChange.bind(this);
|
|
875
|
+
this.boundOnPageHide = this.handlePageHide.bind(this);
|
|
876
|
+
this.boundOnBeforeExit = this.handleBeforeExit.bind(this);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/** Current scheduler status snapshot. */
|
|
880
|
+
get status() {
|
|
881
|
+
return {
|
|
882
|
+
state: this._state,
|
|
883
|
+
lastPushAt: this._lastPushAt,
|
|
884
|
+
lastPullAt: this._lastPullAt,
|
|
885
|
+
lastError: this._lastError,
|
|
886
|
+
pendingWrites: this.callbacks.getDirtyCount()
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
/** Start the scheduler — registers timers, event listeners. */
|
|
890
|
+
start() {
|
|
891
|
+
if (this.started) return;
|
|
892
|
+
this.started = true;
|
|
893
|
+
if (this.policy.push.mode === "interval" && this.policy.push.intervalMs) {
|
|
894
|
+
this.pushIntervalTimer = setInterval(() => {
|
|
895
|
+
void this.executePush();
|
|
896
|
+
}, this.policy.push.intervalMs);
|
|
897
|
+
}
|
|
898
|
+
if (this.policy.pull.mode === "interval" && this.policy.pull.intervalMs) {
|
|
899
|
+
this.pullIntervalTimer = setInterval(() => {
|
|
900
|
+
void this.executePull();
|
|
901
|
+
}, this.policy.pull.intervalMs);
|
|
902
|
+
}
|
|
903
|
+
if (this.policy.pull.mode === "on-focus" && typeof document !== "undefined") {
|
|
904
|
+
document.addEventListener("visibilitychange", this.handleFocusPull);
|
|
905
|
+
}
|
|
906
|
+
if (this.shouldRegisterUnload()) {
|
|
907
|
+
if (typeof document !== "undefined" && this.boundOnVisibilityChange) {
|
|
908
|
+
document.addEventListener("visibilitychange", this.boundOnVisibilityChange);
|
|
909
|
+
}
|
|
910
|
+
if (typeof globalThis.addEventListener === "function" && this.boundOnPageHide) {
|
|
911
|
+
globalThis.addEventListener("pagehide", this.boundOnPageHide);
|
|
912
|
+
}
|
|
913
|
+
if (typeof process !== "undefined" && this.boundOnBeforeExit) {
|
|
914
|
+
process.on("beforeExit", this.boundOnBeforeExit);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/** Stop the scheduler — clears timers, removes event listeners. */
|
|
919
|
+
stop() {
|
|
920
|
+
if (!this.started) return;
|
|
921
|
+
this.started = false;
|
|
922
|
+
if (this.debounceTimer) {
|
|
923
|
+
clearTimeout(this.debounceTimer);
|
|
924
|
+
this.debounceTimer = null;
|
|
925
|
+
}
|
|
926
|
+
if (this.pushIntervalTimer) {
|
|
927
|
+
clearInterval(this.pushIntervalTimer);
|
|
928
|
+
this.pushIntervalTimer = null;
|
|
929
|
+
}
|
|
930
|
+
if (this.pullIntervalTimer) {
|
|
931
|
+
clearInterval(this.pullIntervalTimer);
|
|
932
|
+
this.pullIntervalTimer = null;
|
|
933
|
+
}
|
|
934
|
+
if (this.policy.pull.mode === "on-focus" && typeof document !== "undefined") {
|
|
935
|
+
document.removeEventListener("visibilitychange", this.handleFocusPull);
|
|
936
|
+
}
|
|
937
|
+
if (typeof document !== "undefined" && this.boundOnVisibilityChange) {
|
|
938
|
+
document.removeEventListener("visibilitychange", this.boundOnVisibilityChange);
|
|
939
|
+
}
|
|
940
|
+
if (typeof globalThis.removeEventListener === "function" && this.boundOnPageHide) {
|
|
941
|
+
globalThis.removeEventListener("pagehide", this.boundOnPageHide);
|
|
942
|
+
}
|
|
943
|
+
if (typeof process !== "undefined" && this.boundOnBeforeExit) {
|
|
944
|
+
process.removeListener("beforeExit", this.boundOnBeforeExit);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Notify the scheduler that a local write occurred.
|
|
949
|
+
* For `on-change` mode: triggers immediate push (respecting minIntervalMs).
|
|
950
|
+
* For `debounce` mode: resets the debounce timer.
|
|
951
|
+
* For `manual` / `interval`: no-op.
|
|
952
|
+
*/
|
|
953
|
+
notifyChange() {
|
|
954
|
+
if (!this.started) return;
|
|
955
|
+
if (this.policy.push.mode === "on-change") {
|
|
956
|
+
void this.executePush();
|
|
957
|
+
} else if (this.policy.push.mode === "debounce") {
|
|
958
|
+
this.resetDebounce();
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
/** Force an immediate push, bypassing the scheduler. */
|
|
962
|
+
async forcePush() {
|
|
963
|
+
await this.executePush();
|
|
964
|
+
}
|
|
965
|
+
/** Force an immediate pull, bypassing the scheduler. */
|
|
966
|
+
async forcePull() {
|
|
967
|
+
await this.executePull();
|
|
968
|
+
}
|
|
969
|
+
// ─── Internal ─────────────────────────────────────────────────────
|
|
970
|
+
async executePush() {
|
|
971
|
+
if (this._state === "pushing") return;
|
|
972
|
+
const minInterval = this.policy.push.minIntervalMs ?? 0;
|
|
973
|
+
if (minInterval > 0) {
|
|
974
|
+
const elapsed = Date.now() - this._lastPushTime;
|
|
975
|
+
if (elapsed < minInterval) {
|
|
976
|
+
if (this.policy.push.mode === "debounce") {
|
|
977
|
+
this.scheduleDebounce(minInterval - elapsed);
|
|
978
|
+
}
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (this.callbacks.getDirtyCount() === 0) {
|
|
983
|
+
this._state = "idle";
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
this._state = "pushing";
|
|
987
|
+
try {
|
|
988
|
+
await this.callbacks.push();
|
|
989
|
+
this._lastPushAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
990
|
+
this._lastPushTime = Date.now();
|
|
991
|
+
this._lastError = null;
|
|
992
|
+
this._state = this.callbacks.getDirtyCount() > 0 ? "pending" : "idle";
|
|
993
|
+
} catch (err) {
|
|
994
|
+
this._lastError = err instanceof Error ? err : new Error(String(err));
|
|
995
|
+
this._state = "error";
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
async executePull() {
|
|
999
|
+
if (this._state === "pulling") return;
|
|
1000
|
+
const previousState = this._state;
|
|
1001
|
+
this._state = "pulling";
|
|
1002
|
+
try {
|
|
1003
|
+
await this.callbacks.pull();
|
|
1004
|
+
this._lastPullAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1005
|
+
this._lastError = null;
|
|
1006
|
+
this._state = previousState === "pending" ? "pending" : "idle";
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
this._lastError = err instanceof Error ? err : new Error(String(err));
|
|
1009
|
+
this._state = "error";
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
resetDebounce() {
|
|
1013
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
1014
|
+
const ms = this.policy.push.debounceMs ?? 3e4;
|
|
1015
|
+
this._state = "pending";
|
|
1016
|
+
this.scheduleDebounce(ms);
|
|
1017
|
+
}
|
|
1018
|
+
scheduleDebounce(ms) {
|
|
1019
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
1020
|
+
this.debounceTimer = setTimeout(() => {
|
|
1021
|
+
this.debounceTimer = null;
|
|
1022
|
+
void this.executePush();
|
|
1023
|
+
}, ms);
|
|
1024
|
+
}
|
|
1025
|
+
shouldRegisterUnload() {
|
|
1026
|
+
const onUnload = this.policy.push.onUnload;
|
|
1027
|
+
if (onUnload !== void 0) return onUnload;
|
|
1028
|
+
return this.policy.push.mode !== "manual";
|
|
1029
|
+
}
|
|
1030
|
+
// ─── Event handlers ───────────────────────────────────────────────
|
|
1031
|
+
handleVisibilityChange() {
|
|
1032
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
1033
|
+
this.fireUnloadPush();
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
handlePageHide() {
|
|
1037
|
+
this.fireUnloadPush();
|
|
1038
|
+
}
|
|
1039
|
+
handleBeforeExit() {
|
|
1040
|
+
this.fireUnloadPush();
|
|
1041
|
+
}
|
|
1042
|
+
handleFocusPull = () => {
|
|
1043
|
+
if (typeof document !== "undefined" && document.visibilityState === "visible") {
|
|
1044
|
+
void this.executePull();
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
fireUnloadPush() {
|
|
1048
|
+
if (this.callbacks.getDirtyCount() === 0) return;
|
|
1049
|
+
void this.callbacks.push().catch(() => {
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1054
|
+
0 && (module.exports = {
|
|
1055
|
+
BUNDLE_STORE_POLICY,
|
|
1056
|
+
INDEXED_STORE_POLICY,
|
|
1057
|
+
SyncScheduler,
|
|
1058
|
+
createBundleStore,
|
|
1059
|
+
routeStore,
|
|
1060
|
+
withCache,
|
|
1061
|
+
withCircuitBreaker,
|
|
1062
|
+
withHealthCheck,
|
|
1063
|
+
withLogging,
|
|
1064
|
+
withMetrics,
|
|
1065
|
+
withRetry,
|
|
1066
|
+
wrapBundleStore,
|
|
1067
|
+
wrapStore
|
|
1068
|
+
});
|
|
1069
|
+
//# sourceMappingURL=index.cjs.map
|