velocious 1.0.658 → 1.0.660

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 (63) hide show
  1. package/README.md +11 -1
  2. package/build/background-jobs/adapter.js +15 -0
  3. package/build/background-jobs/client.js +6 -2
  4. package/build/background-jobs/execution-context.js +47 -0
  5. package/build/background-jobs/job-runner.js +5 -2
  6. package/build/background-jobs/job.js +21 -2
  7. package/build/background-jobs/main.js +24 -10
  8. package/build/background-jobs/runtime.js +6 -2
  9. package/build/background-jobs/sql-adapter.js +6 -0
  10. package/build/background-jobs/store.js +289 -48
  11. package/build/background-jobs/types.js +11 -2
  12. package/build/background-jobs/worker.js +10 -7
  13. package/build/frontend-model-controller.js +15 -2
  14. package/build/frontend-models/query.js +43 -0
  15. package/build/src/background-jobs/adapter.d.ts +19 -0
  16. package/build/src/background-jobs/adapter.d.ts.map +1 -1
  17. package/build/src/background-jobs/adapter.js +14 -1
  18. package/build/src/background-jobs/client.d.ts +5 -1
  19. package/build/src/background-jobs/client.d.ts.map +1 -1
  20. package/build/src/background-jobs/client.js +7 -3
  21. package/build/src/background-jobs/execution-context.d.ts +23 -0
  22. package/build/src/background-jobs/execution-context.d.ts.map +1 -0
  23. package/build/src/background-jobs/execution-context.js +42 -0
  24. package/build/src/background-jobs/job-runner.d.ts.map +1 -1
  25. package/build/src/background-jobs/job-runner.js +6 -3
  26. package/build/src/background-jobs/job.d.ts.map +1 -1
  27. package/build/src/background-jobs/job.js +21 -3
  28. package/build/src/background-jobs/main.d.ts +2 -2
  29. package/build/src/background-jobs/main.d.ts.map +1 -1
  30. package/build/src/background-jobs/main.js +26 -11
  31. package/build/src/background-jobs/runtime.d.ts +5 -1
  32. package/build/src/background-jobs/runtime.d.ts.map +1 -1
  33. package/build/src/background-jobs/runtime.js +7 -3
  34. package/build/src/background-jobs/sql-adapter.d.ts +5 -0
  35. package/build/src/background-jobs/sql-adapter.d.ts.map +1 -1
  36. package/build/src/background-jobs/sql-adapter.js +6 -1
  37. package/build/src/background-jobs/store.d.ts +119 -1
  38. package/build/src/background-jobs/store.d.ts.map +1 -1
  39. package/build/src/background-jobs/store.js +268 -47
  40. package/build/src/background-jobs/types.d.ts +33 -2
  41. package/build/src/background-jobs/types.d.ts.map +1 -1
  42. package/build/src/background-jobs/types.js +12 -3
  43. package/build/src/background-jobs/worker.d.ts.map +1 -1
  44. package/build/src/background-jobs/worker.js +11 -8
  45. package/build/src/frontend-model-controller.d.ts.map +1 -1
  46. package/build/src/frontend-model-controller.js +15 -3
  47. package/build/src/frontend-models/query.d.ts +6 -0
  48. package/build/src/frontend-models/query.d.ts.map +1 -1
  49. package/build/src/frontend-models/query.js +40 -1
  50. package/package.json +1 -1
  51. package/src/background-jobs/adapter.js +15 -0
  52. package/src/background-jobs/client.js +6 -2
  53. package/src/background-jobs/execution-context.js +47 -0
  54. package/src/background-jobs/job-runner.js +5 -2
  55. package/src/background-jobs/job.js +21 -2
  56. package/src/background-jobs/main.js +24 -10
  57. package/src/background-jobs/runtime.js +6 -2
  58. package/src/background-jobs/sql-adapter.js +6 -0
  59. package/src/background-jobs/store.js +289 -48
  60. package/src/background-jobs/types.js +11 -2
  61. package/src/background-jobs/worker.js +10 -7
  62. package/src/frontend-model-controller.js +15 -2
  63. package/src/frontend-models/query.js +43 -0
@@ -150,11 +150,15 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
150
150
  * @param {object} args - Options.
151
151
  * @param {import("../configuration.js").default} args.configuration - Configuration.
152
152
  * @param {string} [args.databaseIdentifier] - Database identifier.
153
+ * @param {{now: () => number}} [args.clock] - Injectable persistence clock.
154
+ * @param {(producerProof: import("./types.js").BackgroundJobProducerProof) => void | Promise<void>} [args.afterOwnedProducerValidation] - Exact owned-enqueue validation hook.
153
155
  */
154
- constructor({configuration, databaseIdentifier}) {
156
+ constructor({configuration, databaseIdentifier, clock, afterOwnedProducerValidation}) {
155
157
  super()
156
158
  this.configuration = configuration
157
159
  this.databaseIdentifier = databaseIdentifier
160
+ this.clock = clock || {now: () => Date.now()}
161
+ this.afterOwnedProducerValidation = afterOwnedProducerValidation
158
162
  this.logger = new Logger(this)
159
163
  this._readyPromise = null
160
164
  this._queueConcurrencyReconciled = false
@@ -321,23 +325,10 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
321
325
 
322
326
  await this._serializedCountMutation(async (db) => {
323
327
  if (options?.deduplicateWhileQueued) {
324
- // Dedupe on the job's identity (name + args + queue), NOT its concurrency key, so a job
325
- // keeps whatever concurrency it resolves to. Only an existing job scheduled no later than
326
- // this enqueue can cover it; a retry backed off into the future must not suppress earlier
327
- // work. Ordering returns the earliest covering job when several queued rows already exist.
328
- const existing = await db
329
- .newQuery()
330
- .from(JOBS_TABLE)
331
- .select("id")
332
- .where({status: "queued", job_name: jobName, args_json: preparedJob.argsJson, queue: preparedJob.queue})
333
- .where(`scheduled_at_ms <= ${db.quote(preparedJob.scheduledAtMs)}`)
334
- .order("scheduled_at_ms ASC")
335
- .limit(1)
336
- .results()
337
-
338
- if (existing[0]) {
339
- resultJobId = String(/** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (existing[0]).id)
328
+ const duplicateJobId = await this._deduplicatedQueuedJobId(db, preparedJob)
340
329
 
330
+ if (duplicateJobId) {
331
+ resultJobId = duplicateJobId
341
332
  return
342
333
  }
343
334
  }
@@ -349,6 +340,122 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
349
340
  return resultJobId
350
341
  }
351
342
 
343
+ /**
344
+ * Atomically validates an exact producing handoff and enqueues its follow-up.
345
+ * Every exact request owns an internal durable replay identity, while queued
346
+ * deduplication can point several distinct producer events at one covering row.
347
+ * @param {object} args - Owned enqueue request.
348
+ * @param {string} args.jobName - Job name.
349
+ * @param {Array<ReturnType<typeof JSON.parse>>} args.args - Arguments.
350
+ * @param {import("./types.js").BackgroundJobOptions} [args.options] - Options.
351
+ * @param {string} [args.producerInvocationId] - Stable identity for one owned enqueue invocation.
352
+ * @param {import("./types.js").BackgroundJobProducerProof} args.producerProof - Exact producer lease.
353
+ * @returns {Promise<string>} - Durable follow-up id.
354
+ */
355
+ async enqueueFromOwnedHandoff({jobName, args, options, producerInvocationId, producerProof}) {
356
+ await this.ensureReady()
357
+
358
+ const normalizedProducerProof = this._normalizeProducerProof(producerProof)
359
+ const normalizedProducerInvocationId = this._normalizeProducerInvocationId(producerInvocationId)
360
+ const preparedJob = this._prepareJob({jobName, args, options})
361
+
362
+ return await this._serializedCountMutation(async (db) => {
363
+ await this._validateOwnedProducerProof(db, normalizedProducerProof)
364
+ if (this.afterOwnedProducerValidation) await this.afterOwnedProducerValidation(normalizedProducerProof)
365
+
366
+ if (options?.idempotencyKey !== undefined) {
367
+ return await this._enqueueIdempotentlyInTransaction({
368
+ args: args || [],
369
+ countRevisionLocked: true,
370
+ db,
371
+ options,
372
+ preparedJob
373
+ })
374
+ }
375
+
376
+ return await this._enqueueOwnedReplayInTransaction({
377
+ db,
378
+ options: options || {},
379
+ preparedJob,
380
+ producerInvocationId: normalizedProducerInvocationId,
381
+ producerProof: normalizedProducerProof
382
+ })
383
+ })
384
+ }
385
+
386
+ /**
387
+ * Finds the earliest queued job that covers this enqueue's identity and time.
388
+ * @param {import("../database/drivers/base.js").default} db - Transaction connection.
389
+ * @param {PreparedBackgroundJob} preparedJob - Normalized job.
390
+ * @returns {Promise<string | null>} - Covering job id.
391
+ */
392
+ async _deduplicatedQueuedJobId(db, preparedJob) {
393
+ // Dedupe on the job's identity (name + args + queue), NOT its concurrency key, so a job
394
+ // keeps whatever concurrency it resolves to. Only an existing job scheduled no later than
395
+ // this enqueue can cover it; a retry backed off into the future must not suppress earlier
396
+ // work. Ordering returns the earliest covering job when several queued rows already exist.
397
+ const existing = await db
398
+ .newQuery()
399
+ .from(JOBS_TABLE)
400
+ .select("id")
401
+ .where({status: "queued", job_name: preparedJob.jobName, args_json: preparedJob.argsJson, queue: preparedJob.queue})
402
+ .where(`scheduled_at_ms <= ${db.quote(preparedJob.scheduledAtMs)}`)
403
+ .order("scheduled_at_ms ASC")
404
+ .limit(1)
405
+ .results()
406
+ const row = existing[0]
407
+
408
+ return row ? String(/** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (row).id) : null
409
+ }
410
+
411
+ /**
412
+ * Persists one internal exact-replay owner and its queued job in the caller's
413
+ * producer-validation transaction.
414
+ * @param {object} args - Transaction input.
415
+ * @param {import("../database/drivers/base.js").default} args.db - Transaction connection.
416
+ * @param {import("./types.js").BackgroundJobOptions} args.options - Enqueue options.
417
+ * @param {PreparedBackgroundJob} args.preparedJob - Normalized job.
418
+ * @param {string} args.producerInvocationId - Stable identity for one owned enqueue invocation.
419
+ * @param {import("./types.js").BackgroundJobProducerProof} args.producerProof - Exact producer lease.
420
+ * @returns {Promise<string>} - Stable replay job id.
421
+ */
422
+ async _enqueueOwnedReplayInTransaction({db, options, preparedJob, producerInvocationId, producerProof}) {
423
+ const requestDigest = this._ownedEnqueueRequestDigest({options, preparedJob})
424
+ const scopeDigest = this._ownedEnqueueScopeDigest({preparedJob, producerInvocationId, producerProof, requestDigest})
425
+ const idempotencyKey = `owned-handoff:${scopeDigest}`
426
+ const existing = await this._idempotencyOwnership(db, scopeDigest)
427
+ const baseOwnership = {
428
+ created_at_ms: preparedJob.createdAtMs,
429
+ idempotency_key: idempotencyKey,
430
+ job_name: preparedJob.jobName,
431
+ queue: preparedJob.queue,
432
+ request_digest: requestDigest,
433
+ scope_digest: scopeDigest
434
+ }
435
+
436
+ if (existing) {
437
+ this._validateIdempotencyOwnership({existing, ownership: {...baseOwnership, job_id: String(existing.job_id)}})
438
+ return String(existing.job_id)
439
+ }
440
+
441
+ const duplicateJobId = options.deduplicateWhileQueued
442
+ ? await this._deduplicatedQueuedJobId(db, preparedJob)
443
+ : null
444
+ const ownership = {...baseOwnership, job_id: duplicateJobId || preparedJob.jobId}
445
+ const claimed = await this._claimIdempotencyOwnership(db, ownership)
446
+
447
+ if (!claimed.created) {
448
+ this._validateIdempotencyOwnership({existing: claimed.row, ownership})
449
+ return String(claimed.row.job_id)
450
+ }
451
+ if (duplicateJobId) return duplicateJobId
452
+
453
+ await this._insertPreparedJob(db, {preparedJob, scheduleKey: null})
454
+ await this._recordCountDelta(db, {all: 1, queued: 1})
455
+
456
+ return preparedJob.jobId
457
+ }
458
+
352
459
  /**
353
460
  * Atomically owns one durable idempotency scope and creates its job exactly once.
354
461
  * @param {object} args - Enqueue input.
@@ -358,6 +465,25 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
358
465
  * @returns {Promise<string>} - Stable original job id.
359
466
  */
360
467
  async _enqueueIdempotently({args, options, preparedJob}) {
468
+ // Reuse ordinary enqueue transaction admission because this path changes
469
+ // the same durable count revision. The scope primary key remains the
470
+ // cross-process convergence owner.
471
+ return await this._idempotentEnqueueTransaction(async (db) => {
472
+ return await this._enqueueIdempotentlyInTransaction({args, db, options, preparedJob})
473
+ })
474
+ }
475
+
476
+ /**
477
+ * Owns or replays one public idempotency key inside the caller's transaction.
478
+ * @param {object} args - Transaction input.
479
+ * @param {Array<ReturnType<typeof JSON.parse>>} args.args - Job arguments.
480
+ * @param {boolean} [args.countRevisionLocked] - Whether the caller already owns count serialization.
481
+ * @param {import("../database/drivers/base.js").default} args.db - Transaction connection.
482
+ * @param {import("./types.js").BackgroundJobOptions} args.options - Job options.
483
+ * @param {PreparedBackgroundJob} args.preparedJob - Normalized job.
484
+ * @returns {Promise<string>} - Stable original job id.
485
+ */
486
+ async _enqueueIdempotentlyInTransaction({args, countRevisionLocked = false, db, options, preparedJob}) {
361
487
  const idempotencyKey = this._normalizeIdempotencyKey(options.idempotencyKey)
362
488
  const scopeDigest = this._idempotencyScopeDigest({idempotencyKey, jobName: preparedJob.jobName, queue: preparedJob.queue})
363
489
  const requestDigest = this._idempotencyRequestDigest({args, options, preparedJob})
@@ -378,33 +504,28 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
378
504
  })
379
505
  }
380
506
 
381
- // Reuse ordinary enqueue transaction admission because this path changes
382
- // the same durable count revision. The scope primary key remains the
383
- // cross-process convergence owner.
384
- return await this._idempotentEnqueueTransaction(async (db) => {
385
- const existing = await this._idempotencyOwnership(db, scopeDigest)
507
+ const existing = await this._idempotencyOwnership(db, scopeDigest)
386
508
 
387
- if (existing) {
388
- this._validateIdempotencyOwnership({existing, ownership})
389
- await this._validateMailDeliveryOperation(db, {jobId: String(existing.job_id), mailOperationInput})
390
- return String(existing.job_id)
391
- }
509
+ if (existing) {
510
+ this._validateIdempotencyOwnership({existing, ownership})
511
+ await this._validateMailDeliveryOperation(db, {jobId: String(existing.job_id), mailOperationInput})
512
+ return String(existing.job_id)
513
+ }
392
514
 
393
- const claimed = await this._claimIdempotencyOwnership(db, ownership)
515
+ const claimed = await this._claimIdempotencyOwnership(db, ownership)
394
516
 
395
- if (!claimed.created) {
396
- this._validateIdempotencyOwnership({existing: claimed.row, ownership})
397
- await this._validateMailDeliveryOperation(db, {jobId: String(claimed.row.job_id), mailOperationInput})
398
- return String(claimed.row.job_id)
399
- }
517
+ if (!claimed.created) {
518
+ this._validateIdempotencyOwnership({existing: claimed.row, ownership})
519
+ await this._validateMailDeliveryOperation(db, {jobId: String(claimed.row.job_id), mailOperationInput})
520
+ return String(claimed.row.job_id)
521
+ }
400
522
 
401
- await this._lockCountRevision(db)
402
- await this._insertPreparedJob(db, {preparedJob, scheduleKey: null})
403
- await this._persistMailDeliveryOperation(db, {jobId: preparedJob.jobId, mailOperationInput, createdAtMs: preparedJob.createdAtMs})
404
- await this._recordCountDelta(db, {all: 1, queued: 1})
523
+ if (!countRevisionLocked) await this._lockCountRevision(db)
524
+ await this._insertPreparedJob(db, {preparedJob, scheduleKey: null})
525
+ await this._persistMailDeliveryOperation(db, {jobId: preparedJob.jobId, mailOperationInput, createdAtMs: preparedJob.createdAtMs})
526
+ await this._recordCountDelta(db, {all: 1, queued: 1})
405
527
 
406
- return preparedJob.jobId
407
- })
528
+ return preparedJob.jobId
408
529
  }
409
530
 
410
531
  /**
@@ -625,6 +746,126 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
625
746
  return idempotencyKey
626
747
  }
627
748
 
749
+ /**
750
+ * Canonical request identity for an internal owned-handoff replay.
751
+ * Immediate enqueue wall time and generated job ids remain excluded.
752
+ * @param {object} args - Digest input.
753
+ * @param {import("./types.js").BackgroundJobOptions} args.options - Enqueue options.
754
+ * @param {PreparedBackgroundJob} args.preparedJob - Normalized job.
755
+ * @returns {string} - SHA-256 digest.
756
+ */
757
+ _ownedEnqueueRequestDigest({options, preparedJob}) {
758
+ const serialized = stableJsonStringify({
759
+ argsJson: preparedJob.argsJson,
760
+ concurrency: preparedJob.concurrency,
761
+ deduplicateWhileQueued: options.deduplicateWhileQueued === true,
762
+ executionMode: preparedJob.executionMode,
763
+ format: "velocious-background-job-owned-enqueue-v1",
764
+ jobName: preparedJob.jobName,
765
+ maxRetries: preparedJob.maxRetries,
766
+ queue: preparedJob.queue,
767
+ scheduledAtMs: options.scheduledAtMs === undefined ? null : preparedJob.scheduledAtMs,
768
+ scheduling: options.scheduledAtMs === undefined ? "immediate" : "scheduled",
769
+ ...(preparedJob.timeoutMs === null ? {} : {timeoutMs: preparedJob.timeoutMs})
770
+ })
771
+
772
+ return createHash("sha256").update(serialized).digest("hex")
773
+ }
774
+
775
+ /**
776
+ * Isolates internal producer replay ownership from caller idempotency scopes.
777
+ * @param {object} args - Scope input.
778
+ * @param {PreparedBackgroundJob} args.preparedJob - Normalized job.
779
+ * @param {string} args.producerInvocationId - Stable identity for one owned enqueue invocation.
780
+ * @param {import("./types.js").BackgroundJobProducerProof} args.producerProof - Exact producer lease.
781
+ * @param {string} args.requestDigest - Canonical request digest.
782
+ * @returns {string} - SHA-256 scope digest.
783
+ */
784
+ _ownedEnqueueScopeDigest({preparedJob, producerInvocationId, producerProof, requestDigest}) {
785
+ return createHash("sha256")
786
+ .update(stableJsonStringify({
787
+ format: "velocious-background-job-owned-enqueue-scope-v1",
788
+ jobName: preparedJob.jobName,
789
+ producerInvocationId,
790
+ producerProof,
791
+ queue: preparedJob.queue,
792
+ requestDigest
793
+ }))
794
+ .digest("hex")
795
+ }
796
+
797
+ /**
798
+ * Validates the untrusted identity of one producer-owned enqueue invocation.
799
+ * @param {string | undefined} producerInvocationId - Producer invocation identity.
800
+ * @returns {string} - Validated identity.
801
+ */
802
+ _normalizeProducerInvocationId(producerInvocationId) {
803
+ if (typeof producerInvocationId !== "string" || producerInvocationId.length === 0) {
804
+ throw VelociousError.safe("Background job producer invocation id is invalid.", {
805
+ code: "background-job-producer-invocation-id-invalid"
806
+ })
807
+ }
808
+
809
+ return producerInvocationId
810
+ }
811
+
812
+ /**
813
+ * Validates the untrusted transport shape before transaction admission.
814
+ * @param {import("./types.js").BackgroundJobProducerProof} producerProof - Producer proof.
815
+ * @returns {import("./types.js").BackgroundJobProducerProof} - Normalized immutable proof.
816
+ */
817
+ _normalizeProducerProof(producerProof) {
818
+ const exactKeys = ["handedOffAtMs", "handoffId", "jobId", "workerId"]
819
+ const keys = producerProof && typeof producerProof === "object" ? Object.keys(producerProof) : []
820
+ const valid = producerProof
821
+ && typeof producerProof === "object"
822
+ && keys.length === exactKeys.length
823
+ && keys.every((key) => exactKeys.includes(key))
824
+ && typeof producerProof.jobId === "string"
825
+ && producerProof.jobId.length > 0
826
+ && typeof producerProof.handoffId === "string"
827
+ && producerProof.handoffId.length > 0
828
+ && typeof producerProof.workerId === "string"
829
+ && producerProof.workerId.length > 0
830
+ && Number.isSafeInteger(producerProof.handedOffAtMs)
831
+ && producerProof.handedOffAtMs >= 0
832
+
833
+ if (!valid) {
834
+ throw VelociousError.safe("Background job producer proof is invalid.", {
835
+ code: "background-job-producer-proof-invalid"
836
+ })
837
+ }
838
+
839
+ return Object.freeze({
840
+ handedOffAtMs: producerProof.handedOffAtMs,
841
+ handoffId: producerProof.handoffId,
842
+ jobId: producerProof.jobId,
843
+ workerId: producerProof.workerId
844
+ })
845
+ }
846
+
847
+ /**
848
+ * Confirms exact active ownership while the enqueue transaction holds the
849
+ * shared mutation fence used by terminal producer transitions.
850
+ * @param {import("../database/drivers/base.js").default} db - Transaction connection.
851
+ * @param {import("./types.js").BackgroundJobProducerProof} producerProof - Exact producer lease.
852
+ * @returns {Promise<void>} - Resolves while ownership remains exact.
853
+ */
854
+ async _validateOwnedProducerProof(db, producerProof) {
855
+ const producer = await this._getJobRowById(db, producerProof.jobId)
856
+ const owned = producer
857
+ && producer.status === "handed_off"
858
+ && producer.handoffId === producerProof.handoffId
859
+ && producer.workerId === producerProof.workerId
860
+ && producer.handedOffAtMs === producerProof.handedOffAtMs
861
+
862
+ if (!owned) {
863
+ throw VelociousError.safe("Background job producer handoff is no longer owned.", {
864
+ code: "background-job-producer-handoff-not-owned"
865
+ })
866
+ }
867
+ }
868
+
628
869
  /**
629
870
  * Replaces the queued owner of a stable schedule key with a new one-off job.
630
871
  * A handed-off owner is left running and reported truthfully.
@@ -791,7 +1032,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
791
1032
  * @returns {Promise<import("./types.js").BackgroundJobRow | null>} - Next matching queued job.
792
1033
  */
793
1034
  async _nextQueuedJob({db, scheduledAtOperator, executionMode}) {
794
- const now = Date.now()
1035
+ const now = this.clock.now()
795
1036
  let query = db
796
1037
  .newQuery()
797
1038
  .from(JOBS_TABLE)
@@ -999,7 +1240,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
999
1240
  async markHandedOff({jobId, handoffId = randomUUID(), workerId}) {
1000
1241
  await this.ensureReady()
1001
1242
 
1002
- const handedOffAtMs = Date.now()
1243
+ const handedOffAtMs = this.clock.now()
1003
1244
 
1004
1245
  return await this._serializedCountMutation(async (db) => {
1005
1246
  const selectedJob = await this._getJobRowById(db, jobId)
@@ -1061,7 +1302,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
1061
1302
  tableName: JOBS_TABLE,
1062
1303
  data: {
1063
1304
  status: "completed",
1064
- completed_at_ms: Date.now()
1305
+ completed_at_ms: this.clock.now()
1065
1306
  },
1066
1307
  conditions: this._activeHandoffConditions(job)
1067
1308
  })
@@ -1134,7 +1375,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
1134
1375
  tableName: JOBS_TABLE,
1135
1376
  data: {
1136
1377
  status: "queued",
1137
- scheduled_at_ms: Date.now(),
1378
+ scheduled_at_ms: this.clock.now(),
1138
1379
  handed_off_at_ms: null,
1139
1380
  handoff_id: null,
1140
1381
  worker_id: null
@@ -1291,7 +1532,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
1291
1532
  await this.ensureReady()
1292
1533
 
1293
1534
  return await this._serializedCountMutation(async (db) => {
1294
- const cutoff = Date.now() - orphanedAfterMs
1535
+ const cutoff = this.clock.now() - orphanedAfterMs
1295
1536
  const query = db
1296
1537
  .newQuery()
1297
1538
  .from(JOBS_TABLE)
@@ -1386,7 +1627,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
1386
1627
  async pruneTerminalJobs({completedTtlMs = null, failedTtlMs = null, batchSize = 1000} = {}) {
1387
1628
  await this.ensureReady()
1388
1629
 
1389
- const now = Date.now()
1630
+ const now = this.clock.now()
1390
1631
  const size = batchSize > 0 ? batchSize : 1000
1391
1632
  let deleted = 0
1392
1633
 
@@ -1505,7 +1746,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
1505
1746
  * @returns {PreparedBackgroundJob} - Prepared job.
1506
1747
  */
1507
1748
  _prepareJob({args, jobName, options}) {
1508
- const createdAtMs = Date.now()
1749
+ const createdAtMs = this.clock.now()
1509
1750
  const queue = this._normalizeQueue(options)
1510
1751
 
1511
1752
  return {
@@ -1612,7 +1853,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
1612
1853
  * @returns {number} - Future eligibility timestamp.
1613
1854
  */
1614
1855
  _rescheduledAtMs(delayMs) {
1615
- return rescheduledBackgroundJobAtMs(delayMs, Date.now())
1856
+ return rescheduledBackgroundJobAtMs(delayMs, this.clock.now())
1616
1857
  }
1617
1858
 
1618
1859
  /**
@@ -2238,7 +2479,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
2238
2479
  * @returns {Promise<import("./types.js").BackgroundJobRow | null>} - Updated job row when the lease transition won.
2239
2480
  */
2240
2481
  async _applyFailure({db, job, error, markOrphaned, conditions}) {
2241
- const now = Date.now()
2482
+ const now = this.clock.now()
2242
2483
  const nextAttempt = (job.attempts || 0) + 1
2243
2484
  const maxRetries = this._normalizeMaxRetries(job.maxRetries)
2244
2485
  const shouldRetry = nextAttempt <= maxRetries
@@ -10,6 +10,15 @@
10
10
  /** @typedef {"starting" | "running" | "retiring"} PooledRunnerLifecycleState */
11
11
  /** @typedef {"unexpected" | "job-timeout" | "worker-shutdown-timeout"} PooledRunnerTerminationReason */
12
12
  /** @typedef {"running" | "retiring" | "stopping"} BackgroundJobsWorkerLifecycleState */
13
+ /**
14
+ * Exact durable handoff ownership carried by an executing job when it produces
15
+ * follow-up work.
16
+ * @typedef {object} BackgroundJobProducerProof
17
+ * @property {string} jobId - Producing job id.
18
+ * @property {string} handoffId - Producing handoff lease id.
19
+ * @property {string} workerId - Worker identity persisted with the handoff.
20
+ * @property {number} handedOffAtMs - Durable handoff timestamp.
21
+ */
13
22
  /**
14
23
  * @typedef {object} PooledRunnerActiveJob
15
24
  * @property {string | null} handoffId - Durable handoff lease id.
@@ -84,7 +93,7 @@
84
93
  */
85
94
  /**
86
95
  * @typedef {object} BackgroundJobsProducer
87
- * @property {(args: {jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions}) => Promise<string>} enqueue - Enqueues a job.
96
+ * @property {(args: {jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions, producerInvocationId?: string, producerProof?: BackgroundJobProducerProof}) => Promise<string>} enqueue - Enqueues a job.
88
97
  * @property {(args: {scheduleKey: string, jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions}) => Promise<BackgroundJobReplacementResult>} replaceScheduled - Replaces a stable schedule.
89
98
  * @property {(args: {scheduleKey: string}) => Promise<BackgroundJobCancellationResult>} cancelScheduled - Cancels a stable schedule.
90
99
  */
@@ -200,7 +209,7 @@
200
209
  * @typedef {{type: "ready", acceptsForked?: boolean, acceptsInline?: boolean, acceptsPooled?: boolean, acceptsSpawned?: boolean, availablePooledSlots?: number}} BackgroundJobReadyMessage
201
210
  * @typedef {{type: "draining"}} BackgroundJobDrainingMessage
202
211
  * @typedef {{type: "heartbeat", workerId?: string}} BackgroundJobHeartbeatMessage
203
- * @typedef {{type: "enqueue", jobName: string, args?: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions}} BackgroundJobEnqueueMessage
212
+ * @typedef {{type: "enqueue", jobName: string, args?: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions, producerInvocationId?: string, producerProof?: BackgroundJobProducerProof}} BackgroundJobEnqueueMessage
204
213
  * @typedef {{type: "enqueued", jobId: string}} BackgroundJobEnqueuedMessage
205
214
  * @typedef {{type: "enqueue-error", error?: string}} BackgroundJobEnqueueErrorMessage
206
215
  * @typedef {{type: "replace-scheduled", scheduleKey: string, jobName: string, args?: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions}} BackgroundJobReplaceScheduledMessage
@@ -11,6 +11,7 @@ import { fileURLToPath } from "node:url"
11
11
  import shutdownLifecycle, { runShutdownSteps } from "../utils/shutdown-lifecycle.js"
12
12
  import BackgroundJobRescheduleSignal from "./reschedule-signal.js"
13
13
  import performBackgroundJob from "./perform-job.js"
14
+ import { runWithBackgroundJobPayload } from "./execution-context.js"
14
15
  import { createGenerationWorkerId } from "./generation-identity.js"
15
16
  import BackgroundJobsGenerationHandshakeTimeoutError, { DEFAULT_GENERATION_HANDSHAKE_TIMEOUT_MS, validateGenerationHandshakeTimeoutMs } from "./generation-handshake-timeout-error.js"
16
17
 
@@ -1430,13 +1431,15 @@ export default class BackgroundJobsWorker {
1430
1431
  const registry = new BackgroundJobRegistry({configuration})
1431
1432
  await registry.load()
1432
1433
  const JobClass = registry.getJobByName(payload.jobName)
1433
- await performBackgroundJob({
1434
- configuration,
1435
- JobClass,
1436
- jobArgs: payload.args || [],
1437
- jobOptions: payload.options || {},
1438
- name: `Background job worker inline: ${payload.jobName}`,
1439
- payload
1434
+ await runWithBackgroundJobPayload(payload, async () => {
1435
+ await performBackgroundJob({
1436
+ configuration,
1437
+ JobClass,
1438
+ jobArgs: payload.args || [],
1439
+ jobOptions: payload.options || {},
1440
+ name: `Background job worker inline: ${payload.jobName}`,
1441
+ payload
1442
+ })
1440
1443
  })
1441
1444
  }
1442
1445
 
@@ -10,7 +10,7 @@ import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigura
10
10
  import {createOfflineGrantFromBootstrap, verifyOfflineGrant} from "./sync/offline-grant.js"
11
11
  import {serverChangeFeedStoreForConfiguration} from "./sync/server-change-feed.js"
12
12
  import {mutationIdempotencyKey, verifySignedMutation} from "./sync/device-identity.js"
13
- import {FrontendModelQueryError, normalizeGroup as normalizeQueryGroup, normalizeJoins as normalizeQueryJoins, normalizePluck as normalizeQueryPluck, normalizePreload as normalizeQueryPreload, normalizeSearchOperator as normalizeQuerySearchOperator, normalizeSort as normalizeQuerySort} from "./frontend-models/query.js"
13
+ import {assertFrontendModelIndexPayload, FrontendModelQueryError, normalizeGroup as normalizeQueryGroup, normalizeJoins as normalizeQueryJoins, normalizePluck as normalizeQueryPluck, normalizePreload as normalizeQueryPreload, normalizeSearchOperator as normalizeQuerySearchOperator, normalizeSort as normalizeQuerySort} from "./frontend-models/query.js"
14
14
  import {assignSafeProperty, deserializeFrontendModelTransportValue, isBackendModelInstance, serializeFrontendModelTransportValue} from "./frontend-models/transport-serialization.js"
15
15
  import {requestDetails} from "./error-reporting/request-details.js"
16
16
  import RoutesResolver from "./routes/resolver.js"
@@ -4455,14 +4455,27 @@ export default class FrontendModelController extends Controller {
4455
4455
  }
4456
4456
 
4457
4457
  try {
4458
+ let indexPayload = {}
4459
+
4460
+ if (commandType === "index") {
4461
+ try {
4462
+ indexPayload = assertFrontendModelIndexPayload(payload)
4463
+ } catch (error) {
4464
+ throwFrontendModelQueryErrorForParserError(error)
4465
+ }
4466
+ }
4467
+
4458
4468
  const requestContext = captureFrontendModelRemoteRequestContext(requestEntry?.requestContext)
4459
4469
  let responsePayload
4460
4470
 
4461
4471
  if (isBuiltInCommand) {
4472
+ const commandPayload = commandType === "index"
4473
+ ? indexPayload
4474
+ : (payload && typeof payload === "object" ? payload : {})
4462
4475
  const commandParams = mergeFrontendModelRemoteRequestContext(
4463
4476
  requestContext,
4464
4477
  {
4465
- ...(payload && typeof payload === "object" ? payload : {}),
4478
+ ...commandPayload,
4466
4479
  model
4467
4480
  }
4468
4481
  )
@@ -118,6 +118,28 @@ export class FrontendModelQueryError extends Error {
118
118
  }
119
119
  }
120
120
 
121
+ const FRONTEND_MODEL_INDEX_PAYLOAD_KEYS = new Set([
122
+ "abilities",
123
+ "count",
124
+ "distinct",
125
+ "group",
126
+ "joins",
127
+ "limit",
128
+ "offset",
129
+ "page",
130
+ "perPage",
131
+ "pluck",
132
+ "preload",
133
+ "queryData",
134
+ "ransack",
135
+ "searches",
136
+ "select",
137
+ "selectsExtra",
138
+ "sort",
139
+ "where",
140
+ "withCount"
141
+ ])
142
+
121
143
  /**
122
144
  * Builds a query descriptor error.
123
145
  * @param {string} message - Error message.
@@ -127,6 +149,27 @@ function frontendModelQueryError(message) {
127
149
  return new FrontendModelQueryError(message)
128
150
  }
129
151
 
152
+ /**
153
+ * Asserts the raw payload accepted by a shared frontend-model index command.
154
+ * @param {ReturnType<typeof JSON.parse>} payload - Raw index payload.
155
+ * @returns {Record<string, ReturnType<typeof JSON.parse>>} - Valid index payload.
156
+ */
157
+ export function assertFrontendModelIndexPayload(payload) {
158
+ if (payload == null) return {}
159
+
160
+ if (!isPlainObject(payload)) {
161
+ throw frontendModelQueryError("Expected frontend-model index payload to be a plain object")
162
+ }
163
+
164
+ for (const payloadKey of Object.keys(payload)) {
165
+ if (!FRONTEND_MODEL_INDEX_PAYLOAD_KEYS.has(payloadKey)) {
166
+ throw frontendModelQueryError(`Unknown frontend-model index payload key "${payloadKey}"`)
167
+ }
168
+ }
169
+
170
+ return payload
171
+ }
172
+
130
173
  /**
131
174
  * Runs the normalizePreload helper.
132
175
  * @param {import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord> | boolean | undefined | null} preload - Preload shorthand.
@@ -11,6 +11,13 @@ export default class BackgroundJobsAdapter {
11
11
  * @returns {boolean} - Whether generation mode is supported.
12
12
  */
13
13
  supportsReleaseScopedGenerations(): boolean;
14
+ /**
15
+ * Declares atomic producer-handoff validation plus enqueue support. A
16
+ * generation-capable adapter must override this together with
17
+ * `enqueueFromOwnedHandoff`.
18
+ * @returns {boolean} - Whether atomic owned enqueue is supported.
19
+ */
20
+ supportsOwnedEnqueueFromHandoff(): boolean;
14
21
  /**
15
22
  * Ensures the adapter can accept work.
16
23
  * @returns {Promise<void>} - Resolves when ready.
@@ -56,6 +63,18 @@ export default class BackgroundJobsAdapter {
56
63
  args: Array<ReturnType<typeof JSON.parse>>;
57
64
  options?: import("./types.js").BackgroundJobOptions;
58
65
  }): Promise<string>;
66
+ /**
67
+ * Atomically validates an exact producing handoff and enqueues its follow-up.
68
+ * @param {{jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions, producerInvocationId?: string, producerProof: import("./types.js").BackgroundJobProducerProof}} _args - Owned enqueue request.
69
+ * @returns {Promise<string>} - Job id.
70
+ */
71
+ enqueueFromOwnedHandoff(_args: {
72
+ jobName: string;
73
+ args: Array<ReturnType<typeof JSON.parse>>;
74
+ options?: import("./types.js").BackgroundJobOptions;
75
+ producerInvocationId?: string;
76
+ producerProof: import("./types.js").BackgroundJobProducerProof;
77
+ }): Promise<string>;
59
78
  /**
60
79
  * Replaces the owner of a stable schedule key.
61
80
  * @param {{scheduleKey: string, jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions}} _args - Replacement request.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/adapter.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/adapter.js"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAqB;IACxC;;;;;OAKG;IACH,gCAAgC,IAFnB,OAAO,CAE+B;IAEnD;;;;;OAKG;IACH,+BAA+B,IAFlB,OAAO,CAE8B;IAElD;;;OAGG;IACG,WAAW,IAFJ,OAAO,CAAC,IAAI,CAAC,CAEqE;IAE/F;;;OAGG;IACG,KAAK,IAFE,OAAO,CAAC,IAAI,CAAC,CAEV;IAEhB;;;OAGG;IACG,MAAM,IAFC,OAAO,CAAC,OAAO,YAAY,EAAE,oBAAoB,CAAC,CAI9D;IAED;;;;;OAKG;IACG,qBAAqB,CAAC,KAAK,EAHtB;QAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,6BAA6B,EAAE,OAAO,CAAC,CAAA;KAG7C,GAFpB,OAAO,CAAC,IAAI,CAAC,CAEW;IAErC;;;OAGG;IACG,yBAAyB,IAFlB,OAAO,CAAC,IAAI,CAAC,CAEiG;IAE3H;;;;OAIG;IACG,0BAA0B,IAFnB,OAAO,CAAC,OAAO,YAAY,EAAE,sCAAsC,CAAC,CAIhF;IAED;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAHR;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,oBAAoB,CAAA;KAGzG,GAFN,OAAO,CAAC,MAAM,CAAC,CAEgE;IAE5F;;;;OAIG;IACG,uBAAuB,CAAC,KAAK,EAHxB;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,oBAAoB,CAAC;QAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,OAAO,YAAY,EAAE,0BAA0B,CAAA;KAGxL,GAFtB,OAAO,CAAC,MAAM,CAAC,CAEgG;IAE5H;;;;OAIG;IACG,gBAAgB,CAAC,KAAK,EAHjB;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,oBAAoB,CAAA;KAGrH,GAFf,OAAO,CAAC,OAAO,YAAY,EAAE,8BAA8B,CAAC,CAEqC;IAE9G;;;;OAIG;IACG,eAAe,CAAC,YAAY,EAHvB,MAGuB,GAFrB,OAAO,CAAC,OAAO,YAAY,EAAE,+BAA+B,CAAC,CAEyC;IAEnH;;;;OAIG;IACG,gBAAgB,CAAC,KAAK,AAHzB,CACA,EADQ;QAAC,aAAa,CAAC,EAAE,OAAO,YAAY,EAAE,0BAA0B,GAAG,OAAO,YAAY,EAAE,0BAA0B,EAAE,CAAA;KAG9F,GAFpB,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC,CAEiD;IAEnH;;;OAGG;IACG,gBAAgB,IAFT,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC,CAEuC;IAEzG;;;;OAIG;IACG,MAAM,CAAC,MAAM,EAHR,MAGQ,GAFN,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC,CAEyB;IAE3F;;;;;;OAMG;IACG,aAAa,CAAC,KAAK,EAHd,OAAO,YAAY,EAAE,2BAGP,GAFZ,OAAO,CAAC,OAAO,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC,CAEkC;IAExG;;;;OAIG;IACG,aAAa,CAAC,KAAK,EAHd;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAG/D,GAFZ,OAAO,CAAC,OAAO,CAAC,CAE2E;IAExG;;;;OAIG;IACG,eAAe,CAAC,KAAK,EAHhB;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAG9E,GAFd,OAAO,CAAC,OAAO,CAAC,CAE+E;IAE5G;;;;OAIG;IACG,mBAAmB,CAAC,KAAK,EAHpB;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAGb,GAFlB,OAAO,CAAC,IAAI,CAAC,CAE0F;IAEpH;;;;OAIG;IACG,sBAAsB,CAAC,KAAK,EAHvB;QAAC,QAAQ,EAAE,MAAM,CAAA;KAGM,GAFrB,OAAO,CAAC,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC,CAE2D;IAE1H;;;;OAIG;IACG,qBAAqB,IAFd,OAAO,CAAC,OAAO,YAAY,EAAE,4BAA4B,EAAE,CAAC,CAE9B;IAE3C;;;;;;OAMG;IACG,oBAAoB,CAAC,KAAK,EAHrB;QAAC,QAAQ,EAAE,OAAO,YAAY,EAAE,4BAA4B,EAAE,CAAC;QAAC,KAAK,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;KAG/E,GAFnB,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,EAAE,CAAC,CAEd;IAE/C;;;;OAIG;IACG,UAAU,CAAC,KAAK,EAHX;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAGxG,GAFT,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC,CAEgC;IAElG;;;;OAIG;IACG,gBAAgB,CAAC,KAAK,AAHzB,CACA,EADQ;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAGH,GAFpB,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,EAAE,CAAC,CAEsD;IAEnH;;;;OAIG;IACG,iBAAiB,CAAC,KAAK,AAH1B,CACA,EADQ;QAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAGzD,GAFrB,OAAO,CAAC,MAAM,CAAC,CAEyF;CACtH"}