@btravstack/outbox 0.18.0 → 0.20.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.cjs CHANGED
@@ -99,24 +99,18 @@ const startRelay = (store, publisher, observers, clock, { tenants, pollMs }) =>
99
99
  }))))).isOk() ? swept : "failed";
100
100
  };
101
101
  const backoff = (failures) => Math.min(pollMs * 2 ** failures, Math.max(pollMs, MAX_BACKOFF_MS));
102
- const running = (async () => {
103
- const schedule = tenants.map((tenantId) => ({
104
- tenantId,
105
- due: clock.now(),
106
- failures: 0
107
- }));
102
+ const running = Promise.all(tenants.map(async (tenantId) => {
103
+ let due = clock.now();
104
+ let failures = 0;
108
105
  while (!signal.aborted) {
109
- for (const tenant of schedule) {
110
- if (signal.aborted) break;
111
- if (tenant.due > clock.now()) continue;
112
- const swept = await sweep(tenant.tenantId);
113
- tenant.failures = swept === "failed" ? tenant.failures + 1 : 0;
114
- tenant.due = clock.now() + (swept === "full" ? 0 : backoff(tenant.failures));
115
- }
116
- const wait = Math.min(...schedule.map(({ due }) => due)) - clock.now();
106
+ const wait = due - clock.now();
117
107
  if (wait > 0) await clock.sleep(wait, signal);
108
+ if (signal.aborted) break;
109
+ const swept = await sweep(tenantId);
110
+ failures = swept === "failed" ? failures + 1 : 0;
111
+ due = clock.now() + (swept === "full" ? 0 : backoff(failures));
118
112
  }
119
- })();
113
+ }));
120
114
  return { stop: () => (0, unthrown.fromSafePromise)((async () => {
121
115
  stopping.abort();
122
116
  await running;
@@ -142,9 +136,10 @@ const startRelay = (store, publisher, observers, clock, { tenants, pollMs }) =>
142
136
  *
143
137
  * **A refused publish stops the tenant's batch** and backs that tenant off,
144
138
  * doubling from the poll interval to 30 seconds, so a later fact never
145
- * overtakes an earlier one and no other tenant waits. A message the publisher refuses forever therefore holds its tenant's
146
- * outbox — which is what the health check is for: it reports a tenant whose
147
- * oldest pending message is older than `maxLagMs`.
139
+ * overtakes an earlier one. Each tenant has its own loop, so a pending claim
140
+ * or publish holds only that tenant. A message the publisher refuses forever
141
+ * therefore holds its tenant's outbox — which is what the health check is for:
142
+ * it reports a tenant whose oldest pending message is older than `maxLagMs`.
148
143
  *
149
144
  * Started as the graph builds, before the runtime accepts anything, and
150
145
  * stopped when the application scope closes, after the runtime has drained.
package/dist/index.d.cts CHANGED
@@ -49,9 +49,10 @@ type OutboxOptions = {
49
49
  *
50
50
  * **A refused publish stops the tenant's batch** and backs that tenant off,
51
51
  * doubling from the poll interval to 30 seconds, so a later fact never
52
- * overtakes an earlier one and no other tenant waits. A message the publisher refuses forever therefore holds its tenant's
53
- * outbox — which is what the health check is for: it reports a tenant whose
54
- * oldest pending message is older than `maxLagMs`.
52
+ * overtakes an earlier one. Each tenant has its own loop, so a pending claim
53
+ * or publish holds only that tenant. A message the publisher refuses forever
54
+ * therefore holds its tenant's outbox — which is what the health check is for:
55
+ * it reports a tenant whose oldest pending message is older than `maxLagMs`.
55
56
  *
56
57
  * Started as the graph builds, before the runtime accepts anything, and
57
58
  * stopped when the application scope closes, after the runtime has drained.
package/dist/index.d.mts CHANGED
@@ -49,9 +49,10 @@ type OutboxOptions = {
49
49
  *
50
50
  * **A refused publish stops the tenant's batch** and backs that tenant off,
51
51
  * doubling from the poll interval to 30 seconds, so a later fact never
52
- * overtakes an earlier one and no other tenant waits. A message the publisher refuses forever therefore holds its tenant's
53
- * outbox — which is what the health check is for: it reports a tenant whose
54
- * oldest pending message is older than `maxLagMs`.
52
+ * overtakes an earlier one. Each tenant has its own loop, so a pending claim
53
+ * or publish holds only that tenant. A message the publisher refuses forever
54
+ * therefore holds its tenant's outbox — which is what the health check is for:
55
+ * it reports a tenant whose oldest pending message is older than `maxLagMs`.
55
56
  *
56
57
  * Started as the graph builds, before the runtime accepts anything, and
57
58
  * stopped when the application scope closes, after the runtime has drained.
package/dist/index.mjs CHANGED
@@ -98,24 +98,18 @@ const startRelay = (store, publisher, observers, clock, { tenants, pollMs }) =>
98
98
  }))))).isOk() ? swept : "failed";
99
99
  };
100
100
  const backoff = (failures) => Math.min(pollMs * 2 ** failures, Math.max(pollMs, MAX_BACKOFF_MS));
101
- const running = (async () => {
102
- const schedule = tenants.map((tenantId) => ({
103
- tenantId,
104
- due: clock.now(),
105
- failures: 0
106
- }));
101
+ const running = Promise.all(tenants.map(async (tenantId) => {
102
+ let due = clock.now();
103
+ let failures = 0;
107
104
  while (!signal.aborted) {
108
- for (const tenant of schedule) {
109
- if (signal.aborted) break;
110
- if (tenant.due > clock.now()) continue;
111
- const swept = await sweep(tenant.tenantId);
112
- tenant.failures = swept === "failed" ? tenant.failures + 1 : 0;
113
- tenant.due = clock.now() + (swept === "full" ? 0 : backoff(tenant.failures));
114
- }
115
- const wait = Math.min(...schedule.map(({ due }) => due)) - clock.now();
105
+ const wait = due - clock.now();
116
106
  if (wait > 0) await clock.sleep(wait, signal);
107
+ if (signal.aborted) break;
108
+ const swept = await sweep(tenantId);
109
+ failures = swept === "failed" ? failures + 1 : 0;
110
+ due = clock.now() + (swept === "full" ? 0 : backoff(failures));
117
111
  }
118
- })();
112
+ }));
119
113
  return { stop: () => fromSafePromise((async () => {
120
114
  stopping.abort();
121
115
  await running;
@@ -141,9 +135,10 @@ const startRelay = (store, publisher, observers, clock, { tenants, pollMs }) =>
141
135
  *
142
136
  * **A refused publish stops the tenant's batch** and backs that tenant off,
143
137
  * doubling from the poll interval to 30 seconds, so a later fact never
144
- * overtakes an earlier one and no other tenant waits. A message the publisher refuses forever therefore holds its tenant's
145
- * outbox — which is what the health check is for: it reports a tenant whose
146
- * oldest pending message is older than `maxLagMs`.
138
+ * overtakes an earlier one. Each tenant has its own loop, so a pending claim
139
+ * or publish holds only that tenant. A message the publisher refuses forever
140
+ * therefore holds its tenant's outbox — which is what the health check is for:
141
+ * it reports a tenant whose oldest pending message is older than `maxLagMs`.
147
142
  *
148
143
  * Started as the graph builds, before the runtime accepts anything, and
149
144
  * stopped when the application scope closes, after the runtime has drained.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/outbox",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "The transactional outbox relay for @btravstack/core: a poll loop that publishes committed facts in outbox order, a per-tenant claim so replicas take turns rather than race for the same rows, and a Prisma 8 store",
5
5
  "keywords": [
6
6
  "dependency-injection",
@@ -53,10 +53,10 @@
53
53
  "./package.json": "./package.json"
54
54
  },
55
55
  "devDependencies": {
56
- "@btravstack/config": "0.18.0",
57
- "@btravstack/core": "0.18.0",
58
- "@btravstack/di": "0.18.0",
59
- "@btravstack/testing": "0.18.0",
56
+ "@btravstack/config": "0.20.0",
57
+ "@btravstack/core": "0.20.0",
58
+ "@btravstack/di": "0.20.0",
59
+ "@btravstack/testing": "0.20.0",
60
60
  "@btravstack/tsconfig": "0.4.0",
61
61
  "@types/node": "26.6.4",
62
62
  "@unthrown/vitest": "5.12.0",
@@ -67,9 +67,9 @@
67
67
  "vitest": "5.0.3"
68
68
  },
69
69
  "peerDependencies": {
70
- "@btravstack/config": "^0.18.0",
71
- "@btravstack/core": "^0.18.0",
72
- "@btravstack/di": "^0.18.0",
70
+ "@btravstack/config": "^0.20.0",
71
+ "@btravstack/core": "^0.20.0",
72
+ "@btravstack/di": "^0.20.0",
73
73
  "@prisma/orm-postgres": "^8.0.0-rc.11",
74
74
  "unthrown": "^5.0.0"
75
75
  },