@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/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=A1FB7830183D82D164756E2164756E21
3780
+ //# debugId=AF6701BD8B8BEA7364756E2164756E21
3514
3781
  //# sourceMappingURL=index.js.map