@xnetjs/data-bridge 0.0.2 → 0.0.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/README.md +5 -0
- package/dist/chunk-25WNZV7W.js +831 -0
- package/dist/chunk-5GTIP33X.js +201 -0
- package/dist/chunk-EPNW4GGU.js +460 -0
- package/dist/index.d.ts +259 -449
- package/dist/index.js +1335 -575
- package/dist/native-bridge.d.ts +126 -0
- package/dist/native-bridge.js +13 -0
- package/dist/query-descriptor-D0k2gUQ0.d.ts +298 -0
- package/dist/types-BRvuTwEn.d.ts +547 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +0 -0
- package/dist/worker/data-worker.d.ts +161 -1
- package/dist/worker/data-worker.js +468 -144
- package/package.json +16 -6
- package/dist/chunk-X6F5CPJI.js +0 -386
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xnetjs/data-bridge",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "DataBridge abstraction for off-main-thread data access in xNet",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
"import": "./dist/index.js",
|
|
16
16
|
"types": "./dist/index.d.ts"
|
|
17
17
|
},
|
|
18
|
+
"./native": {
|
|
19
|
+
"import": "./dist/native-bridge.js",
|
|
20
|
+
"types": "./dist/native-bridge.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./types": {
|
|
23
|
+
"import": "./dist/types.js",
|
|
24
|
+
"types": "./dist/types.d.ts"
|
|
25
|
+
},
|
|
18
26
|
"./worker": {
|
|
19
27
|
"import": "./dist/worker/data-worker.js",
|
|
20
28
|
"types": "./dist/worker/data-worker.d.ts"
|
|
@@ -33,18 +41,20 @@
|
|
|
33
41
|
"comlink": "^4.4.2",
|
|
34
42
|
"y-protocols": "^1.0.6",
|
|
35
43
|
"yjs": "^13.6.24",
|
|
36
|
-
"@xnetjs/core": "0.0.
|
|
37
|
-
"@xnetjs/data": "0.0.
|
|
44
|
+
"@xnetjs/core": "0.0.3",
|
|
45
|
+
"@xnetjs/data": "0.0.3",
|
|
46
|
+
"@xnetjs/sqlite": "0.0.3",
|
|
47
|
+
"@xnetjs/sync": "0.0.3"
|
|
38
48
|
},
|
|
39
49
|
"devDependencies": {
|
|
40
50
|
"tsup": "^8.0.0",
|
|
41
51
|
"typescript": "^5.4.0",
|
|
42
52
|
"vitest": "^4.0.0",
|
|
43
|
-
"@xnetjs/crypto": "0.0.
|
|
44
|
-
"@xnetjs/identity": "0.0.
|
|
53
|
+
"@xnetjs/crypto": "0.0.3",
|
|
54
|
+
"@xnetjs/identity": "0.0.3"
|
|
45
55
|
},
|
|
46
56
|
"scripts": {
|
|
47
|
-
"build": "tsup src/index.ts src/worker/data-worker.ts --format esm --dts",
|
|
57
|
+
"build": "tsup src/index.ts src/native-bridge.ts src/types.ts src/worker/data-worker.ts --format esm --dts",
|
|
48
58
|
"test": "vitest run",
|
|
49
59
|
"test:watch": "vitest",
|
|
50
60
|
"typecheck": "tsc --noEmit",
|
package/dist/chunk-X6F5CPJI.js
DELETED
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
// src/query-cache.ts
|
|
2
|
-
var DEFAULT_MAX_SIZE = 100;
|
|
3
|
-
var MIN_AGE_FOR_EVICTION = 3e4;
|
|
4
|
-
var WEAK_REF_CLEANUP_INTERVAL = 6e4;
|
|
5
|
-
var QueryCache = class {
|
|
6
|
-
cache = /* @__PURE__ */ new Map();
|
|
7
|
-
maxSize;
|
|
8
|
-
cleanupInterval = null;
|
|
9
|
-
constructor(options) {
|
|
10
|
-
this.maxSize = options?.maxSize ?? DEFAULT_MAX_SIZE;
|
|
11
|
-
const enableCleanup = options?.enableWeakRefCleanup ?? typeof window !== "undefined";
|
|
12
|
-
if (enableCleanup) {
|
|
13
|
-
this.startWeakRefCleanup();
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Start the interval that cleans up dead weak references.
|
|
18
|
-
*/
|
|
19
|
-
startWeakRefCleanup() {
|
|
20
|
-
if (this.cleanupInterval) return;
|
|
21
|
-
this.cleanupInterval = setInterval(() => {
|
|
22
|
-
this.cleanupDeadWeakRefs();
|
|
23
|
-
}, WEAK_REF_CLEANUP_INTERVAL);
|
|
24
|
-
if (typeof this.cleanupInterval === "object" && "unref" in this.cleanupInterval) {
|
|
25
|
-
this.cleanupInterval.unref();
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Stop the weak reference cleanup interval.
|
|
30
|
-
*/
|
|
31
|
-
stopCleanup() {
|
|
32
|
-
if (this.cleanupInterval) {
|
|
33
|
-
clearInterval(this.cleanupInterval);
|
|
34
|
-
this.cleanupInterval = null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Clean up dead weak references from all cache entries.
|
|
39
|
-
* Returns the number of dead references removed.
|
|
40
|
-
*/
|
|
41
|
-
cleanupDeadWeakRefs() {
|
|
42
|
-
let removed = 0;
|
|
43
|
-
for (const entry of this.cache.values()) {
|
|
44
|
-
for (const [identity, ref] of entry.weakSubscribers) {
|
|
45
|
-
if (ref.deref() === void 0) {
|
|
46
|
-
entry.weakSubscribers.delete(identity);
|
|
47
|
-
removed++;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return removed;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Compute a stable query ID from schema and options.
|
|
55
|
-
* Same query params should produce the same ID for deduplication.
|
|
56
|
-
*/
|
|
57
|
-
computeQueryId(schemaId, options) {
|
|
58
|
-
const parts = [schemaId];
|
|
59
|
-
if (options?.nodeId) {
|
|
60
|
-
parts.push(`id:${options.nodeId}`);
|
|
61
|
-
}
|
|
62
|
-
if (options?.where) {
|
|
63
|
-
const sortedWhere = Object.keys(options.where).sort().map((k) => `${k}:${JSON.stringify(options.where[k])}`).join(",");
|
|
64
|
-
parts.push(`where:{${sortedWhere}}`);
|
|
65
|
-
}
|
|
66
|
-
if (options?.includeDeleted) {
|
|
67
|
-
parts.push("deleted:true");
|
|
68
|
-
}
|
|
69
|
-
if (options?.orderBy) {
|
|
70
|
-
const sortedOrder = Object.entries(options.orderBy).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}:${v}`).join(",");
|
|
71
|
-
parts.push(`order:{${sortedOrder}}`);
|
|
72
|
-
}
|
|
73
|
-
if (options?.limit !== void 0) {
|
|
74
|
-
parts.push(`limit:${options.limit}`);
|
|
75
|
-
}
|
|
76
|
-
if (options?.offset !== void 0) {
|
|
77
|
-
parts.push(`offset:${options.offset}`);
|
|
78
|
-
}
|
|
79
|
-
return parts.join("|");
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Get cached data for a query (synchronous for useSyncExternalStore).
|
|
83
|
-
* Updates lastAccessed for LRU tracking.
|
|
84
|
-
*/
|
|
85
|
-
get(queryId) {
|
|
86
|
-
const entry = this.cache.get(queryId);
|
|
87
|
-
if (entry) {
|
|
88
|
-
entry.lastAccessed = Date.now();
|
|
89
|
-
}
|
|
90
|
-
return entry?.data ?? null;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Check if a query is in the cache.
|
|
94
|
-
*/
|
|
95
|
-
has(queryId) {
|
|
96
|
-
return this.cache.has(queryId);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Set cached data for a query and notify subscribers.
|
|
100
|
-
* Triggers LRU eviction if cache exceeds maxSize.
|
|
101
|
-
*/
|
|
102
|
-
set(queryId, data, schemaId, options) {
|
|
103
|
-
const entry = this.cache.get(queryId);
|
|
104
|
-
const now = Date.now();
|
|
105
|
-
if (entry) {
|
|
106
|
-
entry.data = data;
|
|
107
|
-
entry.lastUpdated = now;
|
|
108
|
-
entry.lastAccessed = now;
|
|
109
|
-
this.notifySubscribers(queryId);
|
|
110
|
-
} else {
|
|
111
|
-
this.evictIfNeeded();
|
|
112
|
-
this.cache.set(queryId, {
|
|
113
|
-
data,
|
|
114
|
-
subscribers: /* @__PURE__ */ new Set(),
|
|
115
|
-
weakSubscribers: /* @__PURE__ */ new Map(),
|
|
116
|
-
schemaId,
|
|
117
|
-
options,
|
|
118
|
-
lastUpdated: now,
|
|
119
|
-
lastAccessed: now
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Initialize a cache entry (called when starting a subscription).
|
|
125
|
-
*/
|
|
126
|
-
initEntry(queryId, schemaId, options) {
|
|
127
|
-
if (!this.cache.has(queryId)) {
|
|
128
|
-
const now = Date.now();
|
|
129
|
-
this.cache.set(queryId, {
|
|
130
|
-
data: null,
|
|
131
|
-
subscribers: /* @__PURE__ */ new Set(),
|
|
132
|
-
weakSubscribers: /* @__PURE__ */ new Map(),
|
|
133
|
-
schemaId,
|
|
134
|
-
options,
|
|
135
|
-
lastUpdated: 0,
|
|
136
|
-
lastAccessed: now
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Subscribe to cache updates for a query.
|
|
142
|
-
* Uses strong references - callback will not be garbage collected until unsubscribed.
|
|
143
|
-
*/
|
|
144
|
-
subscribe(queryId, callback) {
|
|
145
|
-
const entry = this.cache.get(queryId);
|
|
146
|
-
if (entry) {
|
|
147
|
-
entry.subscribers.add(callback);
|
|
148
|
-
}
|
|
149
|
-
return () => {
|
|
150
|
-
const e = this.cache.get(queryId);
|
|
151
|
-
if (e) {
|
|
152
|
-
e.subscribers.delete(callback);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Subscribe with a weak reference.
|
|
158
|
-
* The callback can be garbage collected if the owning component is unmounted
|
|
159
|
-
* and no other references to the callback exist.
|
|
160
|
-
*
|
|
161
|
-
* This is useful for long-lived subscriptions where you want automatic cleanup
|
|
162
|
-
* without explicit unsubscribe calls. However, for React components, prefer
|
|
163
|
-
* the regular subscribe() with proper cleanup in useEffect.
|
|
164
|
-
*
|
|
165
|
-
* @param queryId - The query to subscribe to
|
|
166
|
-
* @param callback - The callback to invoke on updates
|
|
167
|
-
* @returns Unsubscribe function
|
|
168
|
-
*/
|
|
169
|
-
subscribeWeak(queryId, callback) {
|
|
170
|
-
const entry = this.cache.get(queryId);
|
|
171
|
-
if (entry) {
|
|
172
|
-
entry.weakSubscribers.set(callback, new WeakRef(callback));
|
|
173
|
-
}
|
|
174
|
-
return () => {
|
|
175
|
-
const e = this.cache.get(queryId);
|
|
176
|
-
if (e) {
|
|
177
|
-
e.weakSubscribers.delete(callback);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Notify all subscribers of a query that data has changed.
|
|
183
|
-
* Handles both strong and weak subscribers, cleaning up dead weak refs.
|
|
184
|
-
*/
|
|
185
|
-
notifySubscribers(queryId) {
|
|
186
|
-
const entry = this.cache.get(queryId);
|
|
187
|
-
if (!entry) return;
|
|
188
|
-
for (const callback of entry.subscribers) {
|
|
189
|
-
callback();
|
|
190
|
-
}
|
|
191
|
-
for (const [identity, ref] of entry.weakSubscribers) {
|
|
192
|
-
const callback = ref.deref();
|
|
193
|
-
if (callback) {
|
|
194
|
-
callback();
|
|
195
|
-
} else {
|
|
196
|
-
entry.weakSubscribers.delete(identity);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Get the number of active subscribers for a query.
|
|
202
|
-
* Includes both strong and live weak subscribers.
|
|
203
|
-
*/
|
|
204
|
-
getSubscriberCount(queryId) {
|
|
205
|
-
const entry = this.cache.get(queryId);
|
|
206
|
-
if (!entry) return 0;
|
|
207
|
-
let count = entry.subscribers.size;
|
|
208
|
-
for (const ref of entry.weakSubscribers.values()) {
|
|
209
|
-
if (ref.deref() !== void 0) {
|
|
210
|
-
count++;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return count;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Get the number of weak subscribers (including potentially dead ones).
|
|
217
|
-
* Useful for debugging.
|
|
218
|
-
*/
|
|
219
|
-
getWeakSubscriberCount(queryId) {
|
|
220
|
-
return this.cache.get(queryId)?.weakSubscribers.size ?? 0;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Remove a query from the cache.
|
|
224
|
-
*/
|
|
225
|
-
delete(queryId) {
|
|
226
|
-
this.cache.delete(queryId);
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Get all query IDs that match a schema.
|
|
230
|
-
*/
|
|
231
|
-
getQueriesForSchema(schemaId) {
|
|
232
|
-
const matches = [];
|
|
233
|
-
for (const [queryId, entry] of this.cache) {
|
|
234
|
-
if (entry.schemaId === schemaId) {
|
|
235
|
-
matches.push(queryId);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return matches;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Get the schema IRI for a cached query.
|
|
242
|
-
*/
|
|
243
|
-
getSchemaId(queryId) {
|
|
244
|
-
return this.cache.get(queryId)?.schemaId;
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Get the options for a cached query.
|
|
248
|
-
*/
|
|
249
|
-
getOptions(queryId) {
|
|
250
|
-
return this.cache.get(queryId)?.options;
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Clear the entire cache and stop cleanup interval.
|
|
254
|
-
*/
|
|
255
|
-
clear() {
|
|
256
|
-
this.stopCleanup();
|
|
257
|
-
this.cache.clear();
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Destroy the cache, stopping all cleanup intervals.
|
|
261
|
-
*/
|
|
262
|
-
destroy() {
|
|
263
|
-
this.stopCleanup();
|
|
264
|
-
this.cache.clear();
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Get the number of cached queries.
|
|
268
|
-
*/
|
|
269
|
-
get size() {
|
|
270
|
-
return this.cache.size;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Get the maximum cache size.
|
|
274
|
-
*/
|
|
275
|
-
get maxCacheSize() {
|
|
276
|
-
return this.maxSize;
|
|
277
|
-
}
|
|
278
|
-
// ─── LRU Eviction ──────────────────────────────────────────────────────────
|
|
279
|
-
/**
|
|
280
|
-
* Check if an entry has any active subscribers (strong or live weak).
|
|
281
|
-
*/
|
|
282
|
-
hasActiveSubscribers(entry) {
|
|
283
|
-
if (entry.subscribers.size > 0) return true;
|
|
284
|
-
for (const ref of entry.weakSubscribers.values()) {
|
|
285
|
-
if (ref.deref() !== void 0) return true;
|
|
286
|
-
}
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Evict least-recently-used entries if cache exceeds maxSize.
|
|
291
|
-
* Only evicts entries with no active subscribers and older than MIN_AGE_FOR_EVICTION.
|
|
292
|
-
*/
|
|
293
|
-
evictIfNeeded() {
|
|
294
|
-
if (this.cache.size < this.maxSize) return;
|
|
295
|
-
const now = Date.now();
|
|
296
|
-
const candidates = [];
|
|
297
|
-
for (const [queryId, entry] of this.cache) {
|
|
298
|
-
if (!this.hasActiveSubscribers(entry) && now - entry.lastAccessed > MIN_AGE_FOR_EVICTION) {
|
|
299
|
-
candidates.push({ queryId, lastAccessed: entry.lastAccessed });
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
if (candidates.length === 0) return;
|
|
303
|
-
candidates.sort((a, b) => a.lastAccessed - b.lastAccessed);
|
|
304
|
-
const targetSize = Math.floor(this.maxSize * 0.8);
|
|
305
|
-
const toEvict = this.cache.size - targetSize;
|
|
306
|
-
for (let i = 0; i < Math.min(toEvict, candidates.length); i++) {
|
|
307
|
-
this.cache.delete(candidates[i].queryId);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Manually trigger eviction (for testing or explicit cleanup).
|
|
312
|
-
*/
|
|
313
|
-
evict() {
|
|
314
|
-
const sizeBefore = this.cache.size;
|
|
315
|
-
this.evictIfNeeded();
|
|
316
|
-
return sizeBefore - this.cache.size;
|
|
317
|
-
}
|
|
318
|
-
// ─── Helpers for filtering and sorting ─────────────────────────────────────
|
|
319
|
-
/**
|
|
320
|
-
* Filter nodes based on query options.
|
|
321
|
-
*/
|
|
322
|
-
filterNodes(nodes, options) {
|
|
323
|
-
if (!options) return nodes;
|
|
324
|
-
let result = nodes;
|
|
325
|
-
if (options.where) {
|
|
326
|
-
result = result.filter((node) => {
|
|
327
|
-
for (const [key, value] of Object.entries(options.where)) {
|
|
328
|
-
if (node.properties[key] !== value) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return true;
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
if (!options.includeDeleted) {
|
|
336
|
-
result = result.filter((node) => !node.deleted);
|
|
337
|
-
}
|
|
338
|
-
return result;
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Sort nodes based on query options.
|
|
342
|
-
*/
|
|
343
|
-
sortNodes(nodes, options) {
|
|
344
|
-
if (!options?.orderBy) return nodes;
|
|
345
|
-
const entries = Object.entries(options.orderBy);
|
|
346
|
-
if (entries.length === 0) return nodes;
|
|
347
|
-
return [...nodes].sort((a, b) => {
|
|
348
|
-
for (const [key, direction] of entries) {
|
|
349
|
-
const keyStr = key;
|
|
350
|
-
let aVal;
|
|
351
|
-
let bVal;
|
|
352
|
-
if (keyStr === "createdAt" || keyStr === "updatedAt") {
|
|
353
|
-
aVal = a[keyStr];
|
|
354
|
-
bVal = b[keyStr];
|
|
355
|
-
} else {
|
|
356
|
-
aVal = a.properties[keyStr];
|
|
357
|
-
bVal = b.properties[keyStr];
|
|
358
|
-
}
|
|
359
|
-
if (aVal === bVal) continue;
|
|
360
|
-
if (aVal == null) return direction === "asc" ? 1 : -1;
|
|
361
|
-
if (bVal == null) return direction === "asc" ? -1 : 1;
|
|
362
|
-
const comparison = aVal < bVal ? -1 : 1;
|
|
363
|
-
return direction === "asc" ? comparison : -comparison;
|
|
364
|
-
}
|
|
365
|
-
return 0;
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Apply pagination to nodes.
|
|
370
|
-
*/
|
|
371
|
-
paginateNodes(nodes, options) {
|
|
372
|
-
if (!options) return nodes;
|
|
373
|
-
let result = nodes;
|
|
374
|
-
if (options.offset !== void 0 && options.offset > 0) {
|
|
375
|
-
result = result.slice(options.offset);
|
|
376
|
-
}
|
|
377
|
-
if (options.limit !== void 0 && options.limit > 0) {
|
|
378
|
-
result = result.slice(0, options.limit);
|
|
379
|
-
}
|
|
380
|
-
return result;
|
|
381
|
-
}
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
export {
|
|
385
|
-
QueryCache
|
|
386
|
-
};
|