@ttt-productions/media-processing-core 0.8.16 → 0.8.18

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 (141) hide show
  1. package/dist/audio/audio-processor.d.ts +2 -2
  2. package/dist/audio/audio-processor.d.ts.map +1 -1
  3. package/dist/audio/audio-processor.js +63 -50
  4. package/dist/audio/audio-processor.js.map +1 -1
  5. package/dist/audio/probe.d.ts +1 -1
  6. package/dist/audio/probe.d.ts.map +1 -1
  7. package/dist/audio/probe.js +6 -5
  8. package/dist/audio/probe.js.map +1 -1
  9. package/dist/duration/media-duration.d.ts +2 -13
  10. package/dist/duration/media-duration.d.ts.map +1 -1
  11. package/dist/duration/media-duration.js +24 -49
  12. package/dist/duration/media-duration.js.map +1 -1
  13. package/dist/image/formats.d.ts +2 -13
  14. package/dist/image/formats.d.ts.map +1 -1
  15. package/dist/image/formats.js +25 -35
  16. package/dist/image/formats.js.map +1 -1
  17. package/dist/image/image-processor.d.ts +2 -2
  18. package/dist/image/image-processor.d.ts.map +1 -1
  19. package/dist/image/image-processor.js +48 -57
  20. package/dist/image/image-processor.js.map +1 -1
  21. package/dist/image/resize.d.ts +2 -16
  22. package/dist/image/resize.d.ts.map +1 -1
  23. package/dist/image/resize.js +13 -28
  24. package/dist/image/resize.js.map +1 -1
  25. package/dist/index.d.ts +19 -16
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +18 -16
  28. package/dist/index.js.map +1 -1
  29. package/dist/inspection/inspect-media.d.ts +13 -20
  30. package/dist/inspection/inspect-media.d.ts.map +1 -1
  31. package/dist/inspection/inspect-media.js +197 -201
  32. package/dist/inspection/inspect-media.js.map +1 -1
  33. package/dist/io/fs.d.ts +1 -1
  34. package/dist/io/fs.d.ts.map +1 -1
  35. package/dist/io/fs.js +4 -5
  36. package/dist/io/fs.js.map +1 -1
  37. package/dist/io/local-input.d.ts +1 -21
  38. package/dist/io/local-input.d.ts.map +1 -1
  39. package/dist/io/local-input.js +15 -35
  40. package/dist/io/local-input.js.map +1 -1
  41. package/dist/io/types.d.ts +0 -3
  42. package/dist/io/types.d.ts.map +1 -1
  43. package/dist/io/types.js.map +1 -1
  44. package/dist/moderation/merge.d.ts +1 -1
  45. package/dist/moderation/merge.d.ts.map +1 -1
  46. package/dist/moderation/merge.js +1 -2
  47. package/dist/moderation/merge.js.map +1 -1
  48. package/dist/moderation/types.d.ts +3 -5
  49. package/dist/moderation/types.d.ts.map +1 -1
  50. package/dist/moderation/types.js.map +1 -1
  51. package/dist/process-media.d.ts +2 -3
  52. package/dist/process-media.d.ts.map +1 -1
  53. package/dist/process-media.js +9 -9
  54. package/dist/process-media.js.map +1 -1
  55. package/dist/processing/errors.d.ts.map +1 -1
  56. package/dist/processing/errors.js +1 -1
  57. package/dist/processing/errors.js.map +1 -1
  58. package/dist/processing/result.d.ts +2 -2
  59. package/dist/processing/result.js +1 -1
  60. package/dist/processing/result.js.map +1 -1
  61. package/dist/run-pipeline.d.ts +5 -22
  62. package/dist/run-pipeline.d.ts.map +1 -1
  63. package/dist/run-pipeline.js +72 -81
  64. package/dist/run-pipeline.js.map +1 -1
  65. package/dist/server/download-token.d.ts +0 -6
  66. package/dist/server/download-token.d.ts.map +1 -1
  67. package/dist/server/download-token.js +3 -13
  68. package/dist/server/download-token.js.map +1 -1
  69. package/dist/server/firebase-media-io.d.ts +3 -13
  70. package/dist/server/firebase-media-io.d.ts.map +1 -1
  71. package/dist/server/firebase-media-io.js +1 -6
  72. package/dist/server/firebase-media-io.js.map +1 -1
  73. package/dist/server/gcs-errors.d.ts +0 -8
  74. package/dist/server/gcs-errors.d.ts.map +1 -1
  75. package/dist/server/gcs-errors.js +2 -10
  76. package/dist/server/gcs-errors.js.map +1 -1
  77. package/dist/server/index.d.ts +7 -6
  78. package/dist/server/index.d.ts.map +1 -1
  79. package/dist/server/index.js +7 -6
  80. package/dist/server/index.js.map +1 -1
  81. package/dist/server/pending-media.d.ts +3 -34
  82. package/dist/server/pending-media.d.ts.map +1 -1
  83. package/dist/server/pending-media.js +13 -30
  84. package/dist/server/pending-media.js.map +1 -1
  85. package/dist/server/r2-retry.d.ts +4 -52
  86. package/dist/server/r2-retry.d.ts.map +1 -1
  87. package/dist/server/r2-retry.js +49 -134
  88. package/dist/server/r2-retry.js.map +1 -1
  89. package/dist/server/staged-object.d.ts +2 -46
  90. package/dist/server/staged-object.d.ts.map +1 -1
  91. package/dist/server/staged-object.js +4 -48
  92. package/dist/server/staged-object.js.map +1 -1
  93. package/dist/server/storage-ops.d.ts +5 -67
  94. package/dist/server/storage-ops.d.ts.map +1 -1
  95. package/dist/server/storage-ops.js +110 -185
  96. package/dist/server/storage-ops.js.map +1 -1
  97. package/dist/types.d.ts +2 -4
  98. package/dist/types.d.ts.map +1 -1
  99. package/dist/types.js.map +1 -1
  100. package/dist/utils/log.d.ts.map +1 -1
  101. package/dist/utils/log.js +3 -3
  102. package/dist/utils/log.js.map +1 -1
  103. package/dist/utils/paths.d.ts.map +1 -1
  104. package/dist/utils/paths.js +1 -4
  105. package/dist/utils/paths.js.map +1 -1
  106. package/dist/utils/safe-path.d.ts.map +1 -1
  107. package/dist/utils/safe-path.js +8 -9
  108. package/dist/utils/safe-path.js.map +1 -1
  109. package/dist/validation/validate-duration.d.ts.map +1 -1
  110. package/dist/validation/validate-duration.js.map +1 -1
  111. package/dist/validation/validate-mime.d.ts.map +1 -1
  112. package/dist/validation/validate-mime.js.map +1 -1
  113. package/dist/validation/validate-size.d.ts.map +1 -1
  114. package/dist/validation/validate-size.js.map +1 -1
  115. package/dist/video/ffmpeg.d.ts +1 -23
  116. package/dist/video/ffmpeg.d.ts.map +1 -1
  117. package/dist/video/ffmpeg.js +57 -64
  118. package/dist/video/ffmpeg.js.map +1 -1
  119. package/dist/video/probe.d.ts +1 -1
  120. package/dist/video/probe.d.ts.map +1 -1
  121. package/dist/video/probe.js +10 -8
  122. package/dist/video/probe.js.map +1 -1
  123. package/dist/video/video-processor.d.ts +2 -2
  124. package/dist/video/video-processor.d.ts.map +1 -1
  125. package/dist/video/video-processor.js +125 -96
  126. package/dist/video/video-processor.js.map +1 -1
  127. package/dist/workspace/temp.js +4 -4
  128. package/dist/workspace/temp.js.map +1 -1
  129. package/package.json +2 -2
  130. package/dist/audio/index.d.ts +0 -3
  131. package/dist/audio/index.d.ts.map +0 -1
  132. package/dist/audio/index.js +0 -3
  133. package/dist/audio/index.js.map +0 -1
  134. package/dist/image/index.d.ts +0 -4
  135. package/dist/image/index.d.ts.map +0 -1
  136. package/dist/image/index.js +0 -4
  137. package/dist/image/index.js.map +0 -1
  138. package/dist/video/index.d.ts +0 -4
  139. package/dist/video/index.d.ts.map +0 -1
  140. package/dist/video/index.js +0 -4
  141. package/dist/video/index.js.map +0 -1
@@ -1,15 +1,7 @@
1
- import { R2StorageError } from "./r2-retry.js";
2
- /**
3
- * True when a storage read or delete failed because the object does not exist, from
4
- * either backend this package's stores use, decided from structured fields only — never
5
- * from message text, which a library may rewrite and which carries no contract. The R2
6
- * store's `R2StorageError` answers with status `404` and S3 error code `NoSuchKey`, which
7
- * tells a missing key from a missing bucket (`NoSuchBucket`); the Cloud Storage Admin SDK
8
- * answers with the numeric HTTP code `404`.
9
- */
1
+ import { R2StorageError } from './r2-retry.js';
10
2
  export function isObjectNotFoundError(e) {
11
3
  if (e instanceof R2StorageError)
12
- return e.status === 404 && e.s3ErrorCode === "NoSuchKey";
4
+ return e.status === 404 && e.s3ErrorCode === 'NoSuchKey';
13
5
  return e?.code === 404;
14
6
  }
15
7
  //# sourceMappingURL=gcs-errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gcs-errors.js","sourceRoot":"","sources":["../../src/server/gcs-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,CAAU;IAC9C,IAAI,CAAC,YAAY,cAAc;QAAE,OAAO,CAAC,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,KAAK,WAAW,CAAC;IAC1F,OAAQ,CAA2C,EAAE,IAAI,KAAK,GAAG,CAAC;AACpE,CAAC","sourcesContent":["import { R2StorageError } from \"./r2-retry.js\";\n\n/**\n * True when a storage read or delete failed because the object does not exist, from\n * either backend this package's stores use, decided from structured fields only — never\n * from message text, which a library may rewrite and which carries no contract. The R2\n * store's `R2StorageError` answers with status `404` and S3 error code `NoSuchKey`, which\n * tells a missing key from a missing bucket (`NoSuchBucket`); the Cloud Storage Admin SDK\n * answers with the numeric HTTP code `404`.\n */\nexport function isObjectNotFoundError(e: unknown): boolean {\n if (e instanceof R2StorageError) return e.status === 404 && e.s3ErrorCode === \"NoSuchKey\";\n return (e as { code?: unknown } | null | undefined)?.code === 404;\n}\n"]}
1
+ {"version":3,"file":"gcs-errors.js","sourceRoot":"","sources":["../../src/server/gcs-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,UAAU,qBAAqB,CAAC,CAAU;IAC9C,IAAI,CAAC,YAAY,cAAc;QAAE,OAAO,CAAC,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,KAAK,WAAW,CAAC;IAC1F,OAAQ,CAA2C,EAAE,IAAI,KAAK,GAAG,CAAC;AACpE,CAAC","sourcesContent":["import { R2StorageError } from './r2-retry.js';\n\nexport function isObjectNotFoundError(e: unknown): boolean {\n if (e instanceof R2StorageError) return e.status === 404 && e.s3ErrorCode === 'NoSuchKey';\n return (e as { code?: unknown } | null | undefined)?.code === 404;\n}\n"]}
@@ -1,7 +1,8 @@
1
- export * from "./storage-ops.js";
2
- export * from "./firebase-media-io.js";
3
- export * from "./pending-media.js";
4
- export * from "./staged-object.js";
5
- export * from "./gcs-errors.js";
6
- export * from "./download-token.js";
1
+ export * from './storage-ops.js';
2
+ export { R2StorageError } from './r2-retry.js';
3
+ export * from './firebase-media-io.js';
4
+ export * from './pending-media.js';
5
+ export * from './staged-object.js';
6
+ export * from './gcs-errors.js';
7
+ export * from './download-token.js';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
@@ -1,7 +1,8 @@
1
- export * from "./storage-ops.js";
2
- export * from "./firebase-media-io.js";
3
- export * from "./pending-media.js";
4
- export * from "./staged-object.js";
5
- export * from "./gcs-errors.js";
6
- export * from "./download-token.js";
1
+ export * from './storage-ops.js';
2
+ export { R2StorageError } from './r2-retry.js';
3
+ export * from './firebase-media-io.js';
4
+ export * from './pending-media.js';
5
+ export * from './staged-object.js';
6
+ export * from './gcs-errors.js';
7
+ export * from './download-token.js';
7
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC","sourcesContent":["export * from \"./storage-ops.js\";\nexport * from \"./firebase-media-io.js\";\nexport * from \"./pending-media.js\";\nexport * from \"./staged-object.js\";\nexport * from \"./gcs-errors.js\";\nexport * from \"./download-token.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC","sourcesContent":["export * from './storage-ops.js';\nexport { R2StorageError } from './r2-retry.js';\nexport * from './firebase-media-io.js';\nexport * from './pending-media.js';\nexport * from './staged-object.js';\nexport * from './gcs-errors.js';\nexport * from './download-token.js';\n"]}
@@ -1,6 +1,5 @@
1
- import type { DocumentReference, DocumentSnapshot, Firestore, Transaction } from "firebase-admin/firestore";
2
- import { type ClaimablePendingMedia, type PendingMediaClaimOutcome, type PendingMediaClaimPolicy, type PendingMediaFinalizeResult, type PendingMediaTerminalStatus } from "@ttt-productions/media-schemas";
3
- /** The app's pending-media row schema — anything with zod's `safeParse` shape. */
1
+ import type { DocumentReference, DocumentSnapshot, Firestore, Transaction } from 'firebase-admin/firestore';
2
+ import { type ClaimablePendingMedia, type PendingMediaClaimOutcome, type PendingMediaClaimPolicy, type PendingMediaFinalizeResult, type PendingMediaTerminalStatus } from '@ttt-productions/media-schemas';
4
3
  export interface PendingMediaRowParser<TRow> {
5
4
  safeParse(data: unknown): {
6
5
  success: true;
@@ -12,54 +11,24 @@ export interface PendingMediaRowParser<TRow> {
12
11
  export interface ClaimPendingMediaOptions<TRow> {
13
12
  schema: PendingMediaRowParser<TRow>;
14
13
  policy: PendingMediaClaimPolicy;
15
- /** Epoch ms; defaults to `Date.now()`. */
16
14
  now?: number;
17
15
  }
18
- /**
19
- * Claim a row for processing inside `transaction`, driven by the CURRENT row (never a
20
- * frozen event snapshot). Reads before any write. A row that no longer parses is
21
- * `missing` and never dispatched: a corrupt row must not drive the processor.
22
- */
23
16
  export declare function claimPendingMediaForProcessing<TRow extends ClaimablePendingMedia>(transaction: Transaction, pendingMediaRef: DocumentReference, options: ClaimPendingMediaOptions<TRow>): Promise<PendingMediaClaimOutcome<TRow>>;
24
17
  export interface PendingMediaFinalizeContext {
25
18
  targetStatus: PendingMediaTerminalStatus;
26
- /** True when the row failed schema validation and is being failed as unparsable. */
27
19
  unparsable: boolean;
28
20
  }
29
- /**
30
- * Caller-owned writes that must commit atomically with a terminal transition, and only
31
- * when it applies (releasing a quota reservation, for example — exactly once, because
32
- * the compare-and-set gates it). It runs in the transaction's READ phase and may
33
- * `transaction.get`; it returns the WRITE-phase step, or undefined when there is nothing
34
- * to write. The step runs after the terminal write, and its return value is surfaced as
35
- * the outcome's `extra`.
36
- */
37
21
  export type PendingMediaExtraWrites<TExtra> = (transaction: Transaction, snapshot: DocumentSnapshot, context: PendingMediaFinalizeContext) => Promise<(() => TExtra) | undefined>;
38
22
  export interface FinalizePendingMediaOptions<TExtra> {
39
- /** Merged into the terminal fields (`result`, `errorCategory`, `errorMessage`, …). */
40
23
  extraFields?: Record<string, unknown>;
41
24
  extraWrites?: PendingMediaExtraWrites<TExtra>;
42
- /** Epoch ms; defaults to `Date.now()`. */
43
25
  now?: number;
44
26
  }
45
27
  export interface PendingMediaFinalizeOutcome<TExtra> extends PendingMediaFinalizeResult {
46
- /** What the extra writes returned — present only on `applied`, when they wrote. */
47
28
  extra?: TExtra;
48
29
  }
49
- /**
50
- * Compare-and-set finalize inside the caller's transaction: re-read the row, decide,
51
- * and write the terminal fields (plus any extra writes) only on `applied`. An
52
- * already-terminal row is never overwritten. Reads-before-writes holds as long as the
53
- * caller has issued no write on `transaction` yet.
54
- */
30
+ /** The caller must not have written on `transaction` yet: a Firestore transaction reads before its first write. */
55
31
  export declare function finalizePendingMediaInTransaction<TExtra = never>(transaction: Transaction, ref: DocumentReference, targetStatus: PendingMediaTerminalStatus, options?: FinalizePendingMediaOptions<TExtra>): Promise<PendingMediaFinalizeOutcome<TExtra>>;
56
- /** Standalone compare-and-set finalize, in its own transaction. */
57
32
  export declare function finalizePendingMedia<TExtra = never>(db: Firestore, ref: DocumentReference, targetStatus: PendingMediaTerminalStatus, options?: FinalizePendingMediaOptions<TExtra>): Promise<PendingMediaFinalizeOutcome<TExtra>>;
58
- /**
59
- * Fail a row that no longer passes schema validation, so the transition table cannot
60
- * be applied (it may carry no readable `status`). One transaction: the row is failed —
61
- * with any extra writes — only while it has no status or a `pending` / `processing`
62
- * one, so an already-terminal row is never touched and extra writes run exactly once.
63
- */
64
33
  export declare function finalizeUnparsablePendingMedia<TExtra = never>(db: Firestore, ref: DocumentReference, options?: FinalizePendingMediaOptions<TExtra>): Promise<void>;
65
34
  //# sourceMappingURL=pending-media.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pending-media.d.ts","sourceRoot":"","sources":["../../src/server/pending-media.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5G,OAAO,EAML,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAChC,MAAM,gCAAgC,CAAC;AAMxC,kFAAkF;AAClF,MAAM,WAAW,qBAAqB,CAAC,IAAI;IACzC,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,KAAK,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,wBAAwB,CAAC,IAAI;IAC5C,MAAM,EAAE,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,EAAE,uBAAuB,CAAC;IAChC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,SAAS,qBAAqB,EACrF,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,wBAAwB,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAWzC;AAMD,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,0BAA0B,CAAC;IACzC,oFAAoF;IACpF,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,CAAC,MAAM,IAAI,CAC5C,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,2BAA2B,KACjC,OAAO,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;AAEzC,MAAM,WAAW,2BAA2B,CAAC,MAAM;IACjD,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAC9C,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B,CAAC,MAAM,CAAE,SAAQ,0BAA0B;IACrF,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAsB,iCAAiC,CAAC,MAAM,GAAG,KAAK,EACpE,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,0BAA0B,EACxC,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAW9C;AAED,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,MAAM,GAAG,KAAK,EACjD,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,0BAA0B,EACxC,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAE9C;AAED;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAAC,MAAM,GAAG,KAAK,EACjE,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,iBAAiB,EACtB,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,IAAI,CAAC,CAYf"}
1
+ {"version":3,"file":"pending-media.d.ts","sourceRoot":"","sources":["../../src/server/pending-media.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAML,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAChC,MAAM,gCAAgC,CAAC;AAExC,MAAM,WAAW,qBAAqB,CAAC,IAAI;IACzC,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,KAAK,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,wBAAwB,CAAC,IAAI;IAC5C,MAAM,EAAE,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,EAAE,uBAAuB,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,8BAA8B,CAAC,IAAI,SAAS,qBAAqB,EACrF,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,wBAAwB,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAezC;AAED,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,0BAA0B,CAAC;IACzC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,uBAAuB,CAAC,MAAM,IAAI,CAC5C,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,2BAA2B,KACjC,OAAO,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;AAEzC,MAAM,WAAW,2BAA2B,CAAC,MAAM;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B,CAAC,MAAM,CAAE,SAAQ,0BAA0B;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,mHAAmH;AACnH,wBAAsB,iCAAiC,CAAC,MAAM,GAAG,KAAK,EACpE,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,0BAA0B,EACxC,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAqB9C;AAED,wBAAgB,oBAAoB,CAAC,MAAM,GAAG,KAAK,EACjD,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,0BAA0B,EACxC,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAI9C;AAED,wBAAsB,8BAA8B,CAAC,MAAM,GAAG,KAAK,EACjE,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,iBAAiB,EACtB,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -1,53 +1,33 @@
1
- // Admin-SDK transaction wrappers for the pending-media lifecycle. The decisions are the
2
- // pure ones in `@ttt-productions/media-schemas` (`decidePendingMediaClaim`,
3
- // `decidePendingMediaFinalize`); these read the CURRENT row inside a transaction,
4
- // decide, and write. Everything app-specific is injected: the row's `DocumentReference`
5
- // (so no collection name lives here), the row schema, the claim policy, the extra
6
- // writes a terminal transition carries, and the clock.
7
- import { buildPendingMediaTerminalFields, decidePendingMediaClaim, decidePendingMediaFinalize, isPendingMediaNonTerminalStatus, PendingMediaStatusSchema, } from "@ttt-productions/media-schemas";
8
- /**
9
- * Claim a row for processing inside `transaction`, driven by the CURRENT row (never a
10
- * frozen event snapshot). Reads before any write. A row that no longer parses is
11
- * `missing` and never dispatched: a corrupt row must not drive the processor.
12
- */
1
+ import { buildPendingMediaTerminalFields, decidePendingMediaClaim, decidePendingMediaFinalize, isPendingMediaNonTerminalStatus, PendingMediaStatusSchema, } from '@ttt-productions/media-schemas';
13
2
  export async function claimPendingMediaForProcessing(transaction, pendingMediaRef, options) {
14
3
  const snap = await transaction.get(pendingMediaRef);
15
4
  if (!snap.exists)
16
- return { kind: "missing" };
5
+ return { kind: 'missing' };
17
6
  const parsed = options.schema.safeParse(snap.data());
18
7
  if (!parsed.success)
19
- return { kind: "missing" };
8
+ return { kind: 'missing' };
20
9
  const { outcome, write } = decidePendingMediaClaim(parsed.data, options.now ?? Date.now(), options.policy);
21
10
  // Spread into a literal: the Admin SDK's update() wants an UpdateData-compatible object.
22
11
  if (write)
23
12
  transaction.update(pendingMediaRef, { ...write });
24
13
  return outcome;
25
14
  }
26
- /**
27
- * Compare-and-set finalize inside the caller's transaction: re-read the row, decide,
28
- * and write the terminal fields (plus any extra writes) only on `applied`. An
29
- * already-terminal row is never overwritten. Reads-before-writes holds as long as the
30
- * caller has issued no write on `transaction` yet.
31
- */
15
+ /** The caller must not have written on `transaction` yet: a Firestore transaction reads before its first write. */
32
16
  export async function finalizePendingMediaInTransaction(transaction, ref, targetStatus, options = {}) {
33
17
  const snap = await transaction.get(ref);
34
18
  const result = decidePendingMediaFinalize(snap.exists ? (snap.data() ?? {}) : undefined, targetStatus);
35
- if (result.decision !== "applied")
19
+ if (result.decision !== 'applied')
36
20
  return result;
37
- const applyExtra = await options.extraWrites?.(transaction, snap, { targetStatus, unparsable: false });
21
+ const applyExtra = await options.extraWrites?.(transaction, snap, {
22
+ targetStatus,
23
+ unparsable: false,
24
+ });
38
25
  transaction.update(ref, buildPendingMediaTerminalFields(targetStatus, options.extraFields ?? {}, options.now ?? Date.now()));
39
26
  return applyExtra ? { ...result, extra: applyExtra() } : result;
40
27
  }
41
- /** Standalone compare-and-set finalize, in its own transaction. */
42
28
  export function finalizePendingMedia(db, ref, targetStatus, options = {}) {
43
29
  return db.runTransaction((transaction) => finalizePendingMediaInTransaction(transaction, ref, targetStatus, options));
44
30
  }
45
- /**
46
- * Fail a row that no longer passes schema validation, so the transition table cannot
47
- * be applied (it may carry no readable `status`). One transaction: the row is failed —
48
- * with any extra writes — only while it has no status or a `pending` / `processing`
49
- * one, so an already-terminal row is never touched and extra writes run exactly once.
50
- */
51
31
  export async function finalizeUnparsablePendingMedia(db, ref, options = {}) {
52
32
  await db.runTransaction(async (transaction) => {
53
33
  const snap = await transaction.get(ref);
@@ -57,7 +37,10 @@ export async function finalizeUnparsablePendingMedia(db, ref, options = {}) {
57
37
  if (status !== undefined && !isPendingMediaNonTerminalStatus(status))
58
38
  return;
59
39
  const failed = PendingMediaStatusSchema.enum.failed;
60
- const applyExtra = await options.extraWrites?.(transaction, snap, { targetStatus: failed, unparsable: true });
40
+ const applyExtra = await options.extraWrites?.(transaction, snap, {
41
+ targetStatus: failed,
42
+ unparsable: true,
43
+ });
61
44
  transaction.update(ref, buildPendingMediaTerminalFields(failed, options.extraFields ?? {}, options.now ?? Date.now()));
62
45
  applyExtra?.();
63
46
  });
@@ -1 +1 @@
1
- {"version":3,"file":"pending-media.js","sourceRoot":"","sources":["../../src/server/pending-media.ts"],"names":[],"mappings":"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","sourcesContent":["// Admin-SDK transaction wrappers for the pending-media lifecycle. The decisions are the\n// pure ones in `@ttt-productions/media-schemas` (`decidePendingMediaClaim`,\n// `decidePendingMediaFinalize`); these read the CURRENT row inside a transaction,\n// decide, and write. Everything app-specific is injected: the row's `DocumentReference`\n// (so no collection name lives here), the row schema, the claim policy, the extra\n// writes a terminal transition carries, and the clock.\n\nimport type { DocumentReference, DocumentSnapshot, Firestore, Transaction } from \"firebase-admin/firestore\";\nimport {\n buildPendingMediaTerminalFields,\n decidePendingMediaClaim,\n decidePendingMediaFinalize,\n isPendingMediaNonTerminalStatus,\n PendingMediaStatusSchema,\n type ClaimablePendingMedia,\n type PendingMediaClaimOutcome,\n type PendingMediaClaimPolicy,\n type PendingMediaFinalizeResult,\n type PendingMediaTerminalStatus,\n} from \"@ttt-productions/media-schemas\";\n\n// ---------------------------------------------------------------------------\n// Claim\n// ---------------------------------------------------------------------------\n\n/** The app's pending-media row schema — anything with zod's `safeParse` shape. */\nexport interface PendingMediaRowParser<TRow> {\n safeParse(data: unknown): { success: true; data: TRow } | { success: false };\n}\n\nexport interface ClaimPendingMediaOptions<TRow> {\n schema: PendingMediaRowParser<TRow>;\n policy: PendingMediaClaimPolicy;\n /** Epoch ms; defaults to `Date.now()`. */\n now?: number;\n}\n\n/**\n * Claim a row for processing inside `transaction`, driven by the CURRENT row (never a\n * frozen event snapshot). Reads before any write. A row that no longer parses is\n * `missing` and never dispatched: a corrupt row must not drive the processor.\n */\nexport async function claimPendingMediaForProcessing<TRow extends ClaimablePendingMedia>(\n transaction: Transaction,\n pendingMediaRef: DocumentReference,\n options: ClaimPendingMediaOptions<TRow>,\n): Promise<PendingMediaClaimOutcome<TRow>> {\n const snap = await transaction.get(pendingMediaRef);\n if (!snap.exists) return { kind: \"missing\" };\n\n const parsed = options.schema.safeParse(snap.data());\n if (!parsed.success) return { kind: \"missing\" };\n\n const { outcome, write } = decidePendingMediaClaim(parsed.data, options.now ?? Date.now(), options.policy);\n // Spread into a literal: the Admin SDK's update() wants an UpdateData-compatible object.\n if (write) transaction.update(pendingMediaRef, { ...write });\n return outcome;\n}\n\n// ---------------------------------------------------------------------------\n// Finalize\n// ---------------------------------------------------------------------------\n\nexport interface PendingMediaFinalizeContext {\n targetStatus: PendingMediaTerminalStatus;\n /** True when the row failed schema validation and is being failed as unparsable. */\n unparsable: boolean;\n}\n\n/**\n * Caller-owned writes that must commit atomically with a terminal transition, and only\n * when it applies (releasing a quota reservation, for example — exactly once, because\n * the compare-and-set gates it). It runs in the transaction's READ phase and may\n * `transaction.get`; it returns the WRITE-phase step, or undefined when there is nothing\n * to write. The step runs after the terminal write, and its return value is surfaced as\n * the outcome's `extra`.\n */\nexport type PendingMediaExtraWrites<TExtra> = (\n transaction: Transaction,\n snapshot: DocumentSnapshot,\n context: PendingMediaFinalizeContext,\n) => Promise<(() => TExtra) | undefined>;\n\nexport interface FinalizePendingMediaOptions<TExtra> {\n /** Merged into the terminal fields (`result`, `errorCategory`, `errorMessage`, …). */\n extraFields?: Record<string, unknown>;\n extraWrites?: PendingMediaExtraWrites<TExtra>;\n /** Epoch ms; defaults to `Date.now()`. */\n now?: number;\n}\n\nexport interface PendingMediaFinalizeOutcome<TExtra> extends PendingMediaFinalizeResult {\n /** What the extra writes returned — present only on `applied`, when they wrote. */\n extra?: TExtra;\n}\n\n/**\n * Compare-and-set finalize inside the caller's transaction: re-read the row, decide,\n * and write the terminal fields (plus any extra writes) only on `applied`. An\n * already-terminal row is never overwritten. Reads-before-writes holds as long as the\n * caller has issued no write on `transaction` yet.\n */\nexport async function finalizePendingMediaInTransaction<TExtra = never>(\n transaction: Transaction,\n ref: DocumentReference,\n targetStatus: PendingMediaTerminalStatus,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<PendingMediaFinalizeOutcome<TExtra>> {\n const snap = await transaction.get(ref);\n const result = decidePendingMediaFinalize(snap.exists ? (snap.data() ?? {}) : undefined, targetStatus);\n if (result.decision !== \"applied\") return result;\n\n const applyExtra = await options.extraWrites?.(transaction, snap, { targetStatus, unparsable: false });\n transaction.update(\n ref,\n buildPendingMediaTerminalFields(targetStatus, options.extraFields ?? {}, options.now ?? Date.now()),\n );\n return applyExtra ? { ...result, extra: applyExtra() } : result;\n}\n\n/** Standalone compare-and-set finalize, in its own transaction. */\nexport function finalizePendingMedia<TExtra = never>(\n db: Firestore,\n ref: DocumentReference,\n targetStatus: PendingMediaTerminalStatus,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<PendingMediaFinalizeOutcome<TExtra>> {\n return db.runTransaction((transaction) => finalizePendingMediaInTransaction(transaction, ref, targetStatus, options));\n}\n\n/**\n * Fail a row that no longer passes schema validation, so the transition table cannot\n * be applied (it may carry no readable `status`). One transaction: the row is failed —\n * with any extra writes — only while it has no status or a `pending` / `processing`\n * one, so an already-terminal row is never touched and extra writes run exactly once.\n */\nexport async function finalizeUnparsablePendingMedia<TExtra = never>(\n db: Firestore,\n ref: DocumentReference,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<void> {\n await db.runTransaction(async (transaction) => {\n const snap = await transaction.get(ref);\n if (!snap.exists) return;\n const status = (snap.data() as { status?: unknown } | undefined)?.status;\n if (status !== undefined && !isPendingMediaNonTerminalStatus(status)) return;\n\n const failed = PendingMediaStatusSchema.enum.failed;\n const applyExtra = await options.extraWrites?.(transaction, snap, { targetStatus: failed, unparsable: true });\n transaction.update(ref, buildPendingMediaTerminalFields(failed, options.extraFields ?? {}, options.now ?? Date.now()));\n applyExtra?.();\n });\n}\n"]}
1
+ {"version":3,"file":"pending-media.js","sourceRoot":"","sources":["../../src/server/pending-media.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DocumentReference,\n DocumentSnapshot,\n Firestore,\n Transaction,\n} from 'firebase-admin/firestore';\nimport {\n buildPendingMediaTerminalFields,\n decidePendingMediaClaim,\n decidePendingMediaFinalize,\n isPendingMediaNonTerminalStatus,\n PendingMediaStatusSchema,\n type ClaimablePendingMedia,\n type PendingMediaClaimOutcome,\n type PendingMediaClaimPolicy,\n type PendingMediaFinalizeResult,\n type PendingMediaTerminalStatus,\n} from '@ttt-productions/media-schemas';\n\nexport interface PendingMediaRowParser<TRow> {\n safeParse(data: unknown): { success: true; data: TRow } | { success: false };\n}\n\nexport interface ClaimPendingMediaOptions<TRow> {\n schema: PendingMediaRowParser<TRow>;\n policy: PendingMediaClaimPolicy;\n now?: number;\n}\n\nexport async function claimPendingMediaForProcessing<TRow extends ClaimablePendingMedia>(\n transaction: Transaction,\n pendingMediaRef: DocumentReference,\n options: ClaimPendingMediaOptions<TRow>,\n): Promise<PendingMediaClaimOutcome<TRow>> {\n const snap = await transaction.get(pendingMediaRef);\n if (!snap.exists) return { kind: 'missing' };\n\n const parsed = options.schema.safeParse(snap.data());\n if (!parsed.success) return { kind: 'missing' };\n\n const { outcome, write } = decidePendingMediaClaim(\n parsed.data,\n options.now ?? Date.now(),\n options.policy,\n );\n // Spread into a literal: the Admin SDK's update() wants an UpdateData-compatible object.\n if (write) transaction.update(pendingMediaRef, { ...write });\n return outcome;\n}\n\nexport interface PendingMediaFinalizeContext {\n targetStatus: PendingMediaTerminalStatus;\n unparsable: boolean;\n}\n\nexport type PendingMediaExtraWrites<TExtra> = (\n transaction: Transaction,\n snapshot: DocumentSnapshot,\n context: PendingMediaFinalizeContext,\n) => Promise<(() => TExtra) | undefined>;\n\nexport interface FinalizePendingMediaOptions<TExtra> {\n extraFields?: Record<string, unknown>;\n extraWrites?: PendingMediaExtraWrites<TExtra>;\n now?: number;\n}\n\nexport interface PendingMediaFinalizeOutcome<TExtra> extends PendingMediaFinalizeResult {\n extra?: TExtra;\n}\n\n/** The caller must not have written on `transaction` yet: a Firestore transaction reads before its first write. */\nexport async function finalizePendingMediaInTransaction<TExtra = never>(\n transaction: Transaction,\n ref: DocumentReference,\n targetStatus: PendingMediaTerminalStatus,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<PendingMediaFinalizeOutcome<TExtra>> {\n const snap = await transaction.get(ref);\n const result = decidePendingMediaFinalize(\n snap.exists ? (snap.data() ?? {}) : undefined,\n targetStatus,\n );\n if (result.decision !== 'applied') return result;\n\n const applyExtra = await options.extraWrites?.(transaction, snap, {\n targetStatus,\n unparsable: false,\n });\n transaction.update(\n ref,\n buildPendingMediaTerminalFields(\n targetStatus,\n options.extraFields ?? {},\n options.now ?? Date.now(),\n ),\n );\n return applyExtra ? { ...result, extra: applyExtra() } : result;\n}\n\nexport function finalizePendingMedia<TExtra = never>(\n db: Firestore,\n ref: DocumentReference,\n targetStatus: PendingMediaTerminalStatus,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<PendingMediaFinalizeOutcome<TExtra>> {\n return db.runTransaction((transaction) =>\n finalizePendingMediaInTransaction(transaction, ref, targetStatus, options),\n );\n}\n\nexport async function finalizeUnparsablePendingMedia<TExtra = never>(\n db: Firestore,\n ref: DocumentReference,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<void> {\n await db.runTransaction(async (transaction) => {\n const snap = await transaction.get(ref);\n if (!snap.exists) return;\n const status = (snap.data() as { status?: unknown } | undefined)?.status;\n if (status !== undefined && !isPendingMediaNonTerminalStatus(status)) return;\n\n const failed = PendingMediaStatusSchema.enum.failed;\n const applyExtra = await options.extraWrites?.(transaction, snap, {\n targetStatus: failed,\n unparsable: true,\n });\n transaction.update(\n ref,\n buildPendingMediaTerminalFields(failed, options.extraFields ?? {}, options.now ?? Date.now()),\n );\n applyExtra?.();\n });\n}\n"]}
@@ -1,71 +1,29 @@
1
- /** TOTAL attempts (initial + retries) — three totals means at most TWO delays. */
2
1
  export declare const R2_REQUEST_MAX_ATTEMPTS = 3;
3
- /** Base delay for full-jitter exponential backoff. */
4
2
  export declare const R2_RETRY_BASE_DELAY_MS = 250;
5
- /** Ceiling on the exponential backoff (before jitter). */
6
3
  export declare const R2_RETRY_MAX_BACKOFF_MS = 5000;
7
- /** Hard cap on an honored server `Retry-After` so one response can't eat the
8
- * caller's whole timeout budget. */
9
4
  export declare const R2_RETRY_AFTER_CAP_MS = 30000;
10
- /** HTTP statuses treated as transient and safe to retry. */
11
5
  export declare const R2_RETRYABLE_STATUSES: ReadonlySet<number>;
12
- export type R2RetryableOperation = "putFile" | "copy" | "readToFile" | "deleteCopy";
13
- /** Every R2 store operation an `R2StorageError` can name; `delete` never retries. */
14
- export type R2Operation = R2RetryableOperation | "delete";
15
- /** Deterministic seams for tests; production defaults are internal (see below). */
6
+ export type R2RetryableOperation = 'putFile' | 'copy' | 'readToFile' | 'deleteCopy';
7
+ export type R2Operation = R2RetryableOperation | 'delete';
16
8
  export interface R2RetrySeams {
17
- /** Resolve after `ms` milliseconds. */
18
9
  sleep: (ms: number) => Promise<void>;
19
- /** Uniform random in [0, 1). */
20
10
  random: () => number;
21
- /** Epoch-millisecond clock — keeps HTTP-date `Retry-After` tests off wall-clock. */
22
11
  now: () => number;
23
12
  }
24
- /** Production seam defaults. */
25
13
  export declare function defaultR2RetrySeams(): R2RetrySeams;
26
- /**
27
- * True only for recognized transient transport failures. An explicit
28
- * `AbortError` and deterministic local-filesystem errors always return false —
29
- * an abort must never become another attempt.
30
- */
31
14
  export declare function isRetryableTransportError(err: unknown): boolean;
32
- /**
33
- * Parse a `Retry-After` header value into a non-negative millisecond delay.
34
- * Supports both the delta-seconds form (`"120"`) and the HTTP-date form
35
- * (`"Wed, 21 Oct 2015 07:28:00 GMT"`). Returns null when absent/unparseable.
36
- * An HTTP-date in the past clamps to 0.
37
- */
38
15
  export declare function parseRetryAfterMs(headerValue: string | null | undefined, nowMs: number): number | null;
39
- /**
40
- * Compute the pre-next-attempt delay. If the failed response carries a valid
41
- * `Retry-After`, honor it (capped, no backoff added on top). Otherwise use
42
- * full-jitter exponential backoff: `random(0, min(maxBackoff, base * 2^retryIndex))`.
43
- */
44
16
  export declare function computeR2DelayMs(params: {
45
17
  response: Response | null;
46
- /** 0-based index of the retry about to happen (0 for the first retry). */
47
18
  retryIndex: number;
48
19
  seams: R2RetrySeams;
49
20
  }): number;
50
- /**
51
- * Thrown when an R2 operation fails: a retried operation exhausts its attempts or
52
- * hits a non-retryable response, or a one-shot delete fails. Carries
53
- * operation/bucket/key/attempt/status context, the S3 error code parsed from the
54
- * response body, and the bounded body itself as properties — never credentials,
55
- * authorization headers, or full signed URLs. Callers classify it from those
56
- * properties, never from the message.
57
- *
58
- * The message names only the operation, the attempts, the status, and the S3 error
59
- * code. The bucket, the key, and the raw body — which can echo either — stay out of
60
- * it, so an escaping error never carries an object key to monitoring.
61
- */
62
21
  export declare class R2StorageError extends Error {
63
22
  readonly operation: R2Operation;
64
23
  readonly bucket: string;
65
24
  readonly key: string;
66
25
  readonly attempts: number;
67
26
  readonly status?: number;
68
- /** The S3 `<Code>` of the response body (`NoSuchKey`, `PreconditionFailed`, …), when it had one. */
69
27
  readonly s3ErrorCode?: string;
70
28
  readonly responseText?: string;
71
29
  constructor(params: {
@@ -79,18 +37,12 @@ export declare class R2StorageError extends Error {
79
37
  });
80
38
  }
81
39
  export type R2AttemptResult<T> = {
82
- kind: "success";
40
+ kind: 'success';
83
41
  value: T;
84
42
  } | {
85
- kind: "response";
43
+ kind: 'response';
86
44
  response: Response;
87
45
  };
88
- /**
89
- * Drive up to `R2_REQUEST_MAX_ATTEMPTS` attempts of a single storage operation.
90
- * `runAttempt` builds a FRESH signed request each call and returns either a
91
- * success value or the raw `Response` for the driver to classify. Thrown errors
92
- * are classified: only recognized transient transport failures are retried.
93
- */
94
46
  export declare function fetchWithR2Retry<T>(params: {
95
47
  operation: R2RetryableOperation;
96
48
  bucket: string;
@@ -1 +1 @@
1
- {"version":3,"file":"r2-retry.d.ts","sourceRoot":"","sources":["../../src/server/r2-retry.ts"],"names":[],"mappings":"AAiBA,kFAAkF;AAClF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,sDAAsD;AACtD,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,0DAA0D;AAC1D,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAC7C;qCACqC;AACrC,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,4DAA4D;AAC5D,eAAO,MAAM,qBAAqB,EAAE,WAAW,CAAC,MAAM,CAA2C,CAAC;AAElG,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,YAAY,CAAC;AAEpF,qFAAqF;AACrF,MAAM,MAAM,WAAW,GAAG,oBAAoB,GAAG,QAAQ,CAAC;AAE1D,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,gCAAgC;IAChC,MAAM,EAAE,MAAM,MAAM,CAAC;IACrB,oFAAoF;IACpF,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,gCAAgC;AAChC,wBAAgB,mBAAmB,IAAI,YAAY,CAMlD;AAsDD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAwB/D;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBtG;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC;CACrB,GAAG,MAAM,CAYT;AAMD;;;;;;;;;;;GAWG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,oGAAoG;IACpG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAEnB,MAAM,EAAE;QAClB,SAAS,EAAE,WAAW,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CAgBF;AAWD,MAAM,MAAM,eAAe,CAAC,CAAC,IACzB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAyB7C;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE;IAChD,SAAS,EAAE,oBAAoB,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,YAAY,CAAC;IACpB,UAAU,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;CACpE,GAAG,OAAO,CAAC,CAAC,CAAC,CAoCb"}
1
+ {"version":3,"file":"r2-retry.d.ts","sourceRoot":"","sources":["../../src/server/r2-retry.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAC7C,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,eAAO,MAAM,qBAAqB,EAAE,WAAW,CAAC,MAAM,CAA2C,CAAC;AAElG,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,YAAY,CAAC;AAEpF,MAAM,MAAM,WAAW,GAAG,oBAAoB,GAAG,QAAQ,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,EAAE,MAAM,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,wBAAgB,mBAAmB,IAAI,YAAY,CAMlD;AA8CD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAmB/D;AAED,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACtC,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAef;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC;CACrB,GAAG,MAAM,CAYT;AAED,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAEnB,MAAM,EAAE;QAClB,SAAS,EAAE,WAAW,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CAkBF;AAMD,MAAM,MAAM,eAAe,CAAC,CAAC,IAC3B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAqB3E,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE;IAChD,SAAS,EAAE,oBAAoB,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,YAAY,CAAC;IACpB,UAAU,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;CACpE,GAAG,OAAO,CAAC,CAAC,CAAC,CA+Bb"}