@arcote.tech/arc 0.7.15 → 0.7.16
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/dist/adapters/event-publisher.d.ts +1 -8
- package/dist/adapters/event-wire.d.ts +28 -22
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/query-wire.d.ts +2 -2
- package/dist/context-element/aggregate/aggregate-element.d.ts +0 -10
- package/dist/context-element/view/view.d.ts +0 -10
- package/dist/data-storage/data-storage-master.d.ts +2 -4
- package/dist/data-storage/data-storage-observable.d.ts +2 -4
- package/dist/data-storage/data-storage-observable.test.d.ts +2 -0
- package/dist/data-storage/data-storage.abstract.d.ts +1 -14
- package/dist/data-storage/store-state-master.d.ts +1 -11
- package/dist/index.js +275 -312
- package/dist/model/live-query/diff.d.ts +42 -0
- package/dist/model/live-query/diff.test.d.ts +2 -0
- package/dist/model/live-query/index.d.ts +2 -0
- package/dist/model/live-query/live-query-subscription.d.ts +66 -0
- package/dist/streaming/index.d.ts +1 -1
- package/dist/streaming/streaming-event-publisher.d.ts +8 -10
- package/dist/streaming/streaming-query-cache.d.ts +35 -96
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -211,7 +211,8 @@ class EventWire {
|
|
|
211
211
|
onSyncedCallback;
|
|
212
212
|
reconnectTimeout;
|
|
213
213
|
syncRequested = false;
|
|
214
|
-
|
|
214
|
+
querySubscriptions = new Map;
|
|
215
|
+
querySubCounter = 0;
|
|
215
216
|
enableEventSync;
|
|
216
217
|
constructor(baseUrl, options) {
|
|
217
218
|
this.baseUrl = baseUrl;
|
|
@@ -266,7 +267,7 @@ class EventWire {
|
|
|
266
267
|
this.requestSync();
|
|
267
268
|
}
|
|
268
269
|
this.flushPendingEvents();
|
|
269
|
-
this.
|
|
270
|
+
this.sendAllQuerySubscriptions();
|
|
270
271
|
} else {
|
|
271
272
|
console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
|
|
272
273
|
}
|
|
@@ -342,24 +343,29 @@ class EventWire {
|
|
|
342
343
|
onSynced(callback) {
|
|
343
344
|
this.onSyncedCallback = callback;
|
|
344
345
|
}
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
this.
|
|
346
|
+
subscribeQuery(descriptor, scope, callbacks) {
|
|
347
|
+
const subscriptionId = `qs_${this.instanceId}_${++this.querySubCounter}`;
|
|
348
|
+
this.querySubscriptions.set(subscriptionId, {
|
|
349
|
+
descriptor,
|
|
350
|
+
scope,
|
|
351
|
+
callbacks
|
|
352
|
+
});
|
|
348
353
|
if (this.state === "connected" && this.ws) {
|
|
349
354
|
this.ws.send(JSON.stringify({
|
|
350
|
-
type: "subscribe-
|
|
351
|
-
|
|
355
|
+
type: "subscribe-query",
|
|
356
|
+
subscriptionId,
|
|
357
|
+
descriptor,
|
|
352
358
|
scope
|
|
353
359
|
}));
|
|
354
360
|
}
|
|
361
|
+
return subscriptionId;
|
|
355
362
|
}
|
|
356
|
-
|
|
357
|
-
this.
|
|
363
|
+
unsubscribeQuery(subscriptionId) {
|
|
364
|
+
this.querySubscriptions.delete(subscriptionId);
|
|
358
365
|
if (this.state === "connected" && this.ws) {
|
|
359
366
|
this.ws.send(JSON.stringify({
|
|
360
|
-
type: "unsubscribe-
|
|
361
|
-
|
|
362
|
-
scope
|
|
367
|
+
type: "unsubscribe-query",
|
|
368
|
+
subscriptionId
|
|
363
369
|
}));
|
|
364
370
|
}
|
|
365
371
|
}
|
|
@@ -394,17 +400,17 @@ class EventWire {
|
|
|
394
400
|
this.lastHostEventId = message.lastHostEventId;
|
|
395
401
|
}
|
|
396
402
|
break;
|
|
397
|
-
case "
|
|
398
|
-
const sub = this.
|
|
403
|
+
case "query-snapshot": {
|
|
404
|
+
const sub = this.querySubscriptions.get(message.subscriptionId);
|
|
399
405
|
if (sub) {
|
|
400
|
-
sub.onSnapshot(message.
|
|
406
|
+
sub.callbacks.onSnapshot(message.result ?? null);
|
|
401
407
|
}
|
|
402
408
|
break;
|
|
403
409
|
}
|
|
404
|
-
case "
|
|
405
|
-
const sub = this.
|
|
410
|
+
case "query-changes": {
|
|
411
|
+
const sub = this.querySubscriptions.get(message.subscriptionId);
|
|
406
412
|
if (sub && Array.isArray(message.changes)) {
|
|
407
|
-
sub.onChanges(message.changes);
|
|
413
|
+
sub.callbacks.onChanges(message.changes);
|
|
408
414
|
}
|
|
409
415
|
break;
|
|
410
416
|
}
|
|
@@ -432,17 +438,15 @@ class EventWire {
|
|
|
432
438
|
this.pendingEvents = [];
|
|
433
439
|
}
|
|
434
440
|
}
|
|
435
|
-
|
|
441
|
+
sendAllQuerySubscriptions() {
|
|
436
442
|
if (!this.ws || this.state !== "connected")
|
|
437
443
|
return;
|
|
438
|
-
for (const
|
|
439
|
-
const sepIdx = key.indexOf(":");
|
|
440
|
-
const scope = key.slice(0, sepIdx);
|
|
441
|
-
const element = key.slice(sepIdx + 1);
|
|
444
|
+
for (const [subscriptionId, sub] of this.querySubscriptions) {
|
|
442
445
|
this.ws.send(JSON.stringify({
|
|
443
|
-
type: "subscribe-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
+
type: "subscribe-query",
|
|
447
|
+
subscriptionId,
|
|
448
|
+
descriptor: sub.descriptor,
|
|
449
|
+
scope: sub.scope
|
|
446
450
|
}));
|
|
447
451
|
}
|
|
448
452
|
}
|
|
@@ -484,19 +488,12 @@ class LocalEventPublisher {
|
|
|
484
488
|
views = [];
|
|
485
489
|
syncCallback;
|
|
486
490
|
subscribers = new Map;
|
|
487
|
-
viewChangesCallbacks = new Set;
|
|
488
491
|
constructor(dataStorage) {
|
|
489
492
|
this.dataStorage = dataStorage;
|
|
490
493
|
}
|
|
491
494
|
onPublish(callback) {
|
|
492
495
|
this.syncCallback = callback;
|
|
493
496
|
}
|
|
494
|
-
onViewChanges(callback) {
|
|
495
|
-
this.viewChangesCallbacks.add(callback);
|
|
496
|
-
return () => {
|
|
497
|
-
this.viewChangesCallbacks.delete(callback);
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
497
|
registerViews(views) {
|
|
501
498
|
this.views = views;
|
|
502
499
|
}
|
|
@@ -564,19 +561,7 @@ class LocalEventPublisher {
|
|
|
564
561
|
});
|
|
565
562
|
const viewChanges = await this.collectViewChanges(event);
|
|
566
563
|
allChanges.push(...viewChanges);
|
|
567
|
-
|
|
568
|
-
const committed = await this.dataStorage.commitChanges(allChanges, {
|
|
569
|
-
captureRowsFor: viewStoreNames
|
|
570
|
-
});
|
|
571
|
-
if (committed.length > 0 && this.viewChangesCallbacks.size > 0) {
|
|
572
|
-
for (const callback of this.viewChangesCallbacks) {
|
|
573
|
-
try {
|
|
574
|
-
callback(committed);
|
|
575
|
-
} catch (error) {
|
|
576
|
-
console.error(`[EventPublisher] onViewChanges callback error:`, error);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
564
|
+
await this.dataStorage.commitChanges(allChanges);
|
|
580
565
|
await this.notifySubscribers(event);
|
|
581
566
|
if (this.syncCallback) {
|
|
582
567
|
this.syncCallback(event);
|
|
@@ -1262,9 +1247,8 @@ function object(element) {
|
|
|
1262
1247
|
|
|
1263
1248
|
// src/data-storage/data-storage.abstract.ts
|
|
1264
1249
|
class DataStorage {
|
|
1265
|
-
async commitChanges(changes
|
|
1250
|
+
async commitChanges(changes) {
|
|
1266
1251
|
await Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
|
|
1267
|
-
return [];
|
|
1268
1252
|
}
|
|
1269
1253
|
}
|
|
1270
1254
|
|
|
@@ -2229,11 +2213,6 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
2229
2213
|
}
|
|
2230
2214
|
return adapters.dataStorage.getStore(viewName).find(options);
|
|
2231
2215
|
}
|
|
2232
|
-
if (adapters.streamingCache) {
|
|
2233
|
-
const store = adapters.streamingCache.getStore(viewName, adapters.scope?.scopeName);
|
|
2234
|
-
if (store.hasData())
|
|
2235
|
-
return store.find(options);
|
|
2236
|
-
}
|
|
2237
2216
|
if (adapters.queryWire)
|
|
2238
2217
|
return adapters.queryWire.query(viewName, options);
|
|
2239
2218
|
return [];
|
|
@@ -2261,12 +2240,6 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
2261
2240
|
}
|
|
2262
2241
|
};
|
|
2263
2242
|
}
|
|
2264
|
-
getRestrictionsFor(adapters) {
|
|
2265
|
-
return {
|
|
2266
|
-
restrictions: this.getScopeRestrictions(adapters),
|
|
2267
|
-
denied: this.isScopeDenied(adapters)
|
|
2268
|
-
};
|
|
2269
|
-
}
|
|
2270
2243
|
isScopeDenied(adapters, protections = this._protections) {
|
|
2271
2244
|
if (protections.length === 0)
|
|
2272
2245
|
return false;
|
|
@@ -2912,13 +2885,6 @@ class ArcView extends ArcContextElement {
|
|
|
2912
2885
|
}
|
|
2913
2886
|
return adapters.dataStorage.getStore(viewName).find(options);
|
|
2914
2887
|
}
|
|
2915
|
-
if (adapters.streamingCache) {
|
|
2916
|
-
const store = adapters.streamingCache.getStore(viewName, adapters.scope?.scopeName);
|
|
2917
|
-
if (store.hasData()) {
|
|
2918
|
-
const where = restrictions ? { ...options?.where || {}, ...restrictions } : options?.where;
|
|
2919
|
-
return store.find({ ...options, where });
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
2888
|
if (adapters.queryWire) {
|
|
2923
2889
|
const where = restrictions ? { ...options?.where || {}, ...restrictions } : options?.where;
|
|
2924
2890
|
return adapters.queryWire.query(viewName, { ...options, where });
|
|
@@ -2937,13 +2903,6 @@ class ArcView extends ArcContextElement {
|
|
|
2937
2903
|
const results = await adapters.dataStorage.getStore(viewName).find({ where });
|
|
2938
2904
|
return results[0];
|
|
2939
2905
|
}
|
|
2940
|
-
if (adapters.streamingCache) {
|
|
2941
|
-
const store = adapters.streamingCache.getStore(viewName, adapters.scope?.scopeName);
|
|
2942
|
-
if (store.hasData()) {
|
|
2943
|
-
const mergedWhere = restrictions ? { ...where || {}, ...restrictions } : where;
|
|
2944
|
-
return store.findOne(mergedWhere);
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
2906
|
if (adapters.queryWire) {
|
|
2948
2907
|
const mergedWhere = restrictions ? { ...where, ...restrictions } : where;
|
|
2949
2908
|
const results = await adapters.queryWire.query(viewName, { where: mergedWhere });
|
|
@@ -2953,9 +2912,6 @@ class ArcView extends ArcContextElement {
|
|
|
2953
2912
|
}
|
|
2954
2913
|
};
|
|
2955
2914
|
}
|
|
2956
|
-
getRestrictionsFor(adapters) {
|
|
2957
|
-
return this.resolveProtection(this.data.protections || [], adapters);
|
|
2958
|
-
}
|
|
2959
2915
|
resolveProtection(protections, adapters) {
|
|
2960
2916
|
if (protections.length === 0)
|
|
2961
2917
|
return { restrictions: null, denied: false };
|
|
@@ -3403,12 +3359,8 @@ class MasterStoreState extends StoreState {
|
|
|
3403
3359
|
}
|
|
3404
3360
|
return transaction.find(this.storeName, { where: { _id: id2 } }).then((results) => results[0]);
|
|
3405
3361
|
}
|
|
3406
|
-
async applyChangeAndReturnEvent(transaction, change, transactionCache
|
|
3362
|
+
async applyChangeAndReturnEvent(transaction, change, transactionCache) {
|
|
3407
3363
|
if (change.type === "set") {
|
|
3408
|
-
let existing;
|
|
3409
|
-
if (options?.captureRows) {
|
|
3410
|
-
existing = await this.readExisting(transaction, change.data._id, transactionCache);
|
|
3411
|
-
}
|
|
3412
3364
|
await transaction.set(this.storeName, change.data);
|
|
3413
3365
|
const item = this.deserialize ? this.deserialize(change.data) : change.data;
|
|
3414
3366
|
if (transactionCache) {
|
|
@@ -3421,16 +3373,10 @@ class MasterStoreState extends StoreState {
|
|
|
3421
3373
|
type: "set",
|
|
3422
3374
|
item: change.data,
|
|
3423
3375
|
id: change.data._id
|
|
3424
|
-
}
|
|
3425
|
-
oldRow: existing ?? null,
|
|
3426
|
-
newRow: change.data
|
|
3376
|
+
}
|
|
3427
3377
|
};
|
|
3428
3378
|
}
|
|
3429
3379
|
if (change.type === "delete") {
|
|
3430
|
-
let existing;
|
|
3431
|
-
if (options?.captureRows) {
|
|
3432
|
-
existing = await this.readExisting(transaction, change.id, transactionCache);
|
|
3433
|
-
}
|
|
3434
3380
|
await transaction.remove(this.storeName, change.id);
|
|
3435
3381
|
if (transactionCache) {
|
|
3436
3382
|
transactionCache.delete(`${this.storeName}:${change.id}`);
|
|
@@ -3442,9 +3388,7 @@ class MasterStoreState extends StoreState {
|
|
|
3442
3388
|
type: "delete",
|
|
3443
3389
|
item: null,
|
|
3444
3390
|
id: change.id
|
|
3445
|
-
}
|
|
3446
|
-
oldRow: existing ?? null,
|
|
3447
|
-
newRow: null
|
|
3391
|
+
}
|
|
3448
3392
|
};
|
|
3449
3393
|
}
|
|
3450
3394
|
if (change.type === "modify") {
|
|
@@ -3462,9 +3406,7 @@ class MasterStoreState extends StoreState {
|
|
|
3462
3406
|
type: "set",
|
|
3463
3407
|
item,
|
|
3464
3408
|
id: change.id
|
|
3465
|
-
}
|
|
3466
|
-
oldRow: existing ?? null,
|
|
3467
|
-
newRow: updated
|
|
3409
|
+
}
|
|
3468
3410
|
};
|
|
3469
3411
|
}
|
|
3470
3412
|
if (change.type === "mutate") {
|
|
@@ -3482,9 +3424,7 @@ class MasterStoreState extends StoreState {
|
|
|
3482
3424
|
type: "set",
|
|
3483
3425
|
item,
|
|
3484
3426
|
id: change.id
|
|
3485
|
-
}
|
|
3486
|
-
oldRow: existing ?? null,
|
|
3487
|
-
newRow: updated
|
|
3427
|
+
}
|
|
3488
3428
|
};
|
|
3489
3429
|
}
|
|
3490
3430
|
throw new Error("Unknown change type");
|
|
@@ -3563,22 +3503,17 @@ class MasterDataStorage extends DataStorage {
|
|
|
3563
3503
|
applySerializedChanges(changes) {
|
|
3564
3504
|
return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applySerializedChanges(changes2)));
|
|
3565
3505
|
}
|
|
3566
|
-
async commitChanges(changes
|
|
3506
|
+
async commitChanges(changes) {
|
|
3567
3507
|
const transaction = await this.getReadWriteTransaction();
|
|
3568
3508
|
const transactionCache = new Map;
|
|
3569
3509
|
const eventsByStore = new Map;
|
|
3570
|
-
const committed = [];
|
|
3571
3510
|
for (const { store, changes: storeChanges } of changes) {
|
|
3572
3511
|
const storeState = this.getStore(store);
|
|
3573
3512
|
const storeEvents = [];
|
|
3574
|
-
const capture = options?.captureRowsFor?.has(store) ?? false;
|
|
3575
3513
|
for (const change of storeChanges) {
|
|
3576
|
-
const { event: event3
|
|
3514
|
+
const { event: event3 } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache);
|
|
3577
3515
|
if (event3)
|
|
3578
3516
|
storeEvents.push(event3);
|
|
3579
|
-
if (capture) {
|
|
3580
|
-
committed.push({ store, id: event3.id, oldRow, newRow });
|
|
3581
|
-
}
|
|
3582
3517
|
}
|
|
3583
3518
|
if (storeEvents.length > 0) {
|
|
3584
3519
|
eventsByStore.set(store, storeEvents);
|
|
@@ -3589,7 +3524,6 @@ class MasterDataStorage extends DataStorage {
|
|
|
3589
3524
|
const storeState = this.getStore(store);
|
|
3590
3525
|
storeState.notifyListenersPublic(events);
|
|
3591
3526
|
}
|
|
3592
|
-
return committed;
|
|
3593
3527
|
}
|
|
3594
3528
|
fork() {
|
|
3595
3529
|
return new ForkedDataStorage(this);
|
|
@@ -3706,8 +3640,8 @@ class ObservableDataStorage {
|
|
|
3706
3640
|
getReadWriteTransaction() {
|
|
3707
3641
|
return this.source.getReadWriteTransaction();
|
|
3708
3642
|
}
|
|
3709
|
-
commitChanges(changes
|
|
3710
|
-
return this.source.commitChanges(changes
|
|
3643
|
+
commitChanges(changes) {
|
|
3644
|
+
return this.source.commitChanges(changes);
|
|
3711
3645
|
}
|
|
3712
3646
|
trackQuery(storeName, options, result, listener4) {
|
|
3713
3647
|
const key = this.getQueryKey(storeName, options);
|
|
@@ -3720,7 +3654,8 @@ class ObservableDataStorage {
|
|
|
3720
3654
|
}
|
|
3721
3655
|
handleStoreChange(storeName, events) {
|
|
3722
3656
|
let hasChanges = false;
|
|
3723
|
-
|
|
3657
|
+
const staleKeys = [];
|
|
3658
|
+
for (const [key, query] of this.trackedQueries) {
|
|
3724
3659
|
if (query.storeName !== storeName)
|
|
3725
3660
|
continue;
|
|
3726
3661
|
let currentResult = query.result;
|
|
@@ -3732,10 +3667,20 @@ class ObservableDataStorage {
|
|
|
3732
3667
|
queryChanged = true;
|
|
3733
3668
|
}
|
|
3734
3669
|
}
|
|
3735
|
-
if (queryChanged)
|
|
3736
|
-
|
|
3670
|
+
if (!queryChanged)
|
|
3671
|
+
continue;
|
|
3672
|
+
if (query.options.limit !== undefined && query.result.length === query.options.limit && currentResult.length < query.options.limit) {
|
|
3673
|
+
staleKeys.push(key);
|
|
3737
3674
|
hasChanges = true;
|
|
3675
|
+
continue;
|
|
3738
3676
|
}
|
|
3677
|
+
query.result = currentResult;
|
|
3678
|
+
hasChanges = true;
|
|
3679
|
+
}
|
|
3680
|
+
for (const key of staleKeys) {
|
|
3681
|
+
const query = this.trackedQueries.get(key);
|
|
3682
|
+
this.source.getStore(query.storeName).unsubscribe(query.listener);
|
|
3683
|
+
this.trackedQueries.delete(key);
|
|
3739
3684
|
}
|
|
3740
3685
|
if (hasChanges) {
|
|
3741
3686
|
this.onChange();
|
|
@@ -4621,245 +4566,260 @@ function mutationExecutor(model) {
|
|
|
4621
4566
|
}
|
|
4622
4567
|
});
|
|
4623
4568
|
}
|
|
4624
|
-
// src/
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4569
|
+
// src/model/live-query/diff.ts
|
|
4570
|
+
function isIdList(value) {
|
|
4571
|
+
return Array.isArray(value) && value.every((it) => it && typeof it === "object" && typeof it._id === "string");
|
|
4572
|
+
}
|
|
4573
|
+
function diffResults(prev, next) {
|
|
4574
|
+
if (!isIdList(prev) || !isIdList(next)) {
|
|
4575
|
+
return JSON.stringify(prev) === JSON.stringify(next) ? { kind: "none" } : { kind: "snapshot", result: next };
|
|
4576
|
+
}
|
|
4577
|
+
const json = (o) => JSON.stringify(o);
|
|
4578
|
+
const nextIds = new Set(next.map((it) => it._id));
|
|
4579
|
+
const changes = [];
|
|
4580
|
+
for (const it of prev) {
|
|
4581
|
+
if (!nextIds.has(it._id)) {
|
|
4582
|
+
changes.push({ type: "delete", id: it._id });
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
const sim = prev.filter((it) => nextIds.has(it._id));
|
|
4586
|
+
for (let i = 0;i < next.length; i++) {
|
|
4587
|
+
const target = next[i];
|
|
4588
|
+
if (sim[i] && sim[i]._id === target._id && json(sim[i]) === json(target)) {
|
|
4589
|
+
continue;
|
|
4643
4590
|
}
|
|
4644
|
-
|
|
4591
|
+
changes.push({ type: "set", id: target._id, item: target, index: i });
|
|
4592
|
+
const oldIdx = sim.findIndex((it) => it._id === target._id);
|
|
4593
|
+
if (oldIdx !== -1)
|
|
4594
|
+
sim.splice(oldIdx, 1);
|
|
4595
|
+
sim.splice(i, 0, target);
|
|
4645
4596
|
}
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4597
|
+
if (changes.length === 0)
|
|
4598
|
+
return { kind: "none" };
|
|
4599
|
+
if (changes.length > next.length) {
|
|
4600
|
+
return { kind: "snapshot", result: next };
|
|
4649
4601
|
}
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
if (pending) {
|
|
4653
|
-
clearTimeout(pending);
|
|
4654
|
-
this.pendingUnsubscribes.delete(key);
|
|
4655
|
-
}
|
|
4656
|
-
const existing = this.activeStreams.get(key);
|
|
4657
|
-
if (existing) {
|
|
4658
|
-
existing.refCount++;
|
|
4659
|
-
return {
|
|
4660
|
-
unsubscribe: () => this.unregisterStream(key),
|
|
4661
|
-
wasReused: true
|
|
4662
|
-
};
|
|
4663
|
-
}
|
|
4664
|
-
const streamConn = createStream();
|
|
4665
|
-
this.activeStreams.set(key, {
|
|
4666
|
-
unsubscribe: streamConn.unsubscribe,
|
|
4667
|
-
refCount: 1
|
|
4668
|
-
});
|
|
4669
|
-
return {
|
|
4670
|
-
unsubscribe: () => this.unregisterStream(key),
|
|
4671
|
-
wasReused: false
|
|
4672
|
-
};
|
|
4602
|
+
if (sim.length !== next.length || json(sim) !== json(next)) {
|
|
4603
|
+
return { kind: "snapshot", result: next };
|
|
4673
4604
|
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
if (
|
|
4680
|
-
const
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
if (current && current.refCount <= 0) {
|
|
4684
|
-
current.unsubscribe();
|
|
4685
|
-
this.activeStreams.delete(key);
|
|
4686
|
-
}
|
|
4687
|
-
}, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
|
|
4688
|
-
this.pendingUnsubscribes.set(key, timeout);
|
|
4689
|
-
}
|
|
4690
|
-
}
|
|
4691
|
-
subscribeView(viewName, eventWire, scope) {
|
|
4692
|
-
const key = this.storeKey(viewName, scope);
|
|
4693
|
-
const { unsubscribe } = this.registerStream(key, () => {
|
|
4694
|
-
const store = this.stores.get(key) ?? new StreamingStore;
|
|
4695
|
-
this.stores.set(key, store);
|
|
4696
|
-
eventWire.subscribeView(viewName, scope ?? DEFAULT_SCOPE, {
|
|
4697
|
-
onSnapshot: (items) => store.setAll(items),
|
|
4698
|
-
onChanges: (changes) => store.applyChanges(changes)
|
|
4699
|
-
});
|
|
4700
|
-
return {
|
|
4701
|
-
unsubscribe: () => eventWire.unsubscribeView(viewName, scope ?? DEFAULT_SCOPE)
|
|
4702
|
-
};
|
|
4703
|
-
});
|
|
4704
|
-
return unsubscribe;
|
|
4705
|
-
}
|
|
4706
|
-
invalidateScope(scope) {
|
|
4707
|
-
const prefix = `${scope}:`;
|
|
4708
|
-
for (const [key, timeout] of this.pendingUnsubscribes) {
|
|
4709
|
-
if (!key.startsWith(prefix))
|
|
4710
|
-
continue;
|
|
4711
|
-
clearTimeout(timeout);
|
|
4712
|
-
this.pendingUnsubscribes.delete(key);
|
|
4713
|
-
}
|
|
4714
|
-
for (const [key, stream] of this.activeStreams) {
|
|
4715
|
-
if (!key.startsWith(prefix))
|
|
4716
|
-
continue;
|
|
4717
|
-
try {
|
|
4718
|
-
stream.unsubscribe();
|
|
4719
|
-
} catch {}
|
|
4720
|
-
this.activeStreams.delete(key);
|
|
4721
|
-
}
|
|
4722
|
-
for (const [key, store] of this.stores) {
|
|
4723
|
-
if (!key.startsWith(prefix))
|
|
4724
|
-
continue;
|
|
4725
|
-
store.clear();
|
|
4726
|
-
}
|
|
4727
|
-
}
|
|
4728
|
-
async applyEvent(event3) {
|
|
4729
|
-
for (const view3 of this.views) {
|
|
4730
|
-
const handlers = view3.getHandlers();
|
|
4731
|
-
const handler = handlers[event3.type];
|
|
4732
|
-
if (!handler)
|
|
4733
|
-
continue;
|
|
4734
|
-
const suffix = `:${view3.name}`;
|
|
4735
|
-
for (const [key, store] of this.stores) {
|
|
4736
|
-
if (!key.endsWith(suffix))
|
|
4737
|
-
continue;
|
|
4738
|
-
const ctx = {
|
|
4739
|
-
set: async (id3, data) => {
|
|
4740
|
-
store.set(String(id3), { _id: String(id3), ...data });
|
|
4741
|
-
},
|
|
4742
|
-
modify: async (id3, data) => {
|
|
4743
|
-
store.modify(String(id3), data);
|
|
4744
|
-
},
|
|
4745
|
-
remove: async (id3) => {
|
|
4746
|
-
store.remove(String(id3));
|
|
4747
|
-
},
|
|
4748
|
-
find: async (options) => {
|
|
4749
|
-
return store.find(options);
|
|
4750
|
-
},
|
|
4751
|
-
findOne: async (where) => {
|
|
4752
|
-
return store.findOne(where);
|
|
4753
|
-
},
|
|
4754
|
-
$auth: {}
|
|
4755
|
-
};
|
|
4756
|
-
await handler(ctx, event3);
|
|
4757
|
-
}
|
|
4605
|
+
return { kind: "changes", changes };
|
|
4606
|
+
}
|
|
4607
|
+
function applyQueryChanges(result, changes) {
|
|
4608
|
+
const next = [...result];
|
|
4609
|
+
for (const change of changes) {
|
|
4610
|
+
if (change.type === "delete") {
|
|
4611
|
+
const idx = next.findIndex((it) => it._id === change.id);
|
|
4612
|
+
if (idx !== -1)
|
|
4613
|
+
next.splice(idx, 1);
|
|
4758
4614
|
}
|
|
4759
4615
|
}
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
clearTimeout(timeout);
|
|
4767
|
-
}
|
|
4768
|
-
this.pendingUnsubscribes.clear();
|
|
4769
|
-
for (const store of this.stores.values()) {
|
|
4770
|
-
store.clear();
|
|
4616
|
+
for (const change of changes) {
|
|
4617
|
+
if (change.type === "set") {
|
|
4618
|
+
const idx = next.findIndex((it) => it._id === change.id);
|
|
4619
|
+
if (idx !== -1)
|
|
4620
|
+
next.splice(idx, 1);
|
|
4621
|
+
next.splice(change.index, 0, change.item);
|
|
4771
4622
|
}
|
|
4772
4623
|
}
|
|
4624
|
+
return next;
|
|
4773
4625
|
}
|
|
4774
4626
|
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4627
|
+
// src/model/live-query/live-query-subscription.ts
|
|
4628
|
+
class LiveQuery {
|
|
4629
|
+
model;
|
|
4630
|
+
descriptor;
|
|
4631
|
+
scope;
|
|
4632
|
+
rawToken;
|
|
4633
|
+
onUpdate;
|
|
4634
|
+
observable = null;
|
|
4635
|
+
adapters = null;
|
|
4636
|
+
lastResult;
|
|
4637
|
+
scheduled = false;
|
|
4638
|
+
running = false;
|
|
4639
|
+
rerunRequested = false;
|
|
4640
|
+
stopped = false;
|
|
4641
|
+
constructor(model, descriptor, scope, rawToken, onUpdate) {
|
|
4642
|
+
this.model = model;
|
|
4643
|
+
this.descriptor = descriptor;
|
|
4644
|
+
this.scope = scope;
|
|
4645
|
+
this.rawToken = rawToken;
|
|
4646
|
+
this.onUpdate = onUpdate;
|
|
4647
|
+
}
|
|
4648
|
+
async start() {
|
|
4649
|
+
const scoped = new ScopedModel(this.model, this.scope);
|
|
4650
|
+
if (this.rawToken)
|
|
4651
|
+
scoped.setToken(this.rawToken);
|
|
4652
|
+
const baseAdapters = scoped.getAdapters();
|
|
4653
|
+
if (!baseAdapters.dataStorage) {
|
|
4654
|
+
throw new Error("LiveQuery requires a dataStorage adapter (server-side)");
|
|
4655
|
+
}
|
|
4656
|
+
this.observable = new ObservableDataStorage(baseAdapters.dataStorage, () => this.schedule());
|
|
4657
|
+
this.adapters = {
|
|
4658
|
+
...baseAdapters,
|
|
4659
|
+
dataStorage: this.observable
|
|
4660
|
+
};
|
|
4661
|
+
this.lastResult = await executeDescriptor(this.descriptor, this.model.context, this.adapters, "queryContext", { fromWire: true });
|
|
4662
|
+
return this.lastResult;
|
|
4781
4663
|
}
|
|
4782
|
-
|
|
4783
|
-
this.
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4664
|
+
flush() {
|
|
4665
|
+
this.schedule();
|
|
4666
|
+
}
|
|
4667
|
+
stop() {
|
|
4668
|
+
this.stopped = true;
|
|
4669
|
+
this.observable?.clear();
|
|
4670
|
+
}
|
|
4671
|
+
schedule() {
|
|
4672
|
+
if (this.stopped)
|
|
4673
|
+
return;
|
|
4674
|
+
if (this.running) {
|
|
4675
|
+
this.rerunRequested = true;
|
|
4676
|
+
return;
|
|
4787
4677
|
}
|
|
4788
|
-
this.
|
|
4678
|
+
if (this.scheduled)
|
|
4679
|
+
return;
|
|
4680
|
+
this.scheduled = true;
|
|
4681
|
+
queueMicrotask(() => {
|
|
4682
|
+
this.scheduled = false;
|
|
4683
|
+
this.run();
|
|
4684
|
+
});
|
|
4789
4685
|
}
|
|
4790
|
-
|
|
4791
|
-
if (
|
|
4686
|
+
async run() {
|
|
4687
|
+
if (this.stopped)
|
|
4792
4688
|
return;
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4689
|
+
this.running = true;
|
|
4690
|
+
try {
|
|
4691
|
+
const next = await executeDescriptor(this.descriptor, this.model.context, this.adapters, "queryContext", { fromWire: true });
|
|
4692
|
+
if (this.stopped)
|
|
4693
|
+
return;
|
|
4694
|
+
const diff = diffResults(this.lastResult, next);
|
|
4695
|
+
this.lastResult = next;
|
|
4696
|
+
if (diff.kind === "changes") {
|
|
4697
|
+
this.onUpdate({ type: "changes", changes: diff.changes });
|
|
4698
|
+
} else if (diff.kind === "snapshot") {
|
|
4699
|
+
this.onUpdate({ type: "snapshot", result: diff.result });
|
|
4700
|
+
}
|
|
4701
|
+
} catch (err) {
|
|
4702
|
+
console.error(`[Arc] LiveQuery re-execute error:`, err);
|
|
4703
|
+
} finally {
|
|
4704
|
+
this.running = false;
|
|
4705
|
+
if (this.rerunRequested) {
|
|
4706
|
+
this.rerunRequested = false;
|
|
4707
|
+
this.schedule();
|
|
4798
4708
|
}
|
|
4799
4709
|
}
|
|
4800
|
-
this.notifyListeners(events);
|
|
4801
|
-
}
|
|
4802
|
-
set(id3, item) {
|
|
4803
|
-
this.data.set(id3, item);
|
|
4804
|
-
this.notifyListeners([{ type: "set", id: id3, item }]);
|
|
4805
4710
|
}
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4711
|
+
}
|
|
4712
|
+
// src/streaming/streaming-query-cache.ts
|
|
4713
|
+
class StreamingQueryCache {
|
|
4714
|
+
entries = new Map;
|
|
4715
|
+
static UNSUBSCRIBE_DELAY_MS = 5000;
|
|
4716
|
+
entryKey(descriptor, scope) {
|
|
4717
|
+
return `${scope ?? "default"}:${murmurHash(JSON.stringify(descriptor))}`;
|
|
4718
|
+
}
|
|
4719
|
+
subscribe(descriptor, scope, eventWire, onChange) {
|
|
4720
|
+
const key = this.entryKey(descriptor, scope);
|
|
4721
|
+
let entry = this.entries.get(key);
|
|
4722
|
+
if (entry) {
|
|
4723
|
+
if (entry.pendingUnsub) {
|
|
4724
|
+
clearTimeout(entry.pendingUnsub);
|
|
4725
|
+
entry.pendingUnsub = undefined;
|
|
4726
|
+
}
|
|
4727
|
+
entry.refCount++;
|
|
4728
|
+
} else {
|
|
4729
|
+
const newEntry = {
|
|
4730
|
+
result: undefined,
|
|
4731
|
+
hasResult: false,
|
|
4732
|
+
listeners: new Set,
|
|
4733
|
+
refCount: 1,
|
|
4734
|
+
subscriptionId: ""
|
|
4735
|
+
};
|
|
4736
|
+
newEntry.subscriptionId = eventWire.subscribeQuery(descriptor, scope, {
|
|
4737
|
+
onSnapshot: (result) => {
|
|
4738
|
+
newEntry.result = result;
|
|
4739
|
+
newEntry.hasResult = true;
|
|
4740
|
+
this.notify(newEntry);
|
|
4741
|
+
},
|
|
4742
|
+
onChanges: (changes) => {
|
|
4743
|
+
if (!newEntry.hasResult || !Array.isArray(newEntry.result)) {
|
|
4744
|
+
return;
|
|
4745
|
+
}
|
|
4746
|
+
newEntry.result = applyQueryChanges(newEntry.result, changes);
|
|
4747
|
+
this.notify(newEntry);
|
|
4748
|
+
}
|
|
4749
|
+
});
|
|
4750
|
+
this.entries.set(key, newEntry);
|
|
4751
|
+
entry = newEntry;
|
|
4812
4752
|
}
|
|
4753
|
+
const subscribed = entry;
|
|
4754
|
+
subscribed.listeners.add(onChange);
|
|
4755
|
+
let active = true;
|
|
4756
|
+
return {
|
|
4757
|
+
read: () => ({
|
|
4758
|
+
result: subscribed.result,
|
|
4759
|
+
loading: !subscribed.hasResult
|
|
4760
|
+
}),
|
|
4761
|
+
unsubscribe: () => {
|
|
4762
|
+
if (!active)
|
|
4763
|
+
return;
|
|
4764
|
+
active = false;
|
|
4765
|
+
subscribed.listeners.delete(onChange);
|
|
4766
|
+
subscribed.refCount--;
|
|
4767
|
+
if (subscribed.refCount > 0)
|
|
4768
|
+
return;
|
|
4769
|
+
subscribed.pendingUnsub = setTimeout(() => {
|
|
4770
|
+
subscribed.pendingUnsub = undefined;
|
|
4771
|
+
if (subscribed.refCount > 0)
|
|
4772
|
+
return;
|
|
4773
|
+
eventWire.unsubscribeQuery(subscribed.subscriptionId);
|
|
4774
|
+
this.entries.delete(key);
|
|
4775
|
+
}, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
|
|
4776
|
+
}
|
|
4777
|
+
};
|
|
4813
4778
|
}
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4779
|
+
invalidateScope(scope, eventWire) {
|
|
4780
|
+
const prefix = `${scope}:`;
|
|
4781
|
+
for (const [key, entry] of this.entries) {
|
|
4782
|
+
if (!key.startsWith(prefix))
|
|
4783
|
+
continue;
|
|
4784
|
+
if (entry.pendingUnsub)
|
|
4785
|
+
clearTimeout(entry.pendingUnsub);
|
|
4786
|
+
eventWire?.unsubscribeQuery(entry.subscriptionId);
|
|
4787
|
+
this.entries.delete(key);
|
|
4788
|
+
entry.result = undefined;
|
|
4789
|
+
entry.hasResult = false;
|
|
4790
|
+
this.notify(entry);
|
|
4817
4791
|
}
|
|
4818
4792
|
}
|
|
4819
|
-
clear() {
|
|
4820
|
-
this.
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
find(options = {}) {
|
|
4825
|
-
let results = Array.from(this.data.values());
|
|
4826
|
-
if (options.where) {
|
|
4827
|
-
results = results.filter((item) => checkItemMatchesWhere(item, options.where));
|
|
4793
|
+
clear(eventWire) {
|
|
4794
|
+
for (const entry of this.entries.values()) {
|
|
4795
|
+
if (entry.pendingUnsub)
|
|
4796
|
+
clearTimeout(entry.pendingUnsub);
|
|
4797
|
+
eventWire?.unsubscribeQuery(entry.subscriptionId);
|
|
4828
4798
|
}
|
|
4829
|
-
|
|
4830
|
-
}
|
|
4831
|
-
findOne(where) {
|
|
4832
|
-
const results = this.find({ where });
|
|
4833
|
-
return results[0];
|
|
4834
|
-
}
|
|
4835
|
-
subscribe(listener4) {
|
|
4836
|
-
this.listeners.add(listener4);
|
|
4837
|
-
return () => {
|
|
4838
|
-
this.listeners.delete(listener4);
|
|
4839
|
-
};
|
|
4799
|
+
this.entries.clear();
|
|
4840
4800
|
}
|
|
4841
|
-
|
|
4842
|
-
for (const listener4 of
|
|
4843
|
-
|
|
4801
|
+
notify(entry) {
|
|
4802
|
+
for (const listener4 of entry.listeners) {
|
|
4803
|
+
try {
|
|
4804
|
+
listener4();
|
|
4805
|
+
} catch (err) {
|
|
4806
|
+
console.error(`[Arc] Query cache listener error:`, err);
|
|
4807
|
+
}
|
|
4844
4808
|
}
|
|
4845
4809
|
}
|
|
4846
4810
|
}
|
|
4847
4811
|
// src/streaming/streaming-event-publisher.ts
|
|
4848
4812
|
class StreamingEventPublisher {
|
|
4849
|
-
cache;
|
|
4850
4813
|
eventWire;
|
|
4851
4814
|
views = [];
|
|
4852
4815
|
subscribers = new Map;
|
|
4853
|
-
constructor(
|
|
4854
|
-
this.cache = cache;
|
|
4816
|
+
constructor(eventWire) {
|
|
4855
4817
|
this.eventWire = eventWire;
|
|
4856
4818
|
}
|
|
4857
4819
|
registerViews(views) {
|
|
4858
4820
|
this.views = views;
|
|
4859
|
-
this.cache.registerViews(views);
|
|
4860
4821
|
}
|
|
4861
4822
|
async publish(event3) {
|
|
4862
|
-
await this.cache.applyEvent(event3);
|
|
4863
4823
|
await this.notifySubscribers(event3);
|
|
4864
4824
|
this.eventWire.syncEvents([
|
|
4865
4825
|
{
|
|
@@ -5311,6 +5271,7 @@ export {
|
|
|
5311
5271
|
extractDatabaseAgnosticSchema,
|
|
5312
5272
|
executeDescriptor,
|
|
5313
5273
|
event,
|
|
5274
|
+
diffResults,
|
|
5314
5275
|
defaultFunctionData,
|
|
5315
5276
|
deepMerge,
|
|
5316
5277
|
date,
|
|
@@ -5327,6 +5288,7 @@ export {
|
|
|
5327
5288
|
array,
|
|
5328
5289
|
arcFunctionWithCtx,
|
|
5329
5290
|
arcFunction,
|
|
5291
|
+
applyQueryChanges,
|
|
5330
5292
|
applyOrderByAndLimit,
|
|
5331
5293
|
any,
|
|
5332
5294
|
aggregate,
|
|
@@ -5347,6 +5309,7 @@ export {
|
|
|
5347
5309
|
MasterStoreState,
|
|
5348
5310
|
MasterDataStorage,
|
|
5349
5311
|
LocalEventPublisher,
|
|
5312
|
+
LiveQuery,
|
|
5350
5313
|
ForkedStoreState,
|
|
5351
5314
|
ForkedDataStorage,
|
|
5352
5315
|
EventWire,
|