@absolutejs/sync 2.2.1 → 2.3.0
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 +89 -32
- package/dist/connectionBroker.d.ts +147 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +322 -55
- package/dist/index.js.map +5 -4
- package/dist/manifest.d.ts +17 -0
- package/dist/manifest.js +8882 -0
- package/dist/manifest.js.map +123 -0
- package/dist/manifest.json +390 -0
- package/package.json +18 -3
package/dist/index.js
CHANGED
|
@@ -82,6 +82,323 @@ var createWriteBehindCache = (options) => {
|
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
};
|
|
85
|
+
// node_modules/@absolutejs/telemetry/dist/index.js
|
|
86
|
+
var NOOP_SPAN_CONTEXT = {
|
|
87
|
+
spanId: "0000000000000000",
|
|
88
|
+
traceFlags: 0,
|
|
89
|
+
traceId: "00000000000000000000000000000000"
|
|
90
|
+
};
|
|
91
|
+
var noopSpan = {
|
|
92
|
+
addEvent: () => noopSpan,
|
|
93
|
+
end: () => {},
|
|
94
|
+
isRecording: () => false,
|
|
95
|
+
recordException: () => {},
|
|
96
|
+
setAttribute: () => noopSpan,
|
|
97
|
+
setAttributes: () => noopSpan,
|
|
98
|
+
setStatus: () => noopSpan,
|
|
99
|
+
spanContext: () => NOOP_SPAN_CONTEXT,
|
|
100
|
+
updateName: () => noopSpan
|
|
101
|
+
};
|
|
102
|
+
var startActiveSpanNoop = (_name, optionsOrFn, maybeFn) => {
|
|
103
|
+
const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
|
|
104
|
+
return fn(noopSpan);
|
|
105
|
+
};
|
|
106
|
+
var noopTracer = {
|
|
107
|
+
startActiveSpan: startActiveSpanNoop,
|
|
108
|
+
startSpan: () => noopSpan
|
|
109
|
+
};
|
|
110
|
+
var tracerOrNoop = (provider, name, version) => provider !== undefined ? provider.getTracer(name, version) : noopTracer;
|
|
111
|
+
var ABS_ATTRS = {
|
|
112
|
+
tenant: "abs.tenant",
|
|
113
|
+
shardId: "abs.shard.id",
|
|
114
|
+
engineId: "abs.engine.id",
|
|
115
|
+
collection: "abs.collection",
|
|
116
|
+
mutation: "abs.mutation",
|
|
117
|
+
mutationAttempt: "abs.mutation.attempt",
|
|
118
|
+
subscriptionId: "abs.subscription.id",
|
|
119
|
+
batchSize: "abs.batch.size",
|
|
120
|
+
clusterMessageOrigin: "abs.cluster.origin",
|
|
121
|
+
jobId: "abs.job.id",
|
|
122
|
+
jobKind: "abs.job.kind",
|
|
123
|
+
jobAttempt: "abs.job.attempt",
|
|
124
|
+
jobMaxAttempts: "abs.job.max_attempts",
|
|
125
|
+
workerId: "abs.worker.id",
|
|
126
|
+
runtimeKey: "abs.runtime.key",
|
|
127
|
+
runtimePid: "abs.runtime.pid",
|
|
128
|
+
runtimePort: "abs.runtime.port",
|
|
129
|
+
runtimeExitReason: "abs.runtime.exit_reason",
|
|
130
|
+
runtimeReadinessMs: "abs.runtime.readiness_ms",
|
|
131
|
+
routeShard: "abs.route.shard",
|
|
132
|
+
routeDecision: "abs.route.decision",
|
|
133
|
+
secretName: "abs.secret.name",
|
|
134
|
+
secretFingerprint: "abs.secret.fingerprint",
|
|
135
|
+
auditKind: "abs.audit.kind"
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/connectionBroker.ts
|
|
139
|
+
class LeaseTimeoutError extends Error {
|
|
140
|
+
tenant;
|
|
141
|
+
timeoutMs;
|
|
142
|
+
constructor(tenant, timeoutMs) {
|
|
143
|
+
super(`Lease for tenant "${tenant}" timed out after ${timeoutMs}ms; ` + `the broker is at its connection caps and nothing freed up ` + `in time. Raise maxTotal/maxPerTenant or shed load upstream.`);
|
|
144
|
+
this.name = "LeaseTimeoutError";
|
|
145
|
+
this.tenant = tenant;
|
|
146
|
+
this.timeoutMs = timeoutMs;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class ConnectionBrokerDrainedError extends Error {
|
|
151
|
+
constructor() {
|
|
152
|
+
super("Connection broker is drained; no new leases are issued. " + "Construct a fresh broker to resume leasing.");
|
|
153
|
+
this.name = "ConnectionBrokerDrainedError";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
var QUEUE_WAIT_ATTR = "abs.broker.queue_wait_ms";
|
|
157
|
+
var createConnectionBroker = (options) => {
|
|
158
|
+
if (!Number.isFinite(options.maxTotal) || options.maxTotal <= 0) {
|
|
159
|
+
throw new RangeError(`createConnectionBroker requires maxTotal > 0 (got ${options.maxTotal})`);
|
|
160
|
+
}
|
|
161
|
+
const now = options.now ?? Date.now;
|
|
162
|
+
const tracer = tracerOrNoop(options.tracerProvider, "@absolutejs/sync");
|
|
163
|
+
const idle = [];
|
|
164
|
+
const queue = [];
|
|
165
|
+
const inUseByTenant = new Map;
|
|
166
|
+
let inUse = 0;
|
|
167
|
+
let drained = false;
|
|
168
|
+
let disposed = false;
|
|
169
|
+
let drainPromise;
|
|
170
|
+
let resolveDrain;
|
|
171
|
+
const cumulative = {
|
|
172
|
+
created: 0,
|
|
173
|
+
destroyed: 0,
|
|
174
|
+
leases: 0,
|
|
175
|
+
releases: 0,
|
|
176
|
+
timeouts: 0,
|
|
177
|
+
validationFailures: 0
|
|
178
|
+
};
|
|
179
|
+
const destroyConn = async (conn) => {
|
|
180
|
+
cumulative.destroyed += 1;
|
|
181
|
+
try {
|
|
182
|
+
await options.destroy?.(conn);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
options.onError?.(error, "destroy");
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const sweepIdle = () => {
|
|
188
|
+
const idleReleaseMs = options.idleReleaseMs;
|
|
189
|
+
if (idleReleaseMs === undefined)
|
|
190
|
+
return;
|
|
191
|
+
const cutoff = now() - idleReleaseMs;
|
|
192
|
+
while (idle.length > 0 && idle[0] !== undefined) {
|
|
193
|
+
const oldest = idle[0];
|
|
194
|
+
if (oldest.idleSince > cutoff)
|
|
195
|
+
break;
|
|
196
|
+
idle.shift();
|
|
197
|
+
destroyConn(oldest.conn);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const sweepTimer = options.idleReleaseMs === undefined ? undefined : setInterval(sweepIdle, options.idleReleaseMs);
|
|
201
|
+
sweepTimer?.unref?.();
|
|
202
|
+
const canGrant = (tenant) => inUse < options.maxTotal && (options.maxPerTenant === undefined || (inUseByTenant.get(tenant) ?? 0) < options.maxPerTenant);
|
|
203
|
+
const reserve = (tenant) => {
|
|
204
|
+
inUse += 1;
|
|
205
|
+
inUseByTenant.set(tenant, (inUseByTenant.get(tenant) ?? 0) + 1);
|
|
206
|
+
};
|
|
207
|
+
const unreserve = (tenant) => {
|
|
208
|
+
inUse -= 1;
|
|
209
|
+
const count = inUseByTenant.get(tenant) ?? 0;
|
|
210
|
+
if (count <= 1)
|
|
211
|
+
inUseByTenant.delete(tenant);
|
|
212
|
+
else
|
|
213
|
+
inUseByTenant.set(tenant, count - 1);
|
|
214
|
+
if (drained && inUse === 0 && queue.length === 0) {
|
|
215
|
+
resolveDrain?.();
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const acquireConn = async () => {
|
|
219
|
+
let entry = idle.pop();
|
|
220
|
+
while (entry !== undefined) {
|
|
221
|
+
if (options.validate === undefined)
|
|
222
|
+
return entry.conn;
|
|
223
|
+
let healthy = false;
|
|
224
|
+
try {
|
|
225
|
+
healthy = await options.validate(entry.conn);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
options.onError?.(error, "validate");
|
|
228
|
+
}
|
|
229
|
+
if (healthy)
|
|
230
|
+
return entry.conn;
|
|
231
|
+
cumulative.validationFailures += 1;
|
|
232
|
+
await destroyConn(entry.conn);
|
|
233
|
+
entry = idle.pop();
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const conn = await options.create();
|
|
237
|
+
cumulative.created += 1;
|
|
238
|
+
return conn;
|
|
239
|
+
} catch (error) {
|
|
240
|
+
options.onError?.(error, "create");
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const endSpanWithError = (span, error) => {
|
|
245
|
+
const spanError = error instanceof Error ? error : new Error(String(error));
|
|
246
|
+
span.recordException(spanError);
|
|
247
|
+
span.setStatus({
|
|
248
|
+
code: 2,
|
|
249
|
+
message: spanError.message
|
|
250
|
+
});
|
|
251
|
+
span.end();
|
|
252
|
+
};
|
|
253
|
+
const makeLease = (tenant, conn) => {
|
|
254
|
+
cumulative.leases += 1;
|
|
255
|
+
let released = false;
|
|
256
|
+
return {
|
|
257
|
+
conn,
|
|
258
|
+
release: () => {
|
|
259
|
+
if (released)
|
|
260
|
+
return;
|
|
261
|
+
released = true;
|
|
262
|
+
cumulative.releases += 1;
|
|
263
|
+
if (disposed) {
|
|
264
|
+
destroyConn(conn);
|
|
265
|
+
} else {
|
|
266
|
+
idle.push({ conn, idleSince: now() });
|
|
267
|
+
}
|
|
268
|
+
unreserve(tenant);
|
|
269
|
+
pump();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
const grantTo = async (waiter) => {
|
|
274
|
+
try {
|
|
275
|
+
const conn = await acquireConn();
|
|
276
|
+
waiter.span.setAttribute(QUEUE_WAIT_ATTR, now() - waiter.enqueuedAt);
|
|
277
|
+
waiter.span.end();
|
|
278
|
+
waiter.resolve(makeLease(waiter.tenant, conn));
|
|
279
|
+
} catch (error) {
|
|
280
|
+
unreserve(waiter.tenant);
|
|
281
|
+
endSpanWithError(waiter.span, error);
|
|
282
|
+
waiter.reject(error);
|
|
283
|
+
pump();
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const pump = () => {
|
|
287
|
+
if (disposed)
|
|
288
|
+
return;
|
|
289
|
+
let index = 0;
|
|
290
|
+
while (index < queue.length) {
|
|
291
|
+
const waiter = queue[index];
|
|
292
|
+
if (waiter === undefined)
|
|
293
|
+
break;
|
|
294
|
+
if (!canGrant(waiter.tenant)) {
|
|
295
|
+
index += 1;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
queue.splice(index, 1);
|
|
299
|
+
if (waiter.timer !== undefined)
|
|
300
|
+
clearTimeout(waiter.timer);
|
|
301
|
+
reserve(waiter.tenant);
|
|
302
|
+
grantTo(waiter);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
const rejectQueued = (error) => {
|
|
306
|
+
const waiting = queue.splice(0, queue.length);
|
|
307
|
+
for (const waiter of waiting) {
|
|
308
|
+
if (waiter.timer !== undefined)
|
|
309
|
+
clearTimeout(waiter.timer);
|
|
310
|
+
const rejection = error();
|
|
311
|
+
endSpanWithError(waiter.span, rejection);
|
|
312
|
+
waiter.reject(rejection);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const lease = async (tenant) => {
|
|
316
|
+
if (drained)
|
|
317
|
+
throw new ConnectionBrokerDrainedError;
|
|
318
|
+
sweepIdle();
|
|
319
|
+
const span = tracer.startSpan("sync.broker_lease", {
|
|
320
|
+
attributes: { [ABS_ATTRS.tenant]: tenant }
|
|
321
|
+
});
|
|
322
|
+
const startedAt = now();
|
|
323
|
+
if (canGrant(tenant)) {
|
|
324
|
+
reserve(tenant);
|
|
325
|
+
try {
|
|
326
|
+
const conn = await acquireConn();
|
|
327
|
+
span.setAttribute(QUEUE_WAIT_ATTR, now() - startedAt);
|
|
328
|
+
span.end();
|
|
329
|
+
return makeLease(tenant, conn);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
unreserve(tenant);
|
|
332
|
+
endSpanWithError(span, error);
|
|
333
|
+
pump();
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return new Promise((resolve, reject) => {
|
|
338
|
+
const waiter = {
|
|
339
|
+
enqueuedAt: startedAt,
|
|
340
|
+
reject,
|
|
341
|
+
resolve,
|
|
342
|
+
span,
|
|
343
|
+
tenant,
|
|
344
|
+
timer: undefined
|
|
345
|
+
};
|
|
346
|
+
if (options.acquireTimeoutMs !== undefined) {
|
|
347
|
+
const timeoutMs = options.acquireTimeoutMs;
|
|
348
|
+
waiter.timer = setTimeout(() => {
|
|
349
|
+
const position = queue.indexOf(waiter);
|
|
350
|
+
if (position === -1)
|
|
351
|
+
return;
|
|
352
|
+
queue.splice(position, 1);
|
|
353
|
+
cumulative.timeouts += 1;
|
|
354
|
+
const timeout = new LeaseTimeoutError(tenant, timeoutMs);
|
|
355
|
+
endSpanWithError(span, timeout);
|
|
356
|
+
reject(timeout);
|
|
357
|
+
}, timeoutMs);
|
|
358
|
+
}
|
|
359
|
+
queue.push(waiter);
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
const drain = () => {
|
|
363
|
+
drained = true;
|
|
364
|
+
if (drainPromise === undefined) {
|
|
365
|
+
drainPromise = inUse === 0 && queue.length === 0 ? Promise.resolve() : new Promise((resolve) => {
|
|
366
|
+
resolveDrain = resolve;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
return drainPromise;
|
|
370
|
+
};
|
|
371
|
+
return {
|
|
372
|
+
dispose: async () => {
|
|
373
|
+
drained = true;
|
|
374
|
+
disposed = true;
|
|
375
|
+
if (sweepTimer !== undefined)
|
|
376
|
+
clearInterval(sweepTimer);
|
|
377
|
+
rejectQueued(() => new ConnectionBrokerDrainedError);
|
|
378
|
+
const pooled = idle.splice(0, idle.length);
|
|
379
|
+
await Promise.all(pooled.map((entry) => destroyConn(entry.conn)));
|
|
380
|
+
if (inUse === 0)
|
|
381
|
+
resolveDrain?.();
|
|
382
|
+
},
|
|
383
|
+
drain,
|
|
384
|
+
lease,
|
|
385
|
+
metrics: () => ({
|
|
386
|
+
byTenant: Object.fromEntries(inUseByTenant),
|
|
387
|
+
cumulative: { ...cumulative },
|
|
388
|
+
idle: idle.length,
|
|
389
|
+
inUse,
|
|
390
|
+
queued: queue.length
|
|
391
|
+
}),
|
|
392
|
+
withLease: async (tenant, fn) => {
|
|
393
|
+
const { conn, release } = await lease(tenant);
|
|
394
|
+
try {
|
|
395
|
+
return await fn(conn);
|
|
396
|
+
} finally {
|
|
397
|
+
release();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
};
|
|
85
402
|
// src/reactiveHub.ts
|
|
86
403
|
var SYNC_OPEN_TOPIC = "@absolutejs/sync:open";
|
|
87
404
|
var createReactiveHub = () => {
|
|
@@ -564,59 +881,6 @@ var syncSocket = ({
|
|
|
564
881
|
}
|
|
565
882
|
});
|
|
566
883
|
};
|
|
567
|
-
// node_modules/@absolutejs/telemetry/dist/index.js
|
|
568
|
-
var NOOP_SPAN_CONTEXT = {
|
|
569
|
-
spanId: "0000000000000000",
|
|
570
|
-
traceFlags: 0,
|
|
571
|
-
traceId: "00000000000000000000000000000000"
|
|
572
|
-
};
|
|
573
|
-
var noopSpan = {
|
|
574
|
-
addEvent: () => noopSpan,
|
|
575
|
-
end: () => {},
|
|
576
|
-
isRecording: () => false,
|
|
577
|
-
recordException: () => {},
|
|
578
|
-
setAttribute: () => noopSpan,
|
|
579
|
-
setAttributes: () => noopSpan,
|
|
580
|
-
setStatus: () => noopSpan,
|
|
581
|
-
spanContext: () => NOOP_SPAN_CONTEXT,
|
|
582
|
-
updateName: () => noopSpan
|
|
583
|
-
};
|
|
584
|
-
var startActiveSpanNoop = (_name, optionsOrFn, maybeFn) => {
|
|
585
|
-
const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
|
|
586
|
-
return fn(noopSpan);
|
|
587
|
-
};
|
|
588
|
-
var noopTracer = {
|
|
589
|
-
startActiveSpan: startActiveSpanNoop,
|
|
590
|
-
startSpan: () => noopSpan
|
|
591
|
-
};
|
|
592
|
-
var tracerOrNoop = (provider, name, version) => provider !== undefined ? provider.getTracer(name, version) : noopTracer;
|
|
593
|
-
var ABS_ATTRS = {
|
|
594
|
-
tenant: "abs.tenant",
|
|
595
|
-
shardId: "abs.shard.id",
|
|
596
|
-
engineId: "abs.engine.id",
|
|
597
|
-
collection: "abs.collection",
|
|
598
|
-
mutation: "abs.mutation",
|
|
599
|
-
mutationAttempt: "abs.mutation.attempt",
|
|
600
|
-
subscriptionId: "abs.subscription.id",
|
|
601
|
-
batchSize: "abs.batch.size",
|
|
602
|
-
clusterMessageOrigin: "abs.cluster.origin",
|
|
603
|
-
jobId: "abs.job.id",
|
|
604
|
-
jobKind: "abs.job.kind",
|
|
605
|
-
jobAttempt: "abs.job.attempt",
|
|
606
|
-
jobMaxAttempts: "abs.job.max_attempts",
|
|
607
|
-
workerId: "abs.worker.id",
|
|
608
|
-
runtimeKey: "abs.runtime.key",
|
|
609
|
-
runtimePid: "abs.runtime.pid",
|
|
610
|
-
runtimePort: "abs.runtime.port",
|
|
611
|
-
runtimeExitReason: "abs.runtime.exit_reason",
|
|
612
|
-
runtimeReadinessMs: "abs.runtime.readiness_ms",
|
|
613
|
-
routeShard: "abs.route.shard",
|
|
614
|
-
routeDecision: "abs.route.decision",
|
|
615
|
-
secretName: "abs.secret.name",
|
|
616
|
-
secretFingerprint: "abs.secret.fingerprint",
|
|
617
|
-
auditKind: "abs.audit.kind"
|
|
618
|
-
};
|
|
619
|
-
|
|
620
884
|
// src/engine/equiJoin.ts
|
|
621
885
|
var shallowEqual = (a, b) => {
|
|
622
886
|
if (a === b) {
|
|
@@ -3507,8 +3771,11 @@ export {
|
|
|
3507
3771
|
jsonSerializer,
|
|
3508
3772
|
createWriteBehindCache,
|
|
3509
3773
|
createReactiveHub,
|
|
3510
|
-
createPresenceHub
|
|
3774
|
+
createPresenceHub,
|
|
3775
|
+
createConnectionBroker,
|
|
3776
|
+
LeaseTimeoutError,
|
|
3777
|
+
ConnectionBrokerDrainedError
|
|
3511
3778
|
};
|
|
3512
3779
|
|
|
3513
|
-
//# debugId=
|
|
3780
|
+
//# debugId=AF6701BD8B8BEA7364756E2164756E21
|
|
3514
3781
|
//# sourceMappingURL=index.js.map
|