@ttt-productions/media-processing-core 0.8.15 → 0.8.17

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 (132) 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 +62 -48
  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 -3
  8. package/dist/audio/probe.js.map +1 -1
  9. package/dist/duration/media-duration.d.ts +2 -2
  10. package/dist/duration/media-duration.d.ts.map +1 -1
  11. package/dist/duration/media-duration.js +23 -21
  12. package/dist/duration/media-duration.js.map +1 -1
  13. package/dist/image/formats.d.ts +2 -2
  14. package/dist/image/formats.js +25 -25
  15. package/dist/image/formats.js.map +1 -1
  16. package/dist/image/image-processor.d.ts +2 -2
  17. package/dist/image/image-processor.d.ts.map +1 -1
  18. package/dist/image/image-processor.js +47 -37
  19. package/dist/image/image-processor.js.map +1 -1
  20. package/dist/image/resize.d.ts +2 -2
  21. package/dist/image/resize.d.ts.map +1 -1
  22. package/dist/image/resize.js +13 -13
  23. package/dist/image/resize.js.map +1 -1
  24. package/dist/index.d.ts +19 -16
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +18 -16
  27. package/dist/index.js.map +1 -1
  28. package/dist/inspection/inspect-media.d.ts +13 -13
  29. package/dist/inspection/inspect-media.d.ts.map +1 -1
  30. package/dist/inspection/inspect-media.js +196 -140
  31. package/dist/inspection/inspect-media.js.map +1 -1
  32. package/dist/io/fs.d.ts +1 -1
  33. package/dist/io/fs.js +4 -4
  34. package/dist/io/fs.js.map +1 -1
  35. package/dist/io/local-input.d.ts +1 -1
  36. package/dist/io/local-input.d.ts.map +1 -1
  37. package/dist/io/local-input.js +15 -15
  38. package/dist/io/local-input.js.map +1 -1
  39. package/dist/io/types.d.ts.map +1 -1
  40. package/dist/io/types.js.map +1 -1
  41. package/dist/moderation/merge.d.ts +1 -1
  42. package/dist/moderation/merge.d.ts.map +1 -1
  43. package/dist/moderation/merge.js +1 -1
  44. package/dist/moderation/merge.js.map +1 -1
  45. package/dist/moderation/types.d.ts +3 -3
  46. package/dist/moderation/types.js.map +1 -1
  47. package/dist/process-media.d.ts +2 -3
  48. package/dist/process-media.d.ts.map +1 -1
  49. package/dist/process-media.js +9 -9
  50. package/dist/process-media.js.map +1 -1
  51. package/dist/processing/errors.d.ts.map +1 -1
  52. package/dist/processing/errors.js +1 -1
  53. package/dist/processing/errors.js.map +1 -1
  54. package/dist/processing/result.d.ts +2 -2
  55. package/dist/processing/result.js +1 -1
  56. package/dist/processing/result.js.map +1 -1
  57. package/dist/run-pipeline.d.ts +5 -5
  58. package/dist/run-pipeline.d.ts.map +1 -1
  59. package/dist/run-pipeline.js +70 -54
  60. package/dist/run-pipeline.js.map +1 -1
  61. package/dist/server/download-token.d.ts +14 -0
  62. package/dist/server/download-token.d.ts.map +1 -0
  63. package/dist/server/download-token.js +19 -0
  64. package/dist/server/download-token.js.map +1 -0
  65. package/dist/server/firebase-media-io.d.ts +2 -2
  66. package/dist/server/firebase-media-io.js +1 -1
  67. package/dist/server/firebase-media-io.js.map +1 -1
  68. package/dist/server/gcs-errors.js +2 -2
  69. package/dist/server/gcs-errors.js.map +1 -1
  70. package/dist/server/index.d.ts +7 -5
  71. package/dist/server/index.d.ts.map +1 -1
  72. package/dist/server/index.js +7 -5
  73. package/dist/server/index.js.map +1 -1
  74. package/dist/server/pending-media.d.ts +2 -2
  75. package/dist/server/pending-media.d.ts.map +1 -1
  76. package/dist/server/pending-media.js +12 -6
  77. package/dist/server/pending-media.js.map +1 -1
  78. package/dist/server/r2-retry.d.ts +4 -4
  79. package/dist/server/r2-retry.d.ts.map +1 -1
  80. package/dist/server/r2-retry.js +48 -47
  81. package/dist/server/r2-retry.js.map +1 -1
  82. package/dist/server/staged-object.d.ts +2 -2
  83. package/dist/server/staged-object.d.ts.map +1 -1
  84. package/dist/server/staged-object.js +4 -4
  85. package/dist/server/staged-object.js.map +1 -1
  86. package/dist/server/storage-ops.d.ts +3 -5
  87. package/dist/server/storage-ops.d.ts.map +1 -1
  88. package/dist/server/storage-ops.js +101 -97
  89. package/dist/server/storage-ops.js.map +1 -1
  90. package/dist/types.d.ts +2 -2
  91. package/dist/types.js.map +1 -1
  92. package/dist/utils/log.d.ts.map +1 -1
  93. package/dist/utils/log.js +3 -3
  94. package/dist/utils/log.js.map +1 -1
  95. package/dist/utils/paths.d.ts.map +1 -1
  96. package/dist/utils/paths.js +1 -4
  97. package/dist/utils/paths.js.map +1 -1
  98. package/dist/utils/safe-path.js +8 -8
  99. package/dist/utils/safe-path.js.map +1 -1
  100. package/dist/validation/validate-duration.d.ts.map +1 -1
  101. package/dist/validation/validate-duration.js.map +1 -1
  102. package/dist/validation/validate-mime.d.ts.map +1 -1
  103. package/dist/validation/validate-mime.js.map +1 -1
  104. package/dist/validation/validate-size.d.ts.map +1 -1
  105. package/dist/validation/validate-size.js.map +1 -1
  106. package/dist/video/ffmpeg.d.ts +1 -1
  107. package/dist/video/ffmpeg.d.ts.map +1 -1
  108. package/dist/video/ffmpeg.js +51 -43
  109. package/dist/video/ffmpeg.js.map +1 -1
  110. package/dist/video/probe.d.ts +1 -1
  111. package/dist/video/probe.d.ts.map +1 -1
  112. package/dist/video/probe.js +10 -7
  113. package/dist/video/probe.js.map +1 -1
  114. package/dist/video/video-processor.d.ts +2 -2
  115. package/dist/video/video-processor.d.ts.map +1 -1
  116. package/dist/video/video-processor.js +123 -81
  117. package/dist/video/video-processor.js.map +1 -1
  118. package/dist/workspace/temp.js +4 -4
  119. package/dist/workspace/temp.js.map +1 -1
  120. package/package.json +2 -2
  121. package/dist/audio/index.d.ts +0 -3
  122. package/dist/audio/index.d.ts.map +0 -1
  123. package/dist/audio/index.js +0 -3
  124. package/dist/audio/index.js.map +0 -1
  125. package/dist/image/index.d.ts +0 -4
  126. package/dist/image/index.d.ts.map +0 -1
  127. package/dist/image/index.js +0 -4
  128. package/dist/image/index.js.map +0 -1
  129. package/dist/video/index.d.ts +0 -4
  130. package/dist/video/index.d.ts.map +0 -1
  131. package/dist/video/index.js +0 -4
  132. package/dist/video/index.js.map +0 -1
@@ -17,18 +17,15 @@
17
17
  //
18
18
  // Writes return `{ key }` — never URLs. Display URLs are built at render time
19
19
  // from asset refs (see the consuming app's media-asset-url helper).
20
- import { createReadStream, createWriteStream } from "node:fs";
21
- import { stat } from "node:fs/promises";
22
- import { Readable } from "node:stream";
23
- import { pipeline } from "node:stream/promises";
24
- import { AwsClient } from "aws4fetch";
25
- import { NEUTRAL_CONTENT_TYPE } from "@ttt-productions/media-schemas";
26
- import { isObjectNotFoundError } from "./gcs-errors.js";
27
- import { defaultR2RetrySeams, fetchWithR2Retry, R2StorageError, } from "./r2-retry.js";
28
- import { requireStorageObjectGeneration, UnreadableStorageObjectMetadataError } from "./staged-object.js";
29
- // Re-export the exhaustion error so callers can recognize an exhausted R2
30
- // operation. The rest of the retry helper stays private to this module.
31
- export { R2StorageError };
20
+ import { createReadStream, createWriteStream } from 'node:fs';
21
+ import { stat } from 'node:fs/promises';
22
+ import { Readable } from 'node:stream';
23
+ import { pipeline } from 'node:stream/promises';
24
+ import { AwsClient } from 'aws4fetch';
25
+ import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';
26
+ import { isObjectNotFoundError } from './gcs-errors.js';
27
+ import { defaultR2RetrySeams, fetchWithR2Retry, R2StorageError, } from './r2-retry.js';
28
+ import { requireStorageObjectGeneration, UnreadableStorageObjectMetadataError, } from './staged-object.js';
32
29
  /**
33
30
  * A copy found an object at its destination key that is not this copy's result, and
34
31
  * wrote nothing; that object is untouched. The key is a PROPERTY, never message text, so
@@ -37,17 +34,17 @@ export { R2StorageError };
37
34
  export class ObjectAlreadyExistsError extends Error {
38
35
  key;
39
36
  constructor(key) {
40
- super("copy destination already holds another object");
37
+ super('copy destination already holds another object');
41
38
  this.key = key;
42
- this.name = "ObjectAlreadyExistsError";
39
+ this.name = 'ObjectAlreadyExistsError';
43
40
  }
44
41
  }
45
42
  // Every copy records on its destination which source version it holds. A copy whose
46
43
  // destination already exists compares this record with its own, so it recognizes its
47
44
  // own earlier result and never mistakes another writer's object for it. The values are
48
45
  // URI-encoded because R2 carries custom metadata in HTTP headers, which must be ASCII.
49
- const COPY_SOURCE_METADATA_KEY = "copy-source";
50
- const COPY_SOURCE_VERSION_METADATA_KEY = "copy-source-version";
46
+ const COPY_SOURCE_METADATA_KEY = 'copy-source';
47
+ const COPY_SOURCE_VERSION_METADATA_KEY = 'copy-source-version';
51
48
  function copyProvenance(fromKey, sourceVersion) {
52
49
  return {
53
50
  [COPY_SOURCE_METADATA_KEY]: encodeURIComponent(fromKey),
@@ -66,24 +63,24 @@ function holdsProvenance(readMetadata, provenance) {
66
63
  function recordsCopyOf(readMetadata, fromKey) {
67
64
  const version = readMetadata(COPY_SOURCE_VERSION_METADATA_KEY);
68
65
  return (readMetadata(COPY_SOURCE_METADATA_KEY) === encodeURIComponent(fromKey) &&
69
- typeof version === "string" &&
70
- version !== "");
66
+ typeof version === 'string' &&
67
+ version !== '');
71
68
  }
72
69
  /** The content headers a copy carries from its source: HTTP header name → Cloud Storage field. */
73
70
  const CARRIED_CONTENT_HEADERS = {
74
- "content-type": "contentType",
75
- "cache-control": "cacheControl",
76
- "content-disposition": "contentDisposition",
77
- "content-encoding": "contentEncoding",
71
+ 'content-type': 'contentType',
72
+ 'cache-control': 'cacheControl',
73
+ 'content-disposition': 'contentDisposition',
74
+ 'content-encoding': 'contentEncoding',
78
75
  };
79
- const DEFAULT_CACHE_CONTROL = "public, max-age=31536000, immutable";
76
+ const DEFAULT_CACHE_CONTROL = 'public, max-age=31536000, immutable';
80
77
  export function createR2ObjectStore(args) {
81
- const endpoint = (args.endpoint ?? `https://${args.accountId}.r2.cloudflarestorage.com`).replace(/\/$/, "");
78
+ const endpoint = (args.endpoint ?? `https://${args.accountId}.r2.cloudflarestorage.com`).replace(/\/$/, '');
82
79
  const client = new AwsClient({
83
80
  accessKeyId: args.accessKeyId,
84
81
  secretAccessKey: args.secretAccessKey,
85
- service: "s3",
86
- region: "auto",
82
+ service: 's3',
83
+ region: 'auto',
87
84
  });
88
85
  const doFetch = args.fetchImpl ?? fetch;
89
86
  // Retry seams: production defaults are internal; tests inject deterministic
@@ -95,10 +92,10 @@ export function createR2ObjectStore(args) {
95
92
  random: args.randomImpl ?? defaults.random,
96
93
  now: args.nowImpl ?? defaults.now,
97
94
  };
98
- const objectUrl = (key) => `${endpoint}/${args.bucket}/${key.split("/").map(encodeURIComponent).join("/")}`;
95
+ const objectUrl = (key) => `${endpoint}/${args.bucket}/${key.split('/').map(encodeURIComponent).join('/')}`;
99
96
  async function signedFetch(url, init) {
100
97
  const signed = await client.sign(url, { ...init, aws: { allHeaders: true } });
101
- return doFetch(signed, { ...(init.body ? { duplex: "half" } : {}) });
98
+ return doFetch(signed, { ...(init.body ? { duplex: 'half' } : {}) });
102
99
  }
103
100
  // A GET whose body is discarded unread, not a HEAD: a HEAD's error response carries no
104
101
  // body, so the S3 error code that tells a missing key from a missing bucket would be lost.
@@ -109,11 +106,11 @@ export function createR2ObjectStore(args) {
109
106
  key,
110
107
  seams,
111
108
  runAttempt: async () => {
112
- const res = await signedFetch(objectUrl(key), { method: "GET" });
109
+ const res = await signedFetch(objectUrl(key), { method: 'GET' });
113
110
  if (!res.ok)
114
- return { kind: "response", response: res };
111
+ return { kind: 'response', response: res };
115
112
  await res.body?.cancel();
116
- return { kind: "success", value: res.headers };
113
+ return { kind: 'success', value: res.headers };
117
114
  },
118
115
  });
119
116
  }
@@ -122,15 +119,15 @@ export function createR2ObjectStore(args) {
122
119
  // content has the same MD5-derived ETag and lands within the same second. A multipart
123
120
  // upload's ETag is not the content's MD5, but it is the same kind of version token.
124
121
  function readSourceVersion(headers, key) {
125
- const etag = headers.get("etag");
122
+ const etag = headers.get('etag');
126
123
  if (!etag)
127
- throw new UnreadableStorageObjectMetadataError(args.bucket, key, "etag");
128
- const lastModified = headers.get("last-modified");
124
+ throw new UnreadableStorageObjectMetadataError(args.bucket, key, 'etag');
125
+ const lastModified = headers.get('last-modified');
129
126
  if (!lastModified)
130
- throw new UnreadableStorageObjectMetadataError(args.bucket, key, "lastModified");
131
- const size = headers.get("content-length");
127
+ throw new UnreadableStorageObjectMetadataError(args.bucket, key, 'lastModified');
128
+ const size = headers.get('content-length');
132
129
  if (!size)
133
- throw new UnreadableStorageObjectMetadataError(args.bucket, key, "size");
130
+ throw new UnreadableStorageObjectMetadataError(args.bucket, key, 'size');
134
131
  return { etag, version: `${etag} ${lastModified} ${size}` };
135
132
  }
136
133
  return {
@@ -140,7 +137,7 @@ export function createR2ObjectStore(args) {
140
137
  const { size } = await stat(localPath);
141
138
  const ct = contentType ?? getMimeFromExt(extractExt(localPath));
142
139
  return fetchWithR2Retry({
143
- operation: "putFile",
140
+ operation: 'putFile',
144
141
  bucket: args.bucket,
145
142
  key,
146
143
  seams,
@@ -148,35 +145,35 @@ export function createR2ObjectStore(args) {
148
145
  // consumed stream is never reused.
149
146
  runAttempt: async () => {
150
147
  const res = await signedFetch(objectUrl(key), {
151
- method: "PUT",
148
+ method: 'PUT',
152
149
  headers: {
153
- "content-type": ct,
154
- "content-length": String(size),
155
- "cache-control": DEFAULT_CACHE_CONTROL,
150
+ 'content-type': ct,
151
+ 'content-length': String(size),
152
+ 'cache-control': DEFAULT_CACHE_CONTROL,
156
153
  },
157
154
  body: createReadStream(localPath),
158
155
  });
159
156
  if (res.ok) {
160
- return { kind: "success", value: { key, sizeBytes: size, contentType: ct } };
157
+ return { kind: 'success', value: { key, sizeBytes: size, contentType: ct } };
161
158
  }
162
- return { kind: "response", response: res };
159
+ return { kind: 'response', response: res };
163
160
  },
164
161
  });
165
162
  },
166
163
  async copy({ fromKey, toKey }) {
167
- const source = await readObjectHeaders(fromKey, "copy");
164
+ const source = await readObjectHeaders(fromKey, 'copy');
168
165
  const { etag, version } = readSourceVersion(source, fromKey);
169
166
  const provenance = copyProvenance(fromKey, version);
170
167
  const headers = {
171
- "x-amz-copy-source": `/${args.bucket}/${fromKey.split("/").map(encodeURIComponent).join("/")}`,
168
+ 'x-amz-copy-source': `/${args.bucket}/${fromKey.split('/').map(encodeURIComponent).join('/')}`,
172
169
  // Pins the copy to the source version the provenance names.
173
- "x-amz-copy-source-if-match": etag,
170
+ 'x-amz-copy-source-if-match': etag,
174
171
  // R2's destination-conditional CopyObject extension, checked when the copy commits;
175
172
  // S3's x-amz-copy-source-if-* headers condition only the source.
176
- "cf-copy-destination-if-none-match": "*",
173
+ 'cf-copy-destination-if-none-match': '*',
177
174
  // The destination takes exactly the headers below rather than the source's, so it
178
175
  // commits fully formed in this one call.
179
- "x-amz-metadata-directive": "REPLACE",
176
+ 'x-amz-metadata-directive': 'REPLACE',
180
177
  };
181
178
  for (const name of Object.keys(CARRIED_CONTENT_HEADERS)) {
182
179
  const value = source.get(name);
@@ -187,15 +184,15 @@ export function createR2ObjectStore(args) {
187
184
  headers[`x-amz-meta-${name}`] = value;
188
185
  try {
189
186
  await fetchWithR2Retry({
190
- operation: "copy",
187
+ operation: 'copy',
191
188
  bucket: args.bucket,
192
189
  key: toKey,
193
190
  seams,
194
191
  runAttempt: async () => {
195
- const res = await signedFetch(objectUrl(toKey), { method: "PUT", headers });
192
+ const res = await signedFetch(objectUrl(toKey), { method: 'PUT', headers });
196
193
  if (res.ok)
197
- return { kind: "success", value: undefined };
198
- return { kind: "response", response: res };
194
+ return { kind: 'success', value: undefined };
195
+ return { kind: 'response', response: res };
199
196
  },
200
197
  });
201
198
  return { key: toKey, alreadyPresent: false };
@@ -207,7 +204,7 @@ export function createR2ObjectStore(args) {
207
204
  // after it was read; only an existing destination can be this copy's result.
208
205
  let destination;
209
206
  try {
210
- destination = await readObjectHeaders(toKey, "copy");
207
+ destination = await readObjectHeaders(toKey, 'copy');
211
208
  }
212
209
  catch (readError) {
213
210
  if (isObjectNotFoundError(readError))
@@ -222,35 +219,35 @@ export function createR2ObjectStore(args) {
222
219
  },
223
220
  async readToFile({ key, localPath }) {
224
221
  return fetchWithR2Retry({
225
- operation: "readToFile",
222
+ operation: 'readToFile',
226
223
  bucket: args.bucket,
227
224
  key,
228
225
  seams,
229
226
  runAttempt: async () => {
230
- const res = await signedFetch(objectUrl(key), { method: "GET" });
227
+ const res = await signedFetch(objectUrl(key), { method: 'GET' });
231
228
  if (!res.ok || !res.body) {
232
- return { kind: "response", response: res };
229
+ return { kind: 'response', response: res };
233
230
  }
234
- const contentType = res.headers.get("content-type") ?? NEUTRAL_CONTENT_TYPE;
231
+ const contentType = res.headers.get('content-type') ?? NEUTRAL_CONTENT_TYPE;
235
232
  // Pipe ONLY after a successful response, and open the destination in
236
233
  // truncating mode ('w') on every attempt so a partial download from a
237
234
  // prior attempt can never prefix/corrupt this one. res.body is a web
238
235
  // ReadableStream; stream it to disk so large media never sits fully in
239
236
  // memory. A transient body/network stream failure here throws and is
240
237
  // retried by the helper; a deterministic local fs error is not.
241
- await pipeline(Readable.fromWeb(res.body), createWriteStream(localPath, { flags: "w" }));
238
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(localPath, { flags: 'w' }));
242
239
  const { size } = await stat(localPath);
243
- return { kind: "success", value: { sizeBytes: size, contentType } };
240
+ return { kind: 'success', value: { sizeBytes: size, contentType } };
244
241
  },
245
242
  });
246
243
  },
247
244
  async delete(key) {
248
245
  // Intentionally one-shot: delete is never retried (see r2-retry.ts header).
249
- const res = await signedFetch(objectUrl(key), { method: "DELETE" });
246
+ const res = await signedFetch(objectUrl(key), { method: 'DELETE' });
250
247
  // S3 DELETE returns 204 even for missing keys; anything else non-ok is real.
251
248
  if (!res.ok && res.status !== 404) {
252
249
  throw new R2StorageError({
253
- operation: "delete",
250
+ operation: 'delete',
254
251
  bucket: args.bucket,
255
252
  key,
256
253
  attempts: 1,
@@ -262,23 +259,23 @@ export function createR2ObjectStore(args) {
262
259
  async deleteCopy({ fromKey, toKey }) {
263
260
  let destination;
264
261
  try {
265
- destination = await readObjectHeaders(toKey, "deleteCopy");
262
+ destination = await readObjectHeaders(toKey, 'deleteCopy');
266
263
  }
267
264
  catch (e) {
268
265
  if (isObjectNotFoundError(e))
269
- return "absent";
266
+ return 'absent';
270
267
  throw e;
271
268
  }
272
269
  if (!recordsCopyOf((name) => destination.get(`x-amz-meta-${name}`), fromKey))
273
- return "notThisCopy";
270
+ return 'notThisCopy';
274
271
  // Unconditioned: R2's DeleteObject honors no precondition header, so none is sent — one
275
272
  // it silently ignored would only look like protection. `MediaObjectStore.deleteCopy`
276
273
  // states why the gap after the check is harmless. One-shot, like `delete`.
277
- const res = await signedFetch(objectUrl(toKey), { method: "DELETE" });
274
+ const res = await signedFetch(objectUrl(toKey), { method: 'DELETE' });
278
275
  if (res.ok)
279
- return "deleted";
276
+ return 'deleted';
280
277
  const error = new R2StorageError({
281
- operation: "deleteCopy",
278
+ operation: 'deleteCopy',
282
279
  bucket: args.bucket,
283
280
  key: toKey,
284
281
  attempts: 1,
@@ -286,7 +283,7 @@ export function createR2ObjectStore(args) {
286
283
  responseText: await safeText(res),
287
284
  });
288
285
  if (isObjectNotFoundError(error))
289
- return "absent";
286
+ return 'absent';
290
287
  throw error;
291
288
  },
292
289
  };
@@ -296,14 +293,14 @@ async function safeText(res) {
296
293
  return (await res.text()).slice(0, 500);
297
294
  }
298
295
  catch {
299
- return "<no body>";
296
+ return '<no body>';
300
297
  }
301
298
  }
302
299
  // ---------------------------------------------------------------------------
303
300
  // Firebase Storage stores — deployed (token-free) and emulator (fixed token)
304
301
  // ---------------------------------------------------------------------------
305
302
  /** Custom-metadata key Firebase Storage reads download tokens from. */
306
- const DOWNLOAD_TOKENS_METADATA_KEY = "firebaseStorageDownloadTokens";
303
+ const DOWNLOAD_TOKENS_METADATA_KEY = 'firebaseStorageDownloadTokens';
307
304
  /**
308
305
  * How many times `deleteCopy` checks a Firebase Storage destination before giving up. Keys
309
306
  * are write-once, so one change between the check and the delete is already rare; an object
@@ -341,19 +338,24 @@ function createFirebaseStore(bucket, downloadToken) {
341
338
  metadata: {
342
339
  contentType: ct,
343
340
  cacheControl: DEFAULT_CACHE_CONTROL,
344
- ...(downloadToken === null ? {} : { metadata: { [DOWNLOAD_TOKENS_METADATA_KEY]: downloadToken } }),
341
+ ...(downloadToken === null
342
+ ? {}
343
+ : { metadata: { [DOWNLOAD_TOKENS_METADATA_KEY]: downloadToken } }),
345
344
  },
346
345
  });
347
346
  return { key, sizeBytes: size, contentType: ct };
348
347
  },
349
348
  async copy({ fromKey, toKey }) {
350
349
  const [source] = await bucket.file(fromKey).getMetadata();
351
- const generation = requireStorageObjectGeneration(source, { bucket: bucket.name, key: fromKey });
350
+ const generation = requireStorageObjectGeneration(source, {
351
+ bucket: bucket.name,
352
+ key: fromKey,
353
+ });
352
354
  const provenance = copyProvenance(fromKey, generation);
353
355
  const contentHeaders = {};
354
356
  for (const field of Object.values(CARRIED_CONTENT_HEADERS)) {
355
357
  const value = source[field];
356
- if (typeof value === "string" && value !== "")
358
+ if (typeof value === 'string' && value !== '')
357
359
  contentHeaders[field] = value;
358
360
  }
359
361
  try {
@@ -364,7 +366,9 @@ function createFirebaseStore(bucket, downloadToken) {
364
366
  // 412 can only mean the destination exists; a replaced source fails as not-found.
365
367
  await bucket.file(fromKey, { generation }).copy(bucket.file(toKey), {
366
368
  ...contentHeaders,
367
- metadata: downloadToken === null ? provenance : { ...provenance, [DOWNLOAD_TOKENS_METADATA_KEY]: downloadToken },
369
+ metadata: downloadToken === null
370
+ ? provenance
371
+ : { ...provenance, [DOWNLOAD_TOKENS_METADATA_KEY]: downloadToken },
368
372
  preconditionOpts: { ifGenerationMatch: 0 },
369
373
  });
370
374
  return { key: toKey, alreadyPresent: false };
@@ -409,20 +413,20 @@ function createFirebaseStore(bucket, downloadToken) {
409
413
  }
410
414
  catch (e) {
411
415
  if (isObjectNotFoundError(e))
412
- return "absent";
416
+ return 'absent';
413
417
  throw e;
414
418
  }
415
419
  if (!recordsCopyOf((name) => custom?.[name], fromKey))
416
- return "notThisCopy";
420
+ return 'notThisCopy';
417
421
  // The delete names the generation just checked, so it can only remove that object.
418
422
  const checked = requireStorageObjectGeneration({ generation }, { bucket: bucket.name, key: toKey });
419
423
  try {
420
424
  await file.delete({ ifGenerationMatch: checked });
421
- return "deleted";
425
+ return 'deleted';
422
426
  }
423
427
  catch (e) {
424
428
  if (isObjectNotFoundError(e))
425
- return "absent";
429
+ return 'absent';
426
430
  // A 412 means the object changed after it was checked: check what is there now.
427
431
  if (!isPreconditionFailedError(e) || read >= DELETE_COPY_MAX_READS)
428
432
  throw e;
@@ -437,27 +441,27 @@ function isPreconditionFailedError(e) {
437
441
  }
438
442
  // ---------------------------------------------------------------------------
439
443
  function extractExt(localPath) {
440
- return localPath.split(".").pop() || "";
444
+ return localPath.split('.').pop() || '';
441
445
  }
442
446
  export function getMimeFromExt(ext) {
443
447
  switch (ext.toLowerCase()) {
444
- case "jpg":
445
- case "jpeg":
446
- return "image/jpeg";
447
- case "png":
448
- return "image/png";
449
- case "webp":
450
- return "image/webp";
451
- case "mp4":
452
- return "video/mp4";
453
- case "webm":
454
- return "video/webm";
455
- case "m4a":
456
- return "audio/mp4";
457
- case "aac":
458
- return "audio/aac";
459
- case "mp3":
460
- return "audio/mpeg";
448
+ case 'jpg':
449
+ case 'jpeg':
450
+ return 'image/jpeg';
451
+ case 'png':
452
+ return 'image/png';
453
+ case 'webp':
454
+ return 'image/webp';
455
+ case 'mp4':
456
+ return 'video/mp4';
457
+ case 'webm':
458
+ return 'video/webm';
459
+ case 'm4a':
460
+ return 'audio/mp4';
461
+ case 'aac':
462
+ return 'audio/aac';
463
+ case 'mp3':
464
+ return 'audio/mpeg';
461
465
  default:
462
466
  return NEUTRAL_CONTENT_TYPE;
463
467
  }