@absolutejs/sync 1.19.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 +13 -0
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +181 -74
- package/dist/engine/index.js.map +3 -3
- package/dist/engine/syncEngine.d.ts +76 -0
- package/dist/index.js +179 -74
- package/dist/index.js.map +3 -3
- package/dist/testing.js +179 -74
- package/dist/testing.js.map +3 -3
- package/package.json +1 -1
|
@@ -414,6 +414,29 @@ export declare class CdcConsumerSlowError extends Error {
|
|
|
414
414
|
readonly lastDeliveredVersion: number;
|
|
415
415
|
constructor(maxBuffer: number, lastDeliveredVersion: number);
|
|
416
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Thrown by `runMutation` / `runMutations` when `mutationConcurrency` is
|
|
419
|
+
* saturated AND the waiting queue is already at `mutationQueueLimit`. The
|
|
420
|
+
* caller sees this immediately (no queue time) so the host can shed load
|
|
421
|
+
* with a clean 429 instead of letting the queue grow unboundedly. Added
|
|
422
|
+
* in 1.20.0.
|
|
423
|
+
*/
|
|
424
|
+
export declare class MutationQueueOverflowError extends Error {
|
|
425
|
+
readonly queueLimit: number;
|
|
426
|
+
constructor(queueLimit: number);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Thrown by `engine.subscribe` when the calling tenant's active-subscription
|
|
430
|
+
* count is already at the configured `subscriptionLimit.max`. The caller sees
|
|
431
|
+
* this immediately — BEFORE authorize, hydrate, or any subscription state
|
|
432
|
+
* allocation — so a rejected call leaks nothing. Added in 1.20.1.
|
|
433
|
+
*/
|
|
434
|
+
export declare class SubscriptionLimitError extends Error {
|
|
435
|
+
readonly tenantKey: string;
|
|
436
|
+
readonly limit: number;
|
|
437
|
+
readonly active: number;
|
|
438
|
+
constructor(tenantKey: string, limit: number, active: number);
|
|
439
|
+
}
|
|
417
440
|
/**
|
|
418
441
|
* Serializable snapshot of an engine's change log + monotonic version, returned
|
|
419
442
|
* by {@link SyncEngine.exportChangeLog} and consumed by
|
|
@@ -557,6 +580,59 @@ export type SyncEngineOptions = {
|
|
|
557
580
|
* `createSyncEngine` returns. Added in 1.19.0.
|
|
558
581
|
*/
|
|
559
582
|
initialChangeLog?: ChangeLogSnapshot;
|
|
583
|
+
/**
|
|
584
|
+
* Maximum concurrent in-flight mutations (`runMutation` + `runMutations`).
|
|
585
|
+
* Calls beyond the limit wait in a FIFO queue and run as slots free up;
|
|
586
|
+
* `engine.metrics().mutations.queued` surfaces the queue depth.
|
|
587
|
+
*
|
|
588
|
+
* A single tenant flooding `runMutation` can otherwise drive unbounded
|
|
589
|
+
* memory growth (per-mutation `actions` buffers, retry timers, sandbox
|
|
590
|
+
* invocations queued against the isolate pool). Set this to a value
|
|
591
|
+
* appropriate for the host's tenant tier — e.g. `32` for a free tier,
|
|
592
|
+
* `256` for paid. Without this option the engine is unbounded
|
|
593
|
+
* (matching pre-1.20 behavior).
|
|
594
|
+
*
|
|
595
|
+
* Sandboxed mutations are gated by the same semaphore. If you need
|
|
596
|
+
* finer-grained control (sandbox-only throttling), see
|
|
597
|
+
* `@absolutejs/isolated-jsc`'s pool size — that's the lower layer.
|
|
598
|
+
*
|
|
599
|
+
* Added in 1.20.0.
|
|
600
|
+
*/
|
|
601
|
+
mutationConcurrency?: number;
|
|
602
|
+
/**
|
|
603
|
+
* Cap on the queue of waiting mutations once `mutationConcurrency` is
|
|
604
|
+
* saturated. Calls beyond this cap throw {@link MutationQueueOverflowError}
|
|
605
|
+
* immediately instead of queueing — the host can surface a clean 429 or
|
|
606
|
+
* apply a tenant-specific shed policy. Defaults to unbounded (queue
|
|
607
|
+
* never rejects). Only meaningful when `mutationConcurrency` is set.
|
|
608
|
+
*
|
|
609
|
+
* Added in 1.20.0.
|
|
610
|
+
*/
|
|
611
|
+
mutationQueueLimit?: number;
|
|
612
|
+
/**
|
|
613
|
+
* Per-tenant active-subscription cap. Symmetric to
|
|
614
|
+
* {@link SyncEngineOptions.mutationConcurrency} on the read side: a
|
|
615
|
+
* single tenant opening thousands of subscriptions would otherwise
|
|
616
|
+
* exhaust the engine's per-subscription bookkeeping
|
|
617
|
+
* (`active`/`tableIndex` Maps, the reactive cache, per-row diff
|
|
618
|
+
* computation cost).
|
|
619
|
+
*
|
|
620
|
+
* `key` derives a tenant identifier from `(ctx, args)`; returning
|
|
621
|
+
* `undefined` skips the cap for that call (e.g. internal/system
|
|
622
|
+
* subscriptions). When the active count for a key reaches `max`, the
|
|
623
|
+
* next `subscribe` throws {@link SubscriptionLimitError} BEFORE any
|
|
624
|
+
* authorize, hydrate, or state allocation — so a denied call leaks
|
|
625
|
+
* nothing.
|
|
626
|
+
*
|
|
627
|
+
* Active counts are surfaced through `engine.metrics().subscriptions.byTenant`
|
|
628
|
+
* for tier monitoring. Added in 1.20.1.
|
|
629
|
+
*/
|
|
630
|
+
subscriptionLimit?: {
|
|
631
|
+
max: number;
|
|
632
|
+
key: (ctx: unknown, args: {
|
|
633
|
+
collection: string;
|
|
634
|
+
}) => string | undefined;
|
|
635
|
+
};
|
|
560
636
|
};
|
|
561
637
|
/**
|
|
562
638
|
* The Tier 3 sync engine: a registry of collections plus the view syncer. It is
|
package/dist/index.js
CHANGED
|
@@ -1152,6 +1152,28 @@ class CdcConsumerSlowError extends Error {
|
|
|
1152
1152
|
this.lastDeliveredVersion = lastDeliveredVersion;
|
|
1153
1153
|
}
|
|
1154
1154
|
}
|
|
1155
|
+
|
|
1156
|
+
class MutationQueueOverflowError extends Error {
|
|
1157
|
+
queueLimit;
|
|
1158
|
+
constructor(queueLimit) {
|
|
1159
|
+
super(`Mutation queue overflowed (limit ${queueLimit}); the engine is at ` + `its mutationConcurrency cap and the waiting queue is full. ` + `Retry later or shed load at the gateway.`);
|
|
1160
|
+
this.name = "MutationQueueOverflowError";
|
|
1161
|
+
this.queueLimit = queueLimit;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
class SubscriptionLimitError extends Error {
|
|
1166
|
+
tenantKey;
|
|
1167
|
+
limit;
|
|
1168
|
+
active;
|
|
1169
|
+
constructor(tenantKey, limit, active) {
|
|
1170
|
+
super(`Tenant "${tenantKey}" is at the subscription cap ` + `(${active}/${limit}). Close an existing subscription before opening another.`);
|
|
1171
|
+
this.name = "SubscriptionLimitError";
|
|
1172
|
+
this.tenantKey = tenantKey;
|
|
1173
|
+
this.limit = limit;
|
|
1174
|
+
this.active = active;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1155
1177
|
var defaultKey = (row) => row.id;
|
|
1156
1178
|
var shallowEqual3 = (a, b) => {
|
|
1157
1179
|
if (a === b) {
|
|
@@ -1268,6 +1290,65 @@ var createSyncEngine = (options = {}) => {
|
|
|
1268
1290
|
let mutationsFailed = 0;
|
|
1269
1291
|
let mutationsRetried = 0;
|
|
1270
1292
|
let mutationsInFlight = 0;
|
|
1293
|
+
const mutationWaiters = [];
|
|
1294
|
+
let mutationsQueued = 0;
|
|
1295
|
+
const acquireMutationSlot = async () => {
|
|
1296
|
+
const limit = options.mutationConcurrency;
|
|
1297
|
+
if (limit === undefined) {
|
|
1298
|
+
mutationsInFlight += 1;
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
if (mutationsInFlight < limit && mutationWaiters.length === 0) {
|
|
1302
|
+
mutationsInFlight += 1;
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const queueLimit = options.mutationQueueLimit;
|
|
1306
|
+
if (queueLimit !== undefined && mutationsQueued >= queueLimit) {
|
|
1307
|
+
throw new MutationQueueOverflowError(queueLimit);
|
|
1308
|
+
}
|
|
1309
|
+
mutationsQueued += 1;
|
|
1310
|
+
try {
|
|
1311
|
+
await new Promise((resolve) => {
|
|
1312
|
+
mutationWaiters.push(resolve);
|
|
1313
|
+
});
|
|
1314
|
+
} finally {
|
|
1315
|
+
mutationsQueued -= 1;
|
|
1316
|
+
}
|
|
1317
|
+
mutationsInFlight += 1;
|
|
1318
|
+
};
|
|
1319
|
+
const releaseMutationSlot = () => {
|
|
1320
|
+
mutationsInFlight -= 1;
|
|
1321
|
+
if (options.mutationConcurrency === undefined)
|
|
1322
|
+
return;
|
|
1323
|
+
const next = mutationWaiters.shift();
|
|
1324
|
+
if (next !== undefined)
|
|
1325
|
+
next();
|
|
1326
|
+
};
|
|
1327
|
+
const subscriptionsByTenant = new Map;
|
|
1328
|
+
const acquireSubscriptionSlot = (ctx, args) => {
|
|
1329
|
+
const cap = options.subscriptionLimit;
|
|
1330
|
+
if (cap === undefined)
|
|
1331
|
+
return;
|
|
1332
|
+
const tenantKey = cap.key(ctx, args);
|
|
1333
|
+
if (tenantKey === undefined)
|
|
1334
|
+
return;
|
|
1335
|
+
const active2 = subscriptionsByTenant.get(tenantKey) ?? 0;
|
|
1336
|
+
if (active2 >= cap.max) {
|
|
1337
|
+
throw new SubscriptionLimitError(tenantKey, cap.max, active2);
|
|
1338
|
+
}
|
|
1339
|
+
subscriptionsByTenant.set(tenantKey, active2 + 1);
|
|
1340
|
+
return tenantKey;
|
|
1341
|
+
};
|
|
1342
|
+
const releaseSubscriptionSlot = (tenantKey) => {
|
|
1343
|
+
if (tenantKey === undefined)
|
|
1344
|
+
return;
|
|
1345
|
+
const active2 = subscriptionsByTenant.get(tenantKey);
|
|
1346
|
+
if (active2 === undefined || active2 <= 1) {
|
|
1347
|
+
subscriptionsByTenant.delete(tenantKey);
|
|
1348
|
+
} else {
|
|
1349
|
+
subscriptionsByTenant.set(tenantKey, active2 - 1);
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1271
1352
|
const reactiveCacheMax = options.reactiveCache?.max ?? 256;
|
|
1272
1353
|
const reactiveCacheTtlMs = options.reactiveCache?.ttlMs ?? 60000;
|
|
1273
1354
|
const cachedReruns = new Map;
|
|
@@ -2105,84 +2186,103 @@ var createSyncEngine = (options = {}) => {
|
|
|
2105
2186
|
if (registered === undefined) {
|
|
2106
2187
|
throw new Error(`Unknown collection "${collection}"`);
|
|
2107
2188
|
}
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
const
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2189
|
+
const tenantSlot = acquireSubscriptionSlot(ctx, { collection });
|
|
2190
|
+
let slotHandedOff = false;
|
|
2191
|
+
try {
|
|
2192
|
+
const typedOnDiff = onDiff;
|
|
2193
|
+
const subscribeSet = subsFor(collection);
|
|
2194
|
+
const wrapReturn = (sub) => {
|
|
2195
|
+
checkAborted(signal);
|
|
2196
|
+
const innerUnsubscribe = sub.unsubscribe;
|
|
2197
|
+
let released = false;
|
|
2198
|
+
const wrappedUnsubscribe = () => {
|
|
2199
|
+
if (released)
|
|
2200
|
+
return;
|
|
2201
|
+
released = true;
|
|
2202
|
+
releaseSubscriptionSlot(tenantSlot);
|
|
2203
|
+
innerUnsubscribe();
|
|
2204
|
+
};
|
|
2205
|
+
const wrapped = { ...sub, unsubscribe: wrappedUnsubscribe };
|
|
2206
|
+
linkAbortToUnsubscribe(signal, wrappedUnsubscribe);
|
|
2207
|
+
slotHandedOff = true;
|
|
2208
|
+
return wrapped;
|
|
2209
|
+
};
|
|
2210
|
+
const registeredKind = registered.kind;
|
|
2211
|
+
if (registeredKind === "join") {
|
|
2212
|
+
const joined = await subscribeJoin(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2213
|
+
return wrapReturn(joined);
|
|
2214
|
+
}
|
|
2215
|
+
if (registeredKind === "graph") {
|
|
2216
|
+
const graphed = await subscribeGraph(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2217
|
+
return wrapReturn(graphed);
|
|
2218
|
+
}
|
|
2219
|
+
if (registeredKind === "reactive") {
|
|
2220
|
+
const reactived = await subscribeReactive(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2221
|
+
return wrapReturn(reactived);
|
|
2222
|
+
}
|
|
2223
|
+
if (registeredKind === "search") {
|
|
2224
|
+
const searched = await subscribeSearch(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2225
|
+
return wrapReturn(searched);
|
|
2226
|
+
}
|
|
2227
|
+
const definition = registered;
|
|
2228
|
+
if (definition.authorize !== undefined) {
|
|
2229
|
+
const allowed = await definition.authorize(params, ctx);
|
|
2230
|
+
if (!allowed) {
|
|
2231
|
+
throw new UnauthorizedError(`subscribe to collection "${collection}"`);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
const key = definition.key ?? defaultKey;
|
|
2235
|
+
const match = definition.match;
|
|
2236
|
+
const tables = definition.tables ?? [collection];
|
|
2237
|
+
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
2238
|
+
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
2239
|
+
const rehydrate = async () => {
|
|
2240
|
+
const raw = [...await definition.hydrate(params, ctx)];
|
|
2241
|
+
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
2242
|
+
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
2243
|
+
};
|
|
2244
|
+
const incremental = match !== undefined && tables.length === 1;
|
|
2245
|
+
const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
|
|
2246
|
+
const view = createMaterializedView({
|
|
2247
|
+
key,
|
|
2248
|
+
match: boundMatch
|
|
2249
|
+
});
|
|
2250
|
+
const resuming = since !== undefined && canResume(since, incremental);
|
|
2251
|
+
view.hydrate([...await rehydrate()]);
|
|
2252
|
+
const atVersion = version;
|
|
2253
|
+
const subscription = {
|
|
2254
|
+
kind: "view",
|
|
2255
|
+
collection,
|
|
2256
|
+
view,
|
|
2257
|
+
incremental,
|
|
2258
|
+
rehydrate,
|
|
2259
|
+
key,
|
|
2260
|
+
onDiff: typedOnDiff
|
|
2261
|
+
};
|
|
2262
|
+
subscribeSet.add(subscription);
|
|
2263
|
+
const unsubscribe = () => {
|
|
2264
|
+
subscribeSet.delete(subscription);
|
|
2265
|
+
};
|
|
2266
|
+
if (resuming) {
|
|
2267
|
+
return wrapReturn({
|
|
2268
|
+
initial: [],
|
|
2269
|
+
catchup: buildCatchup(since, tables, key, boundMatch),
|
|
2270
|
+
cursor: currentCursor(),
|
|
2271
|
+
version: atVersion,
|
|
2272
|
+
unsubscribe
|
|
2273
|
+
});
|
|
2137
2274
|
}
|
|
2138
|
-
}
|
|
2139
|
-
const key = definition.key ?? defaultKey;
|
|
2140
|
-
const match = definition.match;
|
|
2141
|
-
const tables = definition.tables ?? [collection];
|
|
2142
|
-
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
2143
|
-
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
2144
|
-
const rehydrate = async () => {
|
|
2145
|
-
const raw = [...await definition.hydrate(params, ctx)];
|
|
2146
|
-
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
2147
|
-
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
2148
|
-
};
|
|
2149
|
-
const incremental = match !== undefined && tables.length === 1;
|
|
2150
|
-
const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
|
|
2151
|
-
const view = createMaterializedView({
|
|
2152
|
-
key,
|
|
2153
|
-
match: boundMatch
|
|
2154
|
-
});
|
|
2155
|
-
const resuming = since !== undefined && canResume(since, incremental);
|
|
2156
|
-
view.hydrate([...await rehydrate()]);
|
|
2157
|
-
const atVersion = version;
|
|
2158
|
-
const subscription = {
|
|
2159
|
-
kind: "view",
|
|
2160
|
-
collection,
|
|
2161
|
-
view,
|
|
2162
|
-
incremental,
|
|
2163
|
-
rehydrate,
|
|
2164
|
-
key,
|
|
2165
|
-
onDiff: typedOnDiff
|
|
2166
|
-
};
|
|
2167
|
-
subscribeSet.add(subscription);
|
|
2168
|
-
const unsubscribe = () => {
|
|
2169
|
-
subscribeSet.delete(subscription);
|
|
2170
|
-
};
|
|
2171
|
-
if (resuming) {
|
|
2172
2275
|
return wrapReturn({
|
|
2173
|
-
initial:
|
|
2174
|
-
catchup: buildCatchup(since, tables, key, boundMatch),
|
|
2276
|
+
initial: view.rows(),
|
|
2175
2277
|
cursor: currentCursor(),
|
|
2176
2278
|
version: atVersion,
|
|
2177
2279
|
unsubscribe
|
|
2178
2280
|
});
|
|
2281
|
+
} catch (error) {
|
|
2282
|
+
if (!slotHandedOff)
|
|
2283
|
+
releaseSubscriptionSlot(tenantSlot);
|
|
2284
|
+
throw error;
|
|
2179
2285
|
}
|
|
2180
|
-
return wrapReturn({
|
|
2181
|
-
initial: view.rows(),
|
|
2182
|
-
cursor: currentCursor(),
|
|
2183
|
-
version: atVersion,
|
|
2184
|
-
unsubscribe
|
|
2185
|
-
});
|
|
2186
2286
|
},
|
|
2187
2287
|
hydrate: async (collection, params, ctx, options2) => {
|
|
2188
2288
|
const signal = options2?.signal;
|
|
@@ -2295,6 +2395,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2295
2395
|
throw new UnauthorizedError(`run mutation "${name}"`);
|
|
2296
2396
|
}
|
|
2297
2397
|
}
|
|
2398
|
+
await acquireMutationSlot();
|
|
2298
2399
|
const sandboxRunner = sandboxRunners.get(name);
|
|
2299
2400
|
const invokeHandler = sandboxRunner !== undefined ? sandboxRunner : (a, c, actions) => Promise.resolve(mutation.handler(a, c, actions));
|
|
2300
2401
|
const runHandler = async (tx) => {
|
|
@@ -2310,7 +2411,6 @@ var createSyncEngine = (options = {}) => {
|
|
|
2310
2411
|
const startedAt = Date.now();
|
|
2311
2412
|
let lastError;
|
|
2312
2413
|
let attemptsMade = 0;
|
|
2313
|
-
mutationsInFlight += 1;
|
|
2314
2414
|
try {
|
|
2315
2415
|
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
2316
2416
|
attemptsMade = attempt;
|
|
@@ -2363,7 +2463,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2363
2463
|
}
|
|
2364
2464
|
throw lastError;
|
|
2365
2465
|
} finally {
|
|
2366
|
-
|
|
2466
|
+
releaseMutationSlot();
|
|
2367
2467
|
}
|
|
2368
2468
|
},
|
|
2369
2469
|
runMutations: async (specs, ctx) => {
|
|
@@ -2376,6 +2476,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2376
2476
|
}
|
|
2377
2477
|
return { args: spec.args, mutation, name: spec.name };
|
|
2378
2478
|
});
|
|
2479
|
+
await acquireMutationSlot();
|
|
2379
2480
|
const runBatch = async (tx) => {
|
|
2380
2481
|
const results = [];
|
|
2381
2482
|
const accumulated = [];
|
|
@@ -2413,6 +2514,8 @@ var createSyncEngine = (options = {}) => {
|
|
|
2413
2514
|
status: "error"
|
|
2414
2515
|
});
|
|
2415
2516
|
throw error;
|
|
2517
|
+
} finally {
|
|
2518
|
+
releaseMutationSlot();
|
|
2416
2519
|
}
|
|
2417
2520
|
},
|
|
2418
2521
|
registerSchedule: (schedule) => {
|
|
@@ -2632,6 +2735,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2632
2735
|
completed: mutationsCompleted,
|
|
2633
2736
|
failed: mutationsFailed,
|
|
2634
2737
|
inFlight: mutationsInFlight,
|
|
2738
|
+
queued: mutationsQueued,
|
|
2635
2739
|
retried: mutationsRetried
|
|
2636
2740
|
},
|
|
2637
2741
|
reactiveCache: {
|
|
@@ -2643,6 +2747,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2643
2747
|
},
|
|
2644
2748
|
subscriptions: {
|
|
2645
2749
|
byCollection,
|
|
2750
|
+
byTenant: Object.fromEntries(subscriptionsByTenant),
|
|
2646
2751
|
total: totalSubscriptions
|
|
2647
2752
|
},
|
|
2648
2753
|
uptimeMs: now - engineStartedAt,
|
|
@@ -3025,5 +3130,5 @@ export {
|
|
|
3025
3130
|
createPresenceHub
|
|
3026
3131
|
};
|
|
3027
3132
|
|
|
3028
|
-
//# debugId=
|
|
3133
|
+
//# debugId=27A5DE0DF43D569D64756E2164756E21
|
|
3029
3134
|
//# sourceMappingURL=index.js.map
|