@absolutejs/sync 1.20.0 → 1.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/devtools.d.ts +8 -0
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +131 -72
- package/dist/engine/index.js.map +3 -3
- package/dist/engine/syncEngine.d.ts +36 -0
- package/dist/index.js +130 -72
- package/dist/index.js.map +3 -3
- package/dist/testing.js +130 -72
- package/dist/testing.js.map +3 -3
- package/package.json +1 -1
|
@@ -82,6 +82,14 @@ export type EngineMetrics = {
|
|
|
82
82
|
total: number;
|
|
83
83
|
/** Per-collection breakdown — the values sum to `total`. */
|
|
84
84
|
byCollection: Record<string, number>;
|
|
85
|
+
/**
|
|
86
|
+
* Per-tenant tally — populated only when
|
|
87
|
+
* `SyncEngineOptions.subscriptionLimit` is set; otherwise `{}`. The
|
|
88
|
+
* key is whatever `subscriptionLimit.key(ctx, args)` returns; the
|
|
89
|
+
* value is the count of active subscriptions for that key. Added
|
|
90
|
+
* in 1.20.1.
|
|
91
|
+
*/
|
|
92
|
+
byTenant: Record<string, number>;
|
|
85
93
|
};
|
|
86
94
|
reactiveCache: {
|
|
87
95
|
entries: number;
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export type { MutationActions, MutationDefinition, MutationHandler, TableWriter,
|
|
|
47
47
|
export type { BridgeFetchConfig, BridgeFetchEndpoint, BridgeFetchResponse, HandlerMetricsHook, HandlerMetricsRecord, SandboxConfig } from './sandbox';
|
|
48
48
|
export { exponentialBackoff, isSerializationFailure, RetriesExhaustedError } from './retry';
|
|
49
49
|
export type { ExponentialBackoffOptions, RetryPolicy } from './retry';
|
|
50
|
-
export { CdcConsumerSlowError, createSyncEngine, MissedChangesError, MutationQueueOverflowError, SchemaError, UnauthorizedError } from './syncEngine';
|
|
50
|
+
export { CdcConsumerSlowError, createSyncEngine, MissedChangesError, MutationQueueOverflowError, SchemaError, SubscriptionLimitError, UnauthorizedError } from './syncEngine';
|
|
51
51
|
export type { ChangeLogSnapshot, CrdtFields, LoggedChange, StreamChangesOptions, SubscribeArgs, Subscription, SyncEngine, SyncEngineOptions } from './syncEngine';
|
|
52
52
|
export { syncCdc } from './cdc';
|
|
53
53
|
export type { SyncCdcOptions } from './cdc';
|
package/dist/engine/index.js
CHANGED
|
@@ -1419,6 +1419,19 @@ class MutationQueueOverflowError extends Error {
|
|
|
1419
1419
|
this.queueLimit = queueLimit;
|
|
1420
1420
|
}
|
|
1421
1421
|
}
|
|
1422
|
+
|
|
1423
|
+
class SubscriptionLimitError extends Error {
|
|
1424
|
+
tenantKey;
|
|
1425
|
+
limit;
|
|
1426
|
+
active;
|
|
1427
|
+
constructor(tenantKey, limit, active) {
|
|
1428
|
+
super(`Tenant "${tenantKey}" is at the subscription cap ` + `(${active}/${limit}). Close an existing subscription before opening another.`);
|
|
1429
|
+
this.name = "SubscriptionLimitError";
|
|
1430
|
+
this.tenantKey = tenantKey;
|
|
1431
|
+
this.limit = limit;
|
|
1432
|
+
this.active = active;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1422
1435
|
var defaultKey = (row) => row.id;
|
|
1423
1436
|
var shallowEqual4 = (a, b) => {
|
|
1424
1437
|
if (a === b) {
|
|
@@ -1569,6 +1582,31 @@ var createSyncEngine = (options = {}) => {
|
|
|
1569
1582
|
if (next !== undefined)
|
|
1570
1583
|
next();
|
|
1571
1584
|
};
|
|
1585
|
+
const subscriptionsByTenant = new Map;
|
|
1586
|
+
const acquireSubscriptionSlot = (ctx, args) => {
|
|
1587
|
+
const cap = options.subscriptionLimit;
|
|
1588
|
+
if (cap === undefined)
|
|
1589
|
+
return;
|
|
1590
|
+
const tenantKey = cap.key(ctx, args);
|
|
1591
|
+
if (tenantKey === undefined)
|
|
1592
|
+
return;
|
|
1593
|
+
const active2 = subscriptionsByTenant.get(tenantKey) ?? 0;
|
|
1594
|
+
if (active2 >= cap.max) {
|
|
1595
|
+
throw new SubscriptionLimitError(tenantKey, cap.max, active2);
|
|
1596
|
+
}
|
|
1597
|
+
subscriptionsByTenant.set(tenantKey, active2 + 1);
|
|
1598
|
+
return tenantKey;
|
|
1599
|
+
};
|
|
1600
|
+
const releaseSubscriptionSlot = (tenantKey) => {
|
|
1601
|
+
if (tenantKey === undefined)
|
|
1602
|
+
return;
|
|
1603
|
+
const active2 = subscriptionsByTenant.get(tenantKey);
|
|
1604
|
+
if (active2 === undefined || active2 <= 1) {
|
|
1605
|
+
subscriptionsByTenant.delete(tenantKey);
|
|
1606
|
+
} else {
|
|
1607
|
+
subscriptionsByTenant.set(tenantKey, active2 - 1);
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1572
1610
|
const reactiveCacheMax = options.reactiveCache?.max ?? 256;
|
|
1573
1611
|
const reactiveCacheTtlMs = options.reactiveCache?.ttlMs ?? 60000;
|
|
1574
1612
|
const cachedReruns = new Map;
|
|
@@ -2406,84 +2444,103 @@ var createSyncEngine = (options = {}) => {
|
|
|
2406
2444
|
if (registered === undefined) {
|
|
2407
2445
|
throw new Error(`Unknown collection "${collection}"`);
|
|
2408
2446
|
}
|
|
2409
|
-
const
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
const
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2447
|
+
const tenantSlot = acquireSubscriptionSlot(ctx, { collection });
|
|
2448
|
+
let slotHandedOff = false;
|
|
2449
|
+
try {
|
|
2450
|
+
const typedOnDiff = onDiff;
|
|
2451
|
+
const subscribeSet = subsFor(collection);
|
|
2452
|
+
const wrapReturn = (sub) => {
|
|
2453
|
+
checkAborted(signal);
|
|
2454
|
+
const innerUnsubscribe = sub.unsubscribe;
|
|
2455
|
+
let released = false;
|
|
2456
|
+
const wrappedUnsubscribe = () => {
|
|
2457
|
+
if (released)
|
|
2458
|
+
return;
|
|
2459
|
+
released = true;
|
|
2460
|
+
releaseSubscriptionSlot(tenantSlot);
|
|
2461
|
+
innerUnsubscribe();
|
|
2462
|
+
};
|
|
2463
|
+
const wrapped = { ...sub, unsubscribe: wrappedUnsubscribe };
|
|
2464
|
+
linkAbortToUnsubscribe(signal, wrappedUnsubscribe);
|
|
2465
|
+
slotHandedOff = true;
|
|
2466
|
+
return wrapped;
|
|
2467
|
+
};
|
|
2468
|
+
const registeredKind = registered.kind;
|
|
2469
|
+
if (registeredKind === "join") {
|
|
2470
|
+
const joined = await subscribeJoin(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2471
|
+
return wrapReturn(joined);
|
|
2472
|
+
}
|
|
2473
|
+
if (registeredKind === "graph") {
|
|
2474
|
+
const graphed = await subscribeGraph(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2475
|
+
return wrapReturn(graphed);
|
|
2476
|
+
}
|
|
2477
|
+
if (registeredKind === "reactive") {
|
|
2478
|
+
const reactived = await subscribeReactive(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2479
|
+
return wrapReturn(reactived);
|
|
2480
|
+
}
|
|
2481
|
+
if (registeredKind === "search") {
|
|
2482
|
+
const searched = await subscribeSearch(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2483
|
+
return wrapReturn(searched);
|
|
2484
|
+
}
|
|
2485
|
+
const definition = registered;
|
|
2486
|
+
if (definition.authorize !== undefined) {
|
|
2487
|
+
const allowed = await definition.authorize(params, ctx);
|
|
2488
|
+
if (!allowed) {
|
|
2489
|
+
throw new UnauthorizedError(`subscribe to collection "${collection}"`);
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
const key = definition.key ?? defaultKey;
|
|
2493
|
+
const match = definition.match;
|
|
2494
|
+
const tables = definition.tables ?? [collection];
|
|
2495
|
+
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
2496
|
+
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
2497
|
+
const rehydrate = async () => {
|
|
2498
|
+
const raw = [...await definition.hydrate(params, ctx)];
|
|
2499
|
+
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
2500
|
+
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
2501
|
+
};
|
|
2502
|
+
const incremental = match !== undefined && tables.length === 1;
|
|
2503
|
+
const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
|
|
2504
|
+
const view = createMaterializedView({
|
|
2505
|
+
key,
|
|
2506
|
+
match: boundMatch
|
|
2507
|
+
});
|
|
2508
|
+
const resuming = since !== undefined && canResume(since, incremental);
|
|
2509
|
+
view.hydrate([...await rehydrate()]);
|
|
2510
|
+
const atVersion = version;
|
|
2511
|
+
const subscription = {
|
|
2512
|
+
kind: "view",
|
|
2513
|
+
collection,
|
|
2514
|
+
view,
|
|
2515
|
+
incremental,
|
|
2516
|
+
rehydrate,
|
|
2517
|
+
key,
|
|
2518
|
+
onDiff: typedOnDiff
|
|
2519
|
+
};
|
|
2520
|
+
subscribeSet.add(subscription);
|
|
2521
|
+
const unsubscribe = () => {
|
|
2522
|
+
subscribeSet.delete(subscription);
|
|
2523
|
+
};
|
|
2524
|
+
if (resuming) {
|
|
2525
|
+
return wrapReturn({
|
|
2526
|
+
initial: [],
|
|
2527
|
+
catchup: buildCatchup(since, tables, key, boundMatch),
|
|
2528
|
+
cursor: currentCursor(),
|
|
2529
|
+
version: atVersion,
|
|
2530
|
+
unsubscribe
|
|
2531
|
+
});
|
|
2438
2532
|
}
|
|
2439
|
-
}
|
|
2440
|
-
const key = definition.key ?? defaultKey;
|
|
2441
|
-
const match = definition.match;
|
|
2442
|
-
const tables = definition.tables ?? [collection];
|
|
2443
|
-
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
2444
|
-
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
2445
|
-
const rehydrate = async () => {
|
|
2446
|
-
const raw = [...await definition.hydrate(params, ctx)];
|
|
2447
|
-
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
2448
|
-
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
2449
|
-
};
|
|
2450
|
-
const incremental = match !== undefined && tables.length === 1;
|
|
2451
|
-
const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
|
|
2452
|
-
const view = createMaterializedView({
|
|
2453
|
-
key,
|
|
2454
|
-
match: boundMatch
|
|
2455
|
-
});
|
|
2456
|
-
const resuming = since !== undefined && canResume(since, incremental);
|
|
2457
|
-
view.hydrate([...await rehydrate()]);
|
|
2458
|
-
const atVersion = version;
|
|
2459
|
-
const subscription = {
|
|
2460
|
-
kind: "view",
|
|
2461
|
-
collection,
|
|
2462
|
-
view,
|
|
2463
|
-
incremental,
|
|
2464
|
-
rehydrate,
|
|
2465
|
-
key,
|
|
2466
|
-
onDiff: typedOnDiff
|
|
2467
|
-
};
|
|
2468
|
-
subscribeSet.add(subscription);
|
|
2469
|
-
const unsubscribe = () => {
|
|
2470
|
-
subscribeSet.delete(subscription);
|
|
2471
|
-
};
|
|
2472
|
-
if (resuming) {
|
|
2473
2533
|
return wrapReturn({
|
|
2474
|
-
initial:
|
|
2475
|
-
catchup: buildCatchup(since, tables, key, boundMatch),
|
|
2534
|
+
initial: view.rows(),
|
|
2476
2535
|
cursor: currentCursor(),
|
|
2477
2536
|
version: atVersion,
|
|
2478
2537
|
unsubscribe
|
|
2479
2538
|
});
|
|
2539
|
+
} catch (error) {
|
|
2540
|
+
if (!slotHandedOff)
|
|
2541
|
+
releaseSubscriptionSlot(tenantSlot);
|
|
2542
|
+
throw error;
|
|
2480
2543
|
}
|
|
2481
|
-
return wrapReturn({
|
|
2482
|
-
initial: view.rows(),
|
|
2483
|
-
cursor: currentCursor(),
|
|
2484
|
-
version: atVersion,
|
|
2485
|
-
unsubscribe
|
|
2486
|
-
});
|
|
2487
2544
|
},
|
|
2488
2545
|
hydrate: async (collection, params, ctx, options2) => {
|
|
2489
2546
|
const signal = options2?.signal;
|
|
@@ -2948,6 +3005,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2948
3005
|
},
|
|
2949
3006
|
subscriptions: {
|
|
2950
3007
|
byCollection,
|
|
3008
|
+
byTenant: Object.fromEntries(subscriptionsByTenant),
|
|
2951
3009
|
total: totalSubscriptions
|
|
2952
3010
|
},
|
|
2953
3011
|
uptimeMs: now - engineStartedAt,
|
|
@@ -3476,6 +3534,7 @@ export {
|
|
|
3476
3534
|
chain,
|
|
3477
3535
|
aggregateOp,
|
|
3478
3536
|
UnauthorizedError,
|
|
3537
|
+
SubscriptionLimitError,
|
|
3479
3538
|
SchemaError,
|
|
3480
3539
|
SEARCH_SCORE_FIELD,
|
|
3481
3540
|
RetriesExhaustedError,
|
|
@@ -3486,5 +3545,5 @@ export {
|
|
|
3486
3545
|
CdcConsumerSlowError
|
|
3487
3546
|
};
|
|
3488
3547
|
|
|
3489
|
-
//# debugId=
|
|
3548
|
+
//# debugId=251EC576E67A0CAF64756E2164756E21
|
|
3490
3549
|
//# sourceMappingURL=index.js.map
|