velocious 1.0.596 → 1.0.597

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.
Files changed (106) hide show
  1. package/README.md +16 -0
  2. package/build/background-jobs/pooled-runner-broker-identity.js +22 -4
  3. package/build/background-jobs/store.js +383 -9
  4. package/build/background-jobs/types.js +1 -0
  5. package/build/cli/commands/db/tenants/migrations/pending.js +45 -0
  6. package/build/configuration-types.js +3 -0
  7. package/build/configuration.js +5 -2
  8. package/build/database/drivers/sqlite/base.js +3 -3
  9. package/build/database/tenants/migration-pending-inspector.js +77 -0
  10. package/build/environment-handlers/base.js +8 -0
  11. package/build/environment-handlers/node.js +14 -0
  12. package/build/mailer/backends/resend-smtp.js +121 -0
  13. package/build/mailer/base.js +52 -22
  14. package/build/mailer/delivery-operation-store.js +152 -0
  15. package/build/mailer/delivery-operation.js +205 -0
  16. package/build/mailer/delivery.js +9 -4
  17. package/build/mailer/index.js +5 -1
  18. package/build/mailer.js +6 -1
  19. package/build/src/background-jobs/pooled-runner-broker-identity.d.ts +10 -1
  20. package/build/src/background-jobs/pooled-runner-broker-identity.d.ts.map +1 -1
  21. package/build/src/background-jobs/pooled-runner-broker-identity.js +23 -5
  22. package/build/src/background-jobs/store.d.ts +154 -0
  23. package/build/src/background-jobs/store.d.ts.map +1 -1
  24. package/build/src/background-jobs/store.js +350 -10
  25. package/build/src/background-jobs/types.d.ts +5 -0
  26. package/build/src/background-jobs/types.d.ts.map +1 -1
  27. package/build/src/background-jobs/types.js +2 -1
  28. package/build/src/cli/commands/db/tenants/migrations/pending.d.ts +15 -0
  29. package/build/src/cli/commands/db/tenants/migrations/pending.d.ts.map +1 -0
  30. package/build/src/cli/commands/db/tenants/migrations/pending.js +40 -0
  31. package/build/src/configuration-types.d.ts +20 -0
  32. package/build/src/configuration-types.d.ts.map +1 -1
  33. package/build/src/configuration-types.js +4 -1
  34. package/build/src/configuration.d.ts +2 -2
  35. package/build/src/configuration.d.ts.map +1 -1
  36. package/build/src/configuration.js +6 -3
  37. package/build/src/database/drivers/sqlite/base.js +4 -4
  38. package/build/src/database/tenants/migration-pending-inspector.d.ts +38 -0
  39. package/build/src/database/tenants/migration-pending-inspector.d.ts.map +1 -0
  40. package/build/src/database/tenants/migration-pending-inspector.js +70 -0
  41. package/build/src/environment-handlers/base.d.ts +10 -0
  42. package/build/src/environment-handlers/base.d.ts.map +1 -1
  43. package/build/src/environment-handlers/base.js +8 -1
  44. package/build/src/environment-handlers/node.d.ts +11 -0
  45. package/build/src/environment-handlers/node.d.ts.map +1 -1
  46. package/build/src/environment-handlers/node.js +14 -1
  47. package/build/src/mailer/backends/resend-smtp.d.ts +43 -0
  48. package/build/src/mailer/backends/resend-smtp.d.ts.map +1 -0
  49. package/build/src/mailer/backends/resend-smtp.js +106 -0
  50. package/build/src/mailer/base.d.ts +9 -2
  51. package/build/src/mailer/base.d.ts.map +1 -1
  52. package/build/src/mailer/base.js +41 -19
  53. package/build/src/mailer/delivery-operation-store.d.ts +52 -0
  54. package/build/src/mailer/delivery-operation-store.d.ts.map +1 -0
  55. package/build/src/mailer/delivery-operation-store.js +130 -0
  56. package/build/src/mailer/delivery-operation.d.ts +73 -0
  57. package/build/src/mailer/delivery-operation.d.ts.map +1 -0
  58. package/build/src/mailer/delivery-operation.js +185 -0
  59. package/build/src/mailer/delivery.d.ts +4 -2
  60. package/build/src/mailer/delivery.d.ts.map +1 -1
  61. package/build/src/mailer/delivery.js +9 -5
  62. package/build/src/mailer/index.d.ts +24 -1
  63. package/build/src/mailer/index.d.ts.map +1 -1
  64. package/build/src/mailer/index.js +6 -2
  65. package/build/src/mailer.d.ts +11 -13
  66. package/build/src/mailer.d.ts.map +1 -1
  67. package/build/src/mailer.js +7 -2
  68. package/build/src/sync/local-mutation-log.d.ts +5 -5
  69. package/build/src/sync/local-mutation-log.d.ts.map +1 -1
  70. package/build/src/sync/local-mutation-log.js +34 -10
  71. package/build/src/sync/stable-json.d.ts +1 -8
  72. package/build/src/sync/stable-json.d.ts.map +1 -1
  73. package/build/src/sync/stable-json.js +2 -27
  74. package/build/src/sync/sync-envelope-replay-service.d.ts +3 -2
  75. package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
  76. package/build/src/sync/sync-envelope-replay-service.js +12 -8
  77. package/build/src/utils/stable-json.d.ts +7 -0
  78. package/build/src/utils/stable-json.d.ts.map +1 -0
  79. package/build/src/utils/stable-json.js +25 -0
  80. package/build/sync/local-mutation-log.js +37 -6
  81. package/build/sync/stable-json.js +1 -28
  82. package/build/sync/sync-envelope-replay-service.js +11 -7
  83. package/build/tsconfig.tsbuildinfo +1 -1
  84. package/build/utils/stable-json.js +26 -0
  85. package/package.json +1 -1
  86. package/src/background-jobs/pooled-runner-broker-identity.js +22 -4
  87. package/src/background-jobs/store.js +383 -9
  88. package/src/background-jobs/types.js +1 -0
  89. package/src/cli/commands/db/tenants/migrations/pending.js +45 -0
  90. package/src/configuration-types.js +3 -0
  91. package/src/configuration.js +5 -2
  92. package/src/database/drivers/sqlite/base.js +3 -3
  93. package/src/database/tenants/migration-pending-inspector.js +77 -0
  94. package/src/environment-handlers/base.js +8 -0
  95. package/src/environment-handlers/node.js +14 -0
  96. package/src/mailer/backends/resend-smtp.js +121 -0
  97. package/src/mailer/base.js +52 -22
  98. package/src/mailer/delivery-operation-store.js +152 -0
  99. package/src/mailer/delivery-operation.js +205 -0
  100. package/src/mailer/delivery.js +9 -4
  101. package/src/mailer/index.js +5 -1
  102. package/src/mailer.js +6 -1
  103. package/src/sync/local-mutation-log.js +37 -6
  104. package/src/sync/stable-json.js +1 -28
  105. package/src/sync/sync-envelope-replay-service.js +11 -7
  106. package/src/utils/stable-json.js +26 -0
@@ -0,0 +1,205 @@
1
+ // @ts-check
2
+
3
+ import {createHash} from "crypto"
4
+ import VelociousError from "../velocious-error.js"
5
+ import stableJsonStringify from "../utils/stable-json.js"
6
+
7
+ export const MAIL_DELIVERY_JOB_NAME = "MailDeliveryJob"
8
+ export const MAIL_DELIVERY_OPERATIONS_TABLE = "mailer_delivery_operations"
9
+ const PAYLOAD_DIGEST_FORMAT = "velocious-mail-delivery-payload-v1"
10
+
11
+ /**
12
+ * Reads and validates a backend's provider idempotency capability.
13
+ * @param {object} args - Capability input.
14
+ * @param {import("../configuration-types.js").MailerBackend | undefined} args.backend - Configured backend.
15
+ * @param {import("./index.js").MailerDeliveryOperationRequest | import("./index.js").MailerDeliveryOperation} args.deliveryOperation - Required operation.
16
+ * @param {import("./index.js").MailerDeliveryPayload} args.payload - Rendered or persisted payload.
17
+ * @returns {import("./index.js").MailerDeliveryIdempotencyCapability} - Capability.
18
+ */
19
+ export function requireDeliveryIdempotencyCapability({backend, deliveryOperation, payload}) {
20
+ if (!backend || typeof backend.deliveryIdempotencyCapability !== "function") {
21
+ throw VelociousError.safe("The configured mailer backend does not support required provider idempotency.", {
22
+ code: "mail-delivery-idempotency-unsupported"
23
+ })
24
+ }
25
+
26
+ const capability = backend.deliveryIdempotencyCapability()
27
+
28
+ if (!capability || typeof capability.providerKind !== "string" || capability.providerKind.length === 0) {
29
+ throw new Error("Mailer backend delivery idempotency capability requires a non-empty providerKind")
30
+ }
31
+ if (!Number.isSafeInteger(capability.retentionMs) || capability.retentionMs <= 0) {
32
+ throw new Error("Mailer backend delivery idempotency capability requires a positive safe-integer retentionMs")
33
+ }
34
+
35
+ if (typeof backend.validateDeliveryOperation === "function") {
36
+ backend.validateDeliveryOperation({deliveryOperation, payload})
37
+ }
38
+
39
+ return capability
40
+ }
41
+
42
+ /**
43
+ * Normalizes one public required operation into immutable payload metadata.
44
+ * @param {object} args - Preparation input.
45
+ * @param {import("./index.js").MailerDeliveryIdempotencyCapability} args.capability - Backend capability.
46
+ * @param {import("./index.js").MailerDeliveryOperationRequest} args.deliveryOperation - Public operation request.
47
+ * @param {import("./index.js").MailerDeliveryPayload} args.payload - Rendered payload.
48
+ * @returns {import("./index.js").MailerDeliveryPayload} - Payload with immutable operation metadata.
49
+ */
50
+ export function prepareRequiredDeliveryPayload({capability, deliveryOperation, payload}) {
51
+ validateDeliveryOperationRequest(deliveryOperation)
52
+ const payloadDigest = mailDeliveryPayloadDigest({operationId: deliveryOperation.id, payload})
53
+
54
+ return {
55
+ ...payload,
56
+ deliveryOperation: {
57
+ id: deliveryOperation.id,
58
+ idempotency: "required",
59
+ payloadDigest,
60
+ providerKind: capability.providerKind,
61
+ providerRetentionMs: capability.retentionMs
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Builds the versioned digest for every recipient-visible/provider-relevant payload field.
68
+ * @param {object} args - Digest input.
69
+ * @param {string} args.operationId - Stable operation id.
70
+ * @param {import("./index.js").MailerDeliveryPayload} args.payload - Rendered payload.
71
+ * @returns {string} - Versioned SHA-256 digest.
72
+ */
73
+ export function mailDeliveryPayloadDigest({operationId, payload}) {
74
+ const canonicalPayload = {
75
+ action: payload.action,
76
+ bcc: payload.bcc ?? null,
77
+ cc: payload.cc ?? null,
78
+ format: PAYLOAD_DIGEST_FORMAT,
79
+ from: payload.from ?? null,
80
+ headers: canonicalHeaders(payload.headers),
81
+ html: payload.html,
82
+ mailer: payload.mailer,
83
+ operationId,
84
+ replyTo: payload.replyTo ?? null,
85
+ subject: payload.subject,
86
+ to: payload.to
87
+ }
88
+ const digest = createHash("sha256").update(stableJsonStringify(canonicalPayload)).digest("hex")
89
+
90
+ return `sha256:v1:${digest}`
91
+ }
92
+
93
+ /**
94
+ * Extracts validated persisted operation metadata from a payload.
95
+ * @param {import("./index.js").MailerDeliveryPayload} payload - Mail payload.
96
+ * @returns {import("./index.js").MailerDeliveryOperation | null} - Operation or null.
97
+ */
98
+ export function deliveryOperationFromPayload(payload) {
99
+ const operation = payload.deliveryOperation
100
+
101
+ if (!operation) return null
102
+ if (operation.idempotency !== "required") throw new Error("Persisted mail delivery operation idempotency must be required")
103
+ if (typeof operation.id !== "string" || operation.id.length === 0) throw new Error("Persisted mail delivery operation requires an id")
104
+ if (typeof operation.payloadDigest !== "string" || !operation.payloadDigest.startsWith("sha256:v1:")) throw new Error("Persisted mail delivery operation requires a versioned payload digest")
105
+ if (typeof operation.providerKind !== "string" || operation.providerKind.length === 0) throw new Error("Persisted mail delivery operation requires a provider kind")
106
+ if (!Number.isSafeInteger(operation.providerRetentionMs) || operation.providerRetentionMs <= 0) throw new Error("Persisted mail delivery operation requires a positive retention")
107
+
108
+ return operation
109
+ }
110
+
111
+ /**
112
+ * Extracts a built-in mail operation from generic job arguments.
113
+ * @param {string} jobName - Job class name.
114
+ * @param {Array<ReturnType<typeof JSON.parse>>} args - Job arguments.
115
+ * @returns {{operation: import("./index.js").MailerDeliveryOperation, payload: import("./index.js").MailerDeliveryPayload} | null} - Mail operation input.
116
+ */
117
+ export function mailDeliveryOperationForJob(jobName, args) {
118
+ if (jobName !== MAIL_DELIVERY_JOB_NAME || !args[0] || typeof args[0] !== "object" || Array.isArray(args[0])) return null
119
+
120
+ const payload = /** @type {import("./index.js").MailerDeliveryPayload} */ (args[0])
121
+ const operation = deliveryOperationFromPayload(payload)
122
+
123
+ return operation ? {operation, payload} : null
124
+ }
125
+
126
+ /**
127
+ * Fixed-size primary key for a potentially long operation id.
128
+ * @param {string} operationId - Operation id.
129
+ * @returns {string} - SHA-256 operation key.
130
+ */
131
+ export function mailDeliveryOperationKey(operationId) {
132
+ return createHash("sha256").update(`velocious-mail-delivery-operation:${operationId}`).digest("hex")
133
+ }
134
+
135
+ /**
136
+ * Validates provider compatibility and payload integrity before an attempt.
137
+ * @param {object} args - Validation input.
138
+ * @param {import("./index.js").MailerDeliveryIdempotencyCapability} args.capability - Current backend capability.
139
+ * @param {import("./index.js").MailerDeliveryPayload} args.payload - Persisted payload.
140
+ * @returns {import("./index.js").MailerDeliveryOperation} - Persisted operation.
141
+ */
142
+ export function validateAttemptPayload({capability, payload}) {
143
+ const operation = deliveryOperationFromPayload(payload)
144
+
145
+ if (!operation) throw new Error("Expected a persisted mail delivery operation")
146
+ if (operation.providerKind !== capability.providerKind || operation.providerRetentionMs !== capability.retentionMs) {
147
+ throw VelociousError.safe("The configured mailer backend no longer matches the required delivery operation provider.", {
148
+ code: "mail-delivery-idempotency-provider-mismatch"
149
+ })
150
+ }
151
+
152
+ const currentDigest = mailDeliveryPayloadDigest({operationId: operation.id, payload})
153
+
154
+ if (currentDigest !== operation.payloadDigest) {
155
+ throw VelociousError.safe("The persisted mail delivery payload digest does not match its required operation.", {
156
+ code: "mail-delivery-idempotency-payload-mismatch"
157
+ })
158
+ }
159
+
160
+ return operation
161
+ }
162
+
163
+ /**
164
+ * Validates the public operation shape without accepting future semantics silently.
165
+ * @param {import("./index.js").MailerDeliveryOperationRequest} deliveryOperation - Public operation.
166
+ * @returns {void}
167
+ */
168
+ function validateDeliveryOperationRequest(deliveryOperation) {
169
+ if (!deliveryOperation || typeof deliveryOperation !== "object" || Array.isArray(deliveryOperation)) {
170
+ throw VelociousError.safe("deliveryOperation must be an object.", {code: "mail-delivery-operation-invalid"})
171
+ }
172
+ if (typeof deliveryOperation.id !== "string" || deliveryOperation.id.length === 0) {
173
+ throw VelociousError.safe("deliveryOperation.id must be a non-empty string.", {code: "mail-delivery-operation-invalid"})
174
+ }
175
+ if (deliveryOperation.idempotency !== "required") {
176
+ throw VelociousError.safe('deliveryOperation.idempotency must be "required".', {code: "mail-delivery-operation-invalid"})
177
+ }
178
+
179
+ const keys = Object.keys(deliveryOperation)
180
+
181
+ if (keys.some((key) => key !== "id" && key !== "idempotency")) {
182
+ throw VelociousError.safe("deliveryOperation contains unsupported fields.", {code: "mail-delivery-operation-invalid"})
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Canonicalizes case-insensitive custom headers without exposing values.
188
+ * @param {Record<string, string> | undefined} headers - Custom headers.
189
+ * @returns {Array<[string, string]>} - Sorted header pairs.
190
+ */
191
+ function canonicalHeaders(headers) {
192
+ if (!headers) return []
193
+
194
+ const pairs = Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value])
195
+ const names = new Set()
196
+
197
+ for (const [name] of pairs) {
198
+ if (names.has(name)) {
199
+ throw VelociousError.safe("Mail headers contain duplicate case-insensitive names.", {code: "mail-delivery-headers-invalid"})
200
+ }
201
+ names.add(name)
202
+ }
203
+
204
+ return /** @type {Array<[string, string]>} */ (pairs.sort(([left], [right]) => left.localeCompare(right)))
205
+ }
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import restArgsError from "../utils/rest-args-error.js"
4
+
3
5
  /**
4
6
  * Represents a prepared mail delivery.
5
7
  */
@@ -52,19 +54,22 @@ export default class MailerDelivery {
52
54
 
53
55
  /**
54
56
  * Runs deliver later.
57
+ * @param {import("./index.js").MailerDeliveryLaterOptions} [options] - Delivery execution options.
55
58
  * @returns {Promise<string | import("./index.js").MailerDeliveryPayload | null>} - Job id or payload in test mode.
56
59
  */
57
- async deliverLater() {
60
+ async deliverLater({deliveryOperation, ...restArgs} = {}) {
61
+ restArgsError(restArgs)
58
62
  const payload = await this.buildPayload()
59
63
 
60
- return await this.mailer._enqueuePayload(payload)
64
+ return await this.mailer._enqueuePayload(payload, {deliveryOperation})
61
65
  }
62
66
 
63
67
  /**
64
68
  * Runs deliver laver.
69
+ * @param {import("./index.js").MailerDeliveryLaterOptions} [options] - Delivery execution options.
65
70
  * @returns {Promise<string | import("./index.js").MailerDeliveryPayload | null>} - Job id or payload in test mode.
66
71
  */
67
- async deliverLaver() {
68
- return await this.deliverLater()
72
+ async deliverLaver(options) {
73
+ return await this.deliverLater(options)
69
74
  }
70
75
  }
@@ -1,6 +1,10 @@
1
1
  // @ts-check
2
2
 
3
- /** @typedef {{to: ReturnType<typeof JSON.parse>, subject: string, from?: ReturnType<typeof JSON.parse>, cc?: ReturnType<typeof JSON.parse>, bcc?: ReturnType<typeof JSON.parse>, replyTo?: ReturnType<typeof JSON.parse>, headers?: Record<string, string>, html: string, mailer: string, action: string}} MailerDeliveryPayload */
3
+ /** @typedef {{id: string, idempotency: "required"}} MailerDeliveryOperationRequest */
4
+ /** @typedef {{id: string, idempotency: "required", payloadDigest: string, providerKind: string, providerRetentionMs: number}} MailerDeliveryOperation */
5
+ /** @typedef {{providerKind: string, retentionMs: number}} MailerDeliveryIdempotencyCapability */
6
+ /** @typedef {{deliveryOperation?: MailerDeliveryOperationRequest}} MailerDeliveryLaterOptions */
7
+ /** @typedef {{to: ReturnType<typeof JSON.parse>, subject: string, from?: ReturnType<typeof JSON.parse>, cc?: ReturnType<typeof JSON.parse>, bcc?: ReturnType<typeof JSON.parse>, replyTo?: ReturnType<typeof JSON.parse>, headers?: Record<string, string>, html: string, mailer: string, action: string, deliveryOperation?: MailerDeliveryOperation}} MailerDeliveryPayload */
4
8
 
5
9
  import {
6
10
  clearDeliveries,
package/build/mailer.js CHANGED
@@ -1,6 +1,10 @@
1
1
  // @ts-check
2
2
 
3
- /** @typedef {{to: ReturnType<typeof JSON.parse>, subject: string, from?: ReturnType<typeof JSON.parse>, cc?: ReturnType<typeof JSON.parse>, bcc?: ReturnType<typeof JSON.parse>, replyTo?: ReturnType<typeof JSON.parse>, headers?: Record<string, string>, html: string, mailer: string, action: string}} MailerDeliveryPayload */
3
+ /** @typedef {import("./mailer/index.js").MailerDeliveryOperationRequest} MailerDeliveryOperationRequest */
4
+ /** @typedef {import("./mailer/index.js").MailerDeliveryOperation} MailerDeliveryOperation */
5
+ /** @typedef {import("./mailer/index.js").MailerDeliveryIdempotencyCapability} MailerDeliveryIdempotencyCapability */
6
+ /** @typedef {import("./mailer/index.js").MailerDeliveryLaterOptions} MailerDeliveryLaterOptions */
7
+ /** @typedef {import("./mailer/index.js").MailerDeliveryPayload} MailerDeliveryPayload */
4
8
 
5
9
  export {
6
10
  VelociousMailerBase,
@@ -12,4 +16,5 @@ export {
12
16
  setDeliveryHandler
13
17
  } from "./mailer/index.js"
14
18
  export {default as SmtpMailerBackend} from "./mailer/backends/smtp.js"
19
+ export {default as ResendSmtpMailerBackend} from "./mailer/backends/resend-smtp.js"
15
20
  export {default} from "./mailer/index.js"
@@ -23,9 +23,10 @@ export default class PooledRunnerBrokerIdentity {
23
23
  /**
24
24
  * Prepares one identity, sharing an in-flight same-identity rotation.
25
25
  * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
26
+ * @param {boolean} [admissionReserved] - Whether the caller already reserved its active-user slot.
26
27
  * @returns {Promise<void>} - Resolves after stale connections close.
27
28
  */
28
- prepare(config: import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig): Promise<void>;
29
+ prepare(config: import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig, admissionReserved?: boolean): Promise<void>;
29
30
  /**
30
31
  * Runs work while preventing a different identity from replacing its connections.
31
32
  * @template T
@@ -34,6 +35,14 @@ export default class PooledRunnerBrokerIdentity {
34
35
  * @returns {Promise<T>} - Job result.
35
36
  */
36
37
  run<T>(config: import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig, callback: () => Promise<T>): Promise<T>;
38
+ /**
39
+ * Atomically prepares an attempt identity and reserves its active user. Without
40
+ * this admission turn, another capability can rotate connections after `prepare`
41
+ * resolves but before `run` increments `activeUsers`.
42
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
43
+ * @returns {Promise<void>} - Resolves after admission is reserved.
44
+ */
45
+ admit(config: import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig): Promise<void>;
37
46
  /**
38
47
  * Rotates retained connection state to an identity.
39
48
  * @param {string} identity - Target identity.
@@ -1 +1 @@
1
- {"version":3,"file":"pooled-runner-broker-identity.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/pooled-runner-broker-identity.js"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,OAAO,0BAA0B;IAMtC,gBAAgB,QAHa,OAAO,CAAC,IAAI,CAAC;IAI/C,iCAAiC;IAC5B,cAAc,EADR,MAAM,GAAG,SAAS;IAE7B,qEAAqE;IAChE,OAAO,EADD;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;KAAC,GAAG,SAAS;IAE5D,WAAW;IAVlB;;;OAGG;IACH,YAAY,EAAC,gBAAgB,EAAC,EAFnB;QAAC,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAEnB,EAO7B;IAED;;;OAGG;IACH,OAAO,IAFM,MAAM,GAAG,SAAS,CAES;IAExC;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAHT,OAAO,+CAA+C,EAAE,gCAG/C,GAFP,OAAO,CAAC,IAAI,CAAC,CAsBzB;IAED;;;;;;OAMG;IACG,GAAG,CALI,CAAC,EAKJ,MAAM,EAJL,OAAO,+CAA+C,EAAE,gCAInD,EAAE,QAAQ,EAHf,MAAM,OAAO,CAAC,CAAC,CAGA,GAFb,OAAO,CAAC,CAAC,CAAC,CAUtB;IAED;;;;OAIG;IACG,MAAM,CAAC,QAAQ,EAHV,MAGU,GAFR,OAAO,CAAC,IAAI,CAAC,CAKzB;CACF"}
1
+ {"version":3,"file":"pooled-runner-broker-identity.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/pooled-runner-broker-identity.js"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,OAAO,0BAA0B;IAMtC,gBAAgB,QAHa,OAAO,CAAC,IAAI,CAAC;IAI/C,iCAAiC;IAC5B,cAAc,EADR,MAAM,GAAG,SAAS;IAE7B,qEAAqE;IAChE,OAAO,EADD;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;KAAC,GAAG,SAAS;IAE5D,WAAW;IAVlB;;;OAGG;IACH,YAAY,EAAC,gBAAgB,EAAC,EAFnB;QAAC,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAEnB,EAO7B;IAED;;;OAGG;IACH,OAAO,IAFM,MAAM,GAAG,SAAS,CAES;IAExC;;;;;OAKG;IACG,OAAO,CAAC,MAAM,EAJT,OAAO,+CAA+C,EAAE,gCAI/C,EAAE,iBAAiB,AAHpC,CACA,EADQ,OAGoC,GAFlC,OAAO,CAAC,IAAI,CAAC,CAuBzB;IAED;;;;;;OAMG;IACG,GAAG,CALI,CAAC,EAKJ,MAAM,EAJL,OAAO,+CAA+C,EAAE,gCAInD,EAAE,QAAQ,EAHf,MAAM,OAAO,CAAC,CAAC,CAGA,GAFb,OAAO,CAAC,CAAC,CAAC,CAStB;IAED;;;;;;OAMG;IACG,KAAK,CAAC,MAAM,EAHP,OAAO,+CAA+C,EAAE,gCAGjD,GAFL,OAAO,CAAC,IAAI,CAAC,CAUzB;IAED;;;;OAIG;IACG,MAAM,CAAC,QAAQ,EAHV,MAGU,GAFR,OAAO,CAAC,IAAI,CAAC,CAKzB;CACF"}
@@ -20,9 +20,10 @@ export default class PooledRunnerBrokerIdentity {
20
20
  /**
21
21
  * Prepares one identity, sharing an in-flight same-identity rotation.
22
22
  * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
23
+ * @param {boolean} [admissionReserved] - Whether the caller already reserved its active-user slot.
23
24
  * @returns {Promise<void>} - Resolves after stale connections close.
24
25
  */
25
- async prepare(config) {
26
+ async prepare(config, admissionReserved = false) {
26
27
  const identity = JSON.stringify(config);
27
28
  if (this.pending) {
28
29
  if (this.pending.identity !== identity)
@@ -31,7 +32,8 @@ export default class PooledRunnerBrokerIdentity {
31
32
  }
32
33
  if (this.activeIdentity === identity)
33
34
  return;
34
- if (this.activeUsers > 0)
35
+ const otherActiveUsers = this.activeUsers - (admissionReserved ? 1 : 0);
36
+ if (otherActiveUsers > 0)
35
37
  throw new Error("Pooled runner cannot mix shared transaction broker capabilities concurrently");
36
38
  if (this.activeIdentity === undefined) {
37
39
  this.activeIdentity = identity;
@@ -54,8 +56,7 @@ export default class PooledRunnerBrokerIdentity {
54
56
  * @returns {Promise<T>} - Job result.
55
57
  */
56
58
  async run(config, callback) {
57
- await this.prepare(config);
58
- this.activeUsers++;
59
+ await this.admit(config);
59
60
  try {
60
61
  return await callback();
61
62
  }
@@ -63,6 +64,23 @@ export default class PooledRunnerBrokerIdentity {
63
64
  this.activeUsers--;
64
65
  }
65
66
  }
67
+ /**
68
+ * Atomically prepares an attempt identity and reserves its active user. Without
69
+ * this admission turn, another capability can rotate connections after `prepare`
70
+ * resolves but before `run` increments `activeUsers`.
71
+ * @param {import("../testing/shared-transaction-proxy-driver.js").SharedTransactionBrokerJobConfig} config - Dispatch configuration.
72
+ * @returns {Promise<void>} - Resolves after admission is reserved.
73
+ */
74
+ async admit(config) {
75
+ this.activeUsers++;
76
+ try {
77
+ await this.prepare(config, true);
78
+ }
79
+ catch (error) {
80
+ this.activeUsers--;
81
+ throw error;
82
+ }
83
+ }
66
84
  /**
67
85
  * Rotates retained connection state to an identity.
68
86
  * @param {string} identity - Target identity.
@@ -73,4 +91,4 @@ export default class PooledRunnerBrokerIdentity {
73
91
  this.activeIdentity = identity;
74
92
  }
75
93
  }
76
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9vbGVkLXJ1bm5lci1icm9rZXItaWRlbnRpdHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmFja2dyb3VuZC1qb2JzL3Bvb2xlZC1ydW5uZXItYnJva2VyLWlkZW50aXR5LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixNQUFNLENBQUMsT0FBTyxPQUFPLDBCQUEwQjtJQUM3Qzs7O09BR0c7SUFDSCxZQUFZLEVBQUMsZ0JBQWdCLEVBQUM7UUFDNUIsSUFBSSxDQUFDLGdCQUFnQixHQUFHLGdCQUFnQixDQUFBO1FBQ3hDLGlDQUFpQztRQUNqQyxJQUFJLENBQUMsY0FBYyxHQUFHLFNBQVMsQ0FBQTtRQUMvQixxRUFBcUU7UUFDckUsSUFBSSxDQUFDLE9BQU8sR0FBRyxTQUFTLENBQUE7UUFDeEIsSUFBSSxDQUFDLFdBQVcsR0FBRyxDQUFDLENBQUE7SUFDdEIsQ0FBQztJQUVEOzs7T0FHRztJQUNILE9BQU8sS0FBSyxPQUFPLElBQUksQ0FBQyxjQUFjLENBQUEsQ0FBQyxDQUFDO0lBRXhDOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQU07UUFDbEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUN2QyxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNqQixJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxLQUFLLFFBQVE7Z0JBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyw4RUFBOEUsQ0FBQyxDQUFBO1lBQ3ZJLE9BQU8sTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQTtRQUNuQyxDQUFDO1FBQ0QsSUFBSSxJQUFJLENBQUMsY0FBYyxLQUFLLFFBQVE7WUFBRSxPQUFNO1FBQzVDLElBQUksSUFBSSxDQUFDLFdBQVcsR0FBRyxDQUFDO1lBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyw4RUFBOEUsQ0FBQyxDQUFBO1FBQ3pILElBQUksSUFBSSxDQUFDLGNBQWMsS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUN0QyxJQUFJLENBQUMsY0FBYyxHQUFHLFFBQVEsQ0FBQTtZQUM5QixPQUFNO1FBQ1IsQ0FBQztRQUVELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUE7UUFDckMsSUFBSSxDQUFDLE9BQU8sR0FBRyxFQUFDLFFBQVEsRUFBRSxPQUFPLEVBQUMsQ0FBQTtRQUNsQyxJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sQ0FBQTtRQUNmLENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksQ0FBQyxPQUFPLEdBQUcsU0FBUyxDQUFBO1FBQzFCLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0gsS0FBSyxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsUUFBUTtRQUN4QixNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDMUIsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFBO1FBQ2xCLElBQUksQ0FBQztZQUNILE9BQU8sTUFBTSxRQUFRLEVBQUUsQ0FBQTtRQUN6QixDQUFDO2dCQUFTLENBQUM7WUFDVCxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUE7UUFDcEIsQ0FBQztJQUNILENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsS0FBSyxDQUFDLE1BQU0sQ0FBQyxRQUFRO1FBQ25CLE1BQU0sSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUE7UUFDN0IsSUFBSSxDQUFDLGNBQWMsR0FBRyxRQUFRLENBQUE7SUFDaEMsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFBvb2xlZFJ1bm5lckJyb2tlcklkZW50aXR5IHtcbiAgLyoqXG4gICAqIENyZWF0ZXMgYSBwb29sZWQgcnVubmVyIGlkZW50aXR5IGNvb3JkaW5hdG9yLlxuICAgKiBAcGFyYW0ge3tjbG9zZUNvbm5lY3Rpb25zOiAoKSA9PiBQcm9taXNlPHZvaWQ+fX0gYXJncyAtIENvbm5lY3Rpb24gY2xlYW51cCBob29rLlxuICAgKi9cbiAgY29uc3RydWN0b3Ioe2Nsb3NlQ29ubmVjdGlvbnN9KSB7XG4gICAgdGhpcy5jbG9zZUNvbm5lY3Rpb25zID0gY2xvc2VDb25uZWN0aW9uc1xuICAgIC8qKiBAdHlwZSB7c3RyaW5nIHwgdW5kZWZpbmVkfSAqL1xuICAgIHRoaXMuYWN0aXZlSWRlbnRpdHkgPSB1bmRlZmluZWRcbiAgICAvKiogQHR5cGUge3tpZGVudGl0eTogc3RyaW5nLCBwcm9taXNlOiBQcm9taXNlPHZvaWQ+fSB8IHVuZGVmaW5lZH0gKi9cbiAgICB0aGlzLnBlbmRpbmcgPSB1bmRlZmluZWRcbiAgICB0aGlzLmFjdGl2ZVVzZXJzID0gMFxuICB9XG5cbiAgLyoqXG4gICAqIEdldHMgdGhlIGN1cnJlbnQgcHJlcGFyZWQgaWRlbnRpdHkuXG4gICAqIEByZXR1cm5zIHtzdHJpbmcgfCB1bmRlZmluZWR9IC0gQ3VycmVudCBwcmVwYXJlZCBpZGVudGl0eS5cbiAgICovXG4gIGN1cnJlbnQoKSB7IHJldHVybiB0aGlzLmFjdGl2ZUlkZW50aXR5IH1cblxuICAvKipcbiAgICogUHJlcGFyZXMgb25lIGlkZW50aXR5LCBzaGFyaW5nIGFuIGluLWZsaWdodCBzYW1lLWlkZW50aXR5IHJvdGF0aW9uLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uL3Rlc3Rpbmcvc2hhcmVkLXRyYW5zYWN0aW9uLXByb3h5LWRyaXZlci5qc1wiKS5TaGFyZWRUcmFuc2FjdGlvbkJyb2tlckpvYkNvbmZpZ30gY29uZmlnIC0gRGlzcGF0Y2ggY29uZmlndXJhdGlvbi5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgc3RhbGUgY29ubmVjdGlvbnMgY2xvc2UuXG4gICAqL1xuICBhc3luYyBwcmVwYXJlKGNvbmZpZykge1xuICAgIGNvbnN0IGlkZW50aXR5ID0gSlNPTi5zdHJpbmdpZnkoY29uZmlnKVxuICAgIGlmICh0aGlzLnBlbmRpbmcpIHtcbiAgICAgIGlmICh0aGlzLnBlbmRpbmcuaWRlbnRpdHkgIT09IGlkZW50aXR5KSB0aHJvdyBuZXcgRXJyb3IoXCJQb29sZWQgcnVubmVyIGNhbm5vdCBtaXggc2hhcmVkIHRyYW5zYWN0aW9uIGJyb2tlciBjYXBhYmlsaXRpZXMgY29uY3VycmVudGx5XCIpXG4gICAgICByZXR1cm4gYXdhaXQgdGhpcy5wZW5kaW5nLnByb21pc2VcbiAgICB9XG4gICAgaWYgKHRoaXMuYWN0aXZlSWRlbnRpdHkgPT09IGlkZW50aXR5KSByZXR1cm5cbiAgICBpZiAodGhpcy5hY3RpdmVVc2VycyA+IDApIHRocm93IG5ldyBFcnJvcihcIlBvb2xlZCBydW5uZXIgY2Fubm90IG1peCBzaGFyZWQgdHJhbnNhY3Rpb24gYnJva2VyIGNhcGFiaWxpdGllcyBjb25jdXJyZW50bHlcIilcbiAgICBpZiAodGhpcy5hY3RpdmVJZGVudGl0eSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aGlzLmFjdGl2ZUlkZW50aXR5ID0gaWRlbnRpdHlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGNvbnN0IHByb21pc2UgPSB0aGlzLnJvdGF0ZShpZGVudGl0eSlcbiAgICB0aGlzLnBlbmRpbmcgPSB7aWRlbnRpdHksIHByb21pc2V9XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHByb21pc2VcbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5wZW5kaW5nID0gdW5kZWZpbmVkXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgd29yayB3aGlsZSBwcmV2ZW50aW5nIGEgZGlmZmVyZW50IGlkZW50aXR5IGZyb20gcmVwbGFjaW5nIGl0cyBjb25uZWN0aW9ucy5cbiAgICogQHRlbXBsYXRlIFRcbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi90ZXN0aW5nL3NoYXJlZC10cmFuc2FjdGlvbi1wcm94eS1kcml2ZXIuanNcIikuU2hhcmVkVHJhbnNhY3Rpb25Ccm9rZXJKb2JDb25maWd9IGNvbmZpZyAtIERpc3BhdGNoIGNvbmZpZ3VyYXRpb24uXG4gICAqIEBwYXJhbSB7KCkgPT4gUHJvbWlzZTxUPn0gY2FsbGJhY2sgLSBKb2IgY2FsbGJhY2suXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFQ+fSAtIEpvYiByZXN1bHQuXG4gICAqL1xuICBhc3luYyBydW4oY29uZmlnLCBjYWxsYmFjaykge1xuICAgIGF3YWl0IHRoaXMucHJlcGFyZShjb25maWcpXG4gICAgdGhpcy5hY3RpdmVVc2VycysrXG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBhd2FpdCBjYWxsYmFjaygpXG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIHRoaXMuYWN0aXZlVXNlcnMtLVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSb3RhdGVzIHJldGFpbmVkIGNvbm5lY3Rpb24gc3RhdGUgdG8gYW4gaWRlbnRpdHkuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBpZGVudGl0eSAtIFRhcmdldCBpZGVudGl0eS5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgcm90YXRpb24uXG4gICAqL1xuICBhc3luYyByb3RhdGUoaWRlbnRpdHkpIHtcbiAgICBhd2FpdCB0aGlzLmNsb3NlQ29ubmVjdGlvbnMoKVxuICAgIHRoaXMuYWN0aXZlSWRlbnRpdHkgPSBpZGVudGl0eVxuICB9XG59XG4iXX0=
94
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9vbGVkLXJ1bm5lci1icm9rZXItaWRlbnRpdHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmFja2dyb3VuZC1qb2JzL3Bvb2xlZC1ydW5uZXItYnJva2VyLWlkZW50aXR5LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixNQUFNLENBQUMsT0FBTyxPQUFPLDBCQUEwQjtJQUM3Qzs7O09BR0c7SUFDSCxZQUFZLEVBQUMsZ0JBQWdCLEVBQUM7UUFDNUIsSUFBSSxDQUFDLGdCQUFnQixHQUFHLGdCQUFnQixDQUFBO1FBQ3hDLGlDQUFpQztRQUNqQyxJQUFJLENBQUMsY0FBYyxHQUFHLFNBQVMsQ0FBQTtRQUMvQixxRUFBcUU7UUFDckUsSUFBSSxDQUFDLE9BQU8sR0FBRyxTQUFTLENBQUE7UUFDeEIsSUFBSSxDQUFDLFdBQVcsR0FBRyxDQUFDLENBQUE7SUFDdEIsQ0FBQztJQUVEOzs7T0FHRztJQUNILE9BQU8sS0FBSyxPQUFPLElBQUksQ0FBQyxjQUFjLENBQUEsQ0FBQyxDQUFDO0lBRXhDOzs7OztPQUtHO0lBQ0gsS0FBSyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsaUJBQWlCLEdBQUcsS0FBSztRQUM3QyxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFBO1FBQ3ZDLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ2pCLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLEtBQUssUUFBUTtnQkFBRSxNQUFNLElBQUksS0FBSyxDQUFDLDhFQUE4RSxDQUFDLENBQUE7WUFDdkksT0FBTyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFBO1FBQ25DLENBQUM7UUFDRCxJQUFJLElBQUksQ0FBQyxjQUFjLEtBQUssUUFBUTtZQUFFLE9BQU07UUFDNUMsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsV0FBVyxHQUFHLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDdkUsSUFBSSxnQkFBZ0IsR0FBRyxDQUFDO1lBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyw4RUFBOEUsQ0FBQyxDQUFBO1FBQ3pILElBQUksSUFBSSxDQUFDLGNBQWMsS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUN0QyxJQUFJLENBQUMsY0FBYyxHQUFHLFFBQVEsQ0FBQTtZQUM5QixPQUFNO1FBQ1IsQ0FBQztRQUVELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUE7UUFDckMsSUFBSSxDQUFDLE9BQU8sR0FBRyxFQUFDLFFBQVEsRUFBRSxPQUFPLEVBQUMsQ0FBQTtRQUNsQyxJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sQ0FBQTtRQUNmLENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksQ0FBQyxPQUFPLEdBQUcsU0FBUyxDQUFBO1FBQzFCLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0gsS0FBSyxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsUUFBUTtRQUN4QixNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDeEIsSUFBSSxDQUFDO1lBQ0gsT0FBTyxNQUFNLFFBQVEsRUFBRSxDQUFBO1FBQ3pCLENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQTtRQUNwQixDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILEtBQUssQ0FBQyxLQUFLLENBQUMsTUFBTTtRQUNoQixJQUFJLENBQUMsV0FBVyxFQUFFLENBQUE7UUFDbEIsSUFBSSxDQUFDO1lBQ0gsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQTtRQUNsQyxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQTtZQUNsQixNQUFNLEtBQUssQ0FBQTtRQUNiLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILEtBQUssQ0FBQyxNQUFNLENBQUMsUUFBUTtRQUNuQixNQUFNLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFBO1FBQzdCLElBQUksQ0FBQyxjQUFjLEdBQUcsUUFBUSxDQUFBO0lBQ2hDLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBQb29sZWRSdW5uZXJCcm9rZXJJZGVudGl0eSB7XG4gIC8qKlxuICAgKiBDcmVhdGVzIGEgcG9vbGVkIHJ1bm5lciBpZGVudGl0eSBjb29yZGluYXRvci5cbiAgICogQHBhcmFtIHt7Y2xvc2VDb25uZWN0aW9uczogKCkgPT4gUHJvbWlzZTx2b2lkPn19IGFyZ3MgLSBDb25uZWN0aW9uIGNsZWFudXAgaG9vay5cbiAgICovXG4gIGNvbnN0cnVjdG9yKHtjbG9zZUNvbm5lY3Rpb25zfSkge1xuICAgIHRoaXMuY2xvc2VDb25uZWN0aW9ucyA9IGNsb3NlQ29ubmVjdGlvbnNcbiAgICAvKiogQHR5cGUge3N0cmluZyB8IHVuZGVmaW5lZH0gKi9cbiAgICB0aGlzLmFjdGl2ZUlkZW50aXR5ID0gdW5kZWZpbmVkXG4gICAgLyoqIEB0eXBlIHt7aWRlbnRpdHk6IHN0cmluZywgcHJvbWlzZTogUHJvbWlzZTx2b2lkPn0gfCB1bmRlZmluZWR9ICovXG4gICAgdGhpcy5wZW5kaW5nID0gdW5kZWZpbmVkXG4gICAgdGhpcy5hY3RpdmVVc2VycyA9IDBcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXRzIHRoZSBjdXJyZW50IHByZXBhcmVkIGlkZW50aXR5LlxuICAgKiBAcmV0dXJucyB7c3RyaW5nIHwgdW5kZWZpbmVkfSAtIEN1cnJlbnQgcHJlcGFyZWQgaWRlbnRpdHkuXG4gICAqL1xuICBjdXJyZW50KCkgeyByZXR1cm4gdGhpcy5hY3RpdmVJZGVudGl0eSB9XG5cbiAgLyoqXG4gICAqIFByZXBhcmVzIG9uZSBpZGVudGl0eSwgc2hhcmluZyBhbiBpbi1mbGlnaHQgc2FtZS1pZGVudGl0eSByb3RhdGlvbi5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi90ZXN0aW5nL3NoYXJlZC10cmFuc2FjdGlvbi1wcm94eS1kcml2ZXIuanNcIikuU2hhcmVkVHJhbnNhY3Rpb25Ccm9rZXJKb2JDb25maWd9IGNvbmZpZyAtIERpc3BhdGNoIGNvbmZpZ3VyYXRpb24uXG4gICAqIEBwYXJhbSB7Ym9vbGVhbn0gW2FkbWlzc2lvblJlc2VydmVkXSAtIFdoZXRoZXIgdGhlIGNhbGxlciBhbHJlYWR5IHJlc2VydmVkIGl0cyBhY3RpdmUtdXNlciBzbG90LlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyBhZnRlciBzdGFsZSBjb25uZWN0aW9ucyBjbG9zZS5cbiAgICovXG4gIGFzeW5jIHByZXBhcmUoY29uZmlnLCBhZG1pc3Npb25SZXNlcnZlZCA9IGZhbHNlKSB7XG4gICAgY29uc3QgaWRlbnRpdHkgPSBKU09OLnN0cmluZ2lmeShjb25maWcpXG4gICAgaWYgKHRoaXMucGVuZGluZykge1xuICAgICAgaWYgKHRoaXMucGVuZGluZy5pZGVudGl0eSAhPT0gaWRlbnRpdHkpIHRocm93IG5ldyBFcnJvcihcIlBvb2xlZCBydW5uZXIgY2Fubm90IG1peCBzaGFyZWQgdHJhbnNhY3Rpb24gYnJva2VyIGNhcGFiaWxpdGllcyBjb25jdXJyZW50bHlcIilcbiAgICAgIHJldHVybiBhd2FpdCB0aGlzLnBlbmRpbmcucHJvbWlzZVxuICAgIH1cbiAgICBpZiAodGhpcy5hY3RpdmVJZGVudGl0eSA9PT0gaWRlbnRpdHkpIHJldHVyblxuICAgIGNvbnN0IG90aGVyQWN0aXZlVXNlcnMgPSB0aGlzLmFjdGl2ZVVzZXJzIC0gKGFkbWlzc2lvblJlc2VydmVkID8gMSA6IDApXG4gICAgaWYgKG90aGVyQWN0aXZlVXNlcnMgPiAwKSB0aHJvdyBuZXcgRXJyb3IoXCJQb29sZWQgcnVubmVyIGNhbm5vdCBtaXggc2hhcmVkIHRyYW5zYWN0aW9uIGJyb2tlciBjYXBhYmlsaXRpZXMgY29uY3VycmVudGx5XCIpXG4gICAgaWYgKHRoaXMuYWN0aXZlSWRlbnRpdHkgPT09IHVuZGVmaW5lZCkge1xuICAgICAgdGhpcy5hY3RpdmVJZGVudGl0eSA9IGlkZW50aXR5XG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBjb25zdCBwcm9taXNlID0gdGhpcy5yb3RhdGUoaWRlbnRpdHkpXG4gICAgdGhpcy5wZW5kaW5nID0ge2lkZW50aXR5LCBwcm9taXNlfVxuICAgIHRyeSB7XG4gICAgICBhd2FpdCBwcm9taXNlXG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIHRoaXMucGVuZGluZyA9IHVuZGVmaW5lZFxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIHdvcmsgd2hpbGUgcHJldmVudGluZyBhIGRpZmZlcmVudCBpZGVudGl0eSBmcm9tIHJlcGxhY2luZyBpdHMgY29ubmVjdGlvbnMuXG4gICAqIEB0ZW1wbGF0ZSBUXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vdGVzdGluZy9zaGFyZWQtdHJhbnNhY3Rpb24tcHJveHktZHJpdmVyLmpzXCIpLlNoYXJlZFRyYW5zYWN0aW9uQnJva2VySm9iQ29uZmlnfSBjb25maWcgLSBEaXNwYXRjaCBjb25maWd1cmF0aW9uLlxuICAgKiBAcGFyYW0geygpID0+IFByb21pc2U8VD59IGNhbGxiYWNrIC0gSm9iIGNhbGxiYWNrLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxUPn0gLSBKb2IgcmVzdWx0LlxuICAgKi9cbiAgYXN5bmMgcnVuKGNvbmZpZywgY2FsbGJhY2spIHtcbiAgICBhd2FpdCB0aGlzLmFkbWl0KGNvbmZpZylcbiAgICB0cnkge1xuICAgICAgcmV0dXJuIGF3YWl0IGNhbGxiYWNrKClcbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5hY3RpdmVVc2Vycy0tXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEF0b21pY2FsbHkgcHJlcGFyZXMgYW4gYXR0ZW1wdCBpZGVudGl0eSBhbmQgcmVzZXJ2ZXMgaXRzIGFjdGl2ZSB1c2VyLiBXaXRob3V0XG4gICAqIHRoaXMgYWRtaXNzaW9uIHR1cm4sIGFub3RoZXIgY2FwYWJpbGl0eSBjYW4gcm90YXRlIGNvbm5lY3Rpb25zIGFmdGVyIGBwcmVwYXJlYFxuICAgKiByZXNvbHZlcyBidXQgYmVmb3JlIGBydW5gIGluY3JlbWVudHMgYGFjdGl2ZVVzZXJzYC5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi90ZXN0aW5nL3NoYXJlZC10cmFuc2FjdGlvbi1wcm94eS1kcml2ZXIuanNcIikuU2hhcmVkVHJhbnNhY3Rpb25Ccm9rZXJKb2JDb25maWd9IGNvbmZpZyAtIERpc3BhdGNoIGNvbmZpZ3VyYXRpb24uXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIGFmdGVyIGFkbWlzc2lvbiBpcyByZXNlcnZlZC5cbiAgICovXG4gIGFzeW5jIGFkbWl0KGNvbmZpZykge1xuICAgIHRoaXMuYWN0aXZlVXNlcnMrK1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0aGlzLnByZXBhcmUoY29uZmlnLCB0cnVlKVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLmFjdGl2ZVVzZXJzLS1cbiAgICAgIHRocm93IGVycm9yXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJvdGF0ZXMgcmV0YWluZWQgY29ubmVjdGlvbiBzdGF0ZSB0byBhbiBpZGVudGl0eS5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGlkZW50aXR5IC0gVGFyZ2V0IGlkZW50aXR5LlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyBhZnRlciByb3RhdGlvbi5cbiAgICovXG4gIGFzeW5jIHJvdGF0ZShpZGVudGl0eSkge1xuICAgIGF3YWl0IHRoaXMuY2xvc2VDb25uZWN0aW9ucygpXG4gICAgdGhpcy5hY3RpdmVJZGVudGl0eSA9IGlkZW50aXR5XG4gIH1cbn1cbiJdfQ==
@@ -110,6 +110,138 @@ export default class BackgroundJobsStore {
110
110
  args: Array<ReturnType<typeof JSON.parse>>;
111
111
  options?: import("./types.js").BackgroundJobOptions;
112
112
  }): Promise<string>;
113
+ /**
114
+ * Atomically owns one durable idempotency scope and creates its job exactly once.
115
+ * @param {object} args - Enqueue input.
116
+ * @param {Array<ReturnType<typeof JSON.parse>>} args.args - Job arguments.
117
+ * @param {import("./types.js").BackgroundJobOptions} args.options - Job options.
118
+ * @param {PreparedBackgroundJob} args.preparedJob - Normalized job.
119
+ * @returns {Promise<string>} - Stable original job id.
120
+ */
121
+ _enqueueIdempotently({ args, options, preparedJob }: {
122
+ args: Array<ReturnType<typeof JSON.parse>>;
123
+ options: import("./types.js").BackgroundJobOptions;
124
+ preparedJob: PreparedBackgroundJob;
125
+ }): Promise<string>;
126
+ /**
127
+ * Serializes one physical connection locally without taking ownership away
128
+ * from the database uniqueness constraint shared by all processes.
129
+ * @template T
130
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
131
+ * @param {() => Promise<T>} callback - Transaction work.
132
+ * @returns {Promise<T>} - Callback result.
133
+ */
134
+ _idempotentEnqueueTransaction<T>(db: import("../database/drivers/base.js").default, callback: () => Promise<T>): Promise<T>;
135
+ /**
136
+ * Inserts an ownership row, resolving only a database uniqueness race.
137
+ * @param {import("../database/drivers/base.js").default} db - Transaction connection.
138
+ * @param {Record<string, ReturnType<typeof JSON.parse>>} ownership - Ownership row.
139
+ * @returns {Promise<{created: boolean, row: Record<string, ReturnType<typeof JSON.parse>>}>} - Claim result.
140
+ */
141
+ _claimIdempotencyOwnership(db: import("../database/drivers/base.js").default, ownership: Record<string, ReturnType<typeof JSON.parse>>): Promise<{
142
+ created: boolean;
143
+ row: Record<string, ReturnType<typeof JSON.parse>>;
144
+ }>;
145
+ /**
146
+ * Loads one durable enqueue owner.
147
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
148
+ * @param {string} scopeDigest - Fixed-size scope digest.
149
+ * @returns {Promise<Record<string, ReturnType<typeof JSON.parse>> | null>} - Row or null.
150
+ */
151
+ _idempotencyOwnership(db: import("../database/drivers/base.js").default, scopeDigest: string): Promise<Record<string, ReturnType<typeof JSON.parse>> | null>;
152
+ /**
153
+ * Fails closed when a durable key is reused for a different canonical request.
154
+ * @param {object} args - Validation input.
155
+ * @param {Record<string, ReturnType<typeof JSON.parse>>} args.existing - Stored owner.
156
+ * @param {Record<string, ReturnType<typeof JSON.parse>>} args.ownership - Requested owner.
157
+ * @returns {void}
158
+ */
159
+ _validateIdempotencyOwnership({ existing, ownership }: {
160
+ existing: Record<string, ReturnType<typeof JSON.parse>>;
161
+ ownership: Record<string, ReturnType<typeof JSON.parse>>;
162
+ }): void;
163
+ /**
164
+ * Persists the built-in mail operation in the same first-enqueue transaction.
165
+ * @param {import("../database/drivers/base.js").default} db - Transaction connection.
166
+ * @param {object} args - Operation input.
167
+ * @param {number} args.createdAtMs - Creation timestamp.
168
+ * @param {string} args.jobId - Native job id.
169
+ * @param {{operation: import("../mailer/index.js").MailerDeliveryOperation, payload: import("../mailer/index.js").MailerDeliveryPayload} | null} args.mailOperationInput - Mail operation.
170
+ * @returns {Promise<void>} - Resolves after persistence.
171
+ */
172
+ _persistMailDeliveryOperation(db: import("../database/drivers/base.js").default, { createdAtMs, jobId, mailOperationInput }: {
173
+ createdAtMs: number;
174
+ jobId: string;
175
+ mailOperationInput: {
176
+ operation: import("../mailer/index.js").MailerDeliveryOperation;
177
+ payload: import("../mailer/index.js").MailerDeliveryPayload;
178
+ } | null;
179
+ }): Promise<void>;
180
+ /**
181
+ * Validates the durable mail row during an exact generic enqueue replay.
182
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
183
+ * @param {object} args - Validation input.
184
+ * @param {string} args.jobId - Owned job id.
185
+ * @param {{operation: import("../mailer/index.js").MailerDeliveryOperation, payload: import("../mailer/index.js").MailerDeliveryPayload} | null} args.mailOperationInput - Mail operation.
186
+ * @returns {Promise<void>} - Resolves when exact.
187
+ */
188
+ _validateMailDeliveryOperation(db: import("../database/drivers/base.js").default, { jobId, mailOperationInput }: {
189
+ jobId: string;
190
+ mailOperationInput: {
191
+ operation: import("../mailer/index.js").MailerDeliveryOperation;
192
+ payload: import("../mailer/index.js").MailerDeliveryPayload;
193
+ } | null;
194
+ }): Promise<void>;
195
+ /**
196
+ * Loads a durable mail operation.
197
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
198
+ * @param {string} operationKey - Fixed-size operation key.
199
+ * @returns {Promise<Record<string, ReturnType<typeof JSON.parse>> | null>} - Row or null.
200
+ */
201
+ _mailDeliveryOperation(db: import("../database/drivers/base.js").default, operationKey: string): Promise<Record<string, ReturnType<typeof JSON.parse>> | null>;
202
+ /**
203
+ * Compares provider-relevant durable mail operation fields.
204
+ * @param {object} args - Validation input.
205
+ * @param {Record<string, ReturnType<typeof JSON.parse>>} args.existing - Stored row.
206
+ * @param {Record<string, ReturnType<typeof JSON.parse>>} args.requested - Requested row.
207
+ * @returns {void}
208
+ */
209
+ _validateMailDeliveryOperationRow({ existing, requested }: {
210
+ existing: Record<string, ReturnType<typeof JSON.parse>>;
211
+ requested: Record<string, ReturnType<typeof JSON.parse>>;
212
+ }): void;
213
+ /**
214
+ * Canonical request digest excluding generated ids and immediate enqueue time.
215
+ * @param {object} args - Digest input.
216
+ * @param {Array<ReturnType<typeof JSON.parse>>} args.args - Job arguments.
217
+ * @param {import("./types.js").BackgroundJobOptions} args.options - Job options.
218
+ * @param {PreparedBackgroundJob} args.preparedJob - Normalized job.
219
+ * @returns {string} - SHA-256 digest.
220
+ */
221
+ _idempotencyRequestDigest({ args, options, preparedJob }: {
222
+ args: Array<ReturnType<typeof JSON.parse>>;
223
+ options: import("./types.js").BackgroundJobOptions;
224
+ preparedJob: PreparedBackgroundJob;
225
+ }): string;
226
+ /**
227
+ * Fixed-size globally indexed representation of the documented scope tuple.
228
+ * @param {object} args - Scope input.
229
+ * @param {string} args.idempotencyKey - Caller key.
230
+ * @param {string} args.jobName - Job class name.
231
+ * @param {string} args.queue - Queue name.
232
+ * @returns {string} - SHA-256 scope digest.
233
+ */
234
+ _idempotencyScopeDigest({ idempotencyKey, jobName, queue }: {
235
+ idempotencyKey: string;
236
+ jobName: string;
237
+ queue: string;
238
+ }): string;
239
+ /**
240
+ * Validates one caller key.
241
+ * @param {string | undefined} idempotencyKey - Caller key.
242
+ * @returns {string} - Valid key.
243
+ */
244
+ _normalizeIdempotencyKey(idempotencyKey: string | undefined): string;
113
245
  /**
114
246
  * Replaces the queued owner of a stable schedule key with a new one-off job.
115
247
  * A handed-off owner is left running and reported truthfully.
@@ -690,6 +822,18 @@ export default class BackgroundJobsStore {
690
822
  * @returns {Promise<void>} - Resolves when ready.
691
823
  */
692
824
  _ensureScheduleKeysTable(db: import("../database/drivers/base.js").default): Promise<void>;
825
+ /**
826
+ * Ensures durable generic enqueue ownership exists independently of job rows.
827
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
828
+ * @returns {Promise<void>} - Resolves when ready.
829
+ */
830
+ _ensureIdempotencyKeysTable(db: import("../database/drivers/base.js").default): Promise<void>;
831
+ /**
832
+ * Ensures durable provider-backed mail operation state exists independently of jobs.
833
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
834
+ * @returns {Promise<void>} - Resolves when ready.
835
+ */
836
+ _ensureMailDeliveryOperationsTable(db: import("../database/drivers/base.js").default): Promise<void>;
693
837
  /**
694
838
  * Ensures the singleton durable count-revision row exists.
695
839
  * @param {import("../database/drivers/base.js").default} db - Database connection.
@@ -885,6 +1029,16 @@ export default class BackgroundJobsStore {
885
1029
  * @returns {Promise<T>} Callback result.
886
1030
  */
887
1031
  _serializedCountMutation<T>(db: import("../database/drivers/base.js").default, callback: () => Promise<T>): Promise<T>;
1032
+ /**
1033
+ * Serializes transactions that may share one physical connection in this
1034
+ * process. Cross-process ordering remains the responsibility of durable row
1035
+ * locks and unique constraints acquired inside the callback.
1036
+ * @template T
1037
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
1038
+ * @param {() => Promise<T>} callback - Transaction callback.
1039
+ * @returns {Promise<T>} Callback result.
1040
+ */
1041
+ _serializedTransactionMutation<T>(db: import("../database/drivers/base.js").default, callback: () => Promise<T>): Promise<T>;
888
1042
  /**
889
1043
  * Runs should accept report.
890
1044
  * @param {object} args - Options.