graphile-presigned-url-plugin 0.22.0 → 0.22.1

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.
@@ -115,30 +115,33 @@ function createDownloadUrlPlugin(options) {
115
115
  let downloadUrlExpirySeconds = 3600;
116
116
  try {
117
117
  if (withPgClient && pgSettings) {
118
- const resolved = await withPgClient(pgSettings, async (pgClient) => {
118
+ const databaseId = await withPgClient(pgSettings, async (pgClient) => {
119
119
  const dbResult = await pgClient.query({
120
120
  text: `SELECT jwt_private.current_database_id() AS id`,
121
121
  });
122
- const databaseId = dbResult.rows[0]?.id;
123
- if (!databaseId)
124
- return null;
125
- const allConfigs = await (0, storage_module_cache_1.loadAllStorageModules)(pgClient, databaseId);
126
- const config = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedCodec, allConfigs);
127
- if (!config)
128
- return null;
129
- // Look up the bucket key for scoped S3 resolution
130
- let bucketKey = 'public';
131
- if (bucketId) {
132
- const bucketResult = await pgClient.query({
133
- text: `SELECT key FROM ${config.bucketsQualifiedName} WHERE id = $1 LIMIT 1`,
134
- values: [bucketId],
135
- });
136
- if (bucketResult.rows[0]?.key) {
137
- bucketKey = bucketResult.rows[0].key;
138
- }
139
- }
140
- return { config, databaseId, bucketKey };
122
+ return dbResult.rows[0]?.id ?? null;
141
123
  });
124
+ // Module registration is server config, not user data:
125
+ // resolve it without the request role's pgSettings.
126
+ const config = databaseId
127
+ ? (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedCodec, await withPgClient(null, (pgClient) => (0, storage_module_cache_1.loadAllStorageModules)(pgClient, databaseId)))
128
+ : null;
129
+ const resolved = config
130
+ ? await withPgClient(pgSettings, async (pgClient) => {
131
+ // Look up the bucket key for scoped S3 resolution
132
+ let bucketKey = 'public';
133
+ if (bucketId) {
134
+ const bucketResult = await pgClient.query({
135
+ text: `SELECT key FROM ${config.bucketsQualifiedName} WHERE id = $1 LIMIT 1`,
136
+ values: [bucketId],
137
+ });
138
+ if (bucketResult.rows[0]?.key) {
139
+ bucketKey = bucketResult.rows[0].key;
140
+ }
141
+ }
142
+ return { config, databaseId, bucketKey };
143
+ })
144
+ : null;
142
145
  if (resolved) {
143
146
  downloadUrlExpirySeconds = resolved.config.downloadUrlExpirySeconds;
144
147
  s3ForDb = resolveS3ForDatabase(options, resolved.config, resolved.databaseId, resolved.bucketKey);
@@ -112,30 +112,33 @@ export function createDownloadUrlPlugin(options) {
112
112
  let downloadUrlExpirySeconds = 3600;
113
113
  try {
114
114
  if (withPgClient && pgSettings) {
115
- const resolved = await withPgClient(pgSettings, async (pgClient) => {
115
+ const databaseId = await withPgClient(pgSettings, async (pgClient) => {
116
116
  const dbResult = await pgClient.query({
117
117
  text: `SELECT jwt_private.current_database_id() AS id`,
118
118
  });
119
- const databaseId = dbResult.rows[0]?.id;
120
- if (!databaseId)
121
- return null;
122
- const allConfigs = await loadAllStorageModules(pgClient, databaseId);
123
- const config = resolveStorageConfigFromCodec(capturedCodec, allConfigs);
124
- if (!config)
125
- return null;
126
- // Look up the bucket key for scoped S3 resolution
127
- let bucketKey = 'public';
128
- if (bucketId) {
129
- const bucketResult = await pgClient.query({
130
- text: `SELECT key FROM ${config.bucketsQualifiedName} WHERE id = $1 LIMIT 1`,
131
- values: [bucketId],
132
- });
133
- if (bucketResult.rows[0]?.key) {
134
- bucketKey = bucketResult.rows[0].key;
135
- }
136
- }
137
- return { config, databaseId, bucketKey };
119
+ return dbResult.rows[0]?.id ?? null;
138
120
  });
121
+ // Module registration is server config, not user data:
122
+ // resolve it without the request role's pgSettings.
123
+ const config = databaseId
124
+ ? resolveStorageConfigFromCodec(capturedCodec, await withPgClient(null, (pgClient) => loadAllStorageModules(pgClient, databaseId)))
125
+ : null;
126
+ const resolved = config
127
+ ? await withPgClient(pgSettings, async (pgClient) => {
128
+ // Look up the bucket key for scoped S3 resolution
129
+ let bucketKey = 'public';
130
+ if (bucketId) {
131
+ const bucketResult = await pgClient.query({
132
+ text: `SELECT key FROM ${config.bucketsQualifiedName} WHERE id = $1 LIMIT 1`,
133
+ values: [bucketId],
134
+ });
135
+ if (bucketResult.rows[0]?.key) {
136
+ bucketKey = bucketResult.rows[0].key;
137
+ }
138
+ }
139
+ return { config, databaseId, bucketKey };
140
+ })
141
+ : null;
139
142
  if (resolved) {
140
143
  downloadUrlExpirySeconds = resolved.config.downloadUrlExpirySeconds;
141
144
  s3ForDb = resolveS3ForDatabase(options, resolved.config, resolved.databaseId, resolved.bucketKey);
package/esm/plugin.js CHANGED
@@ -202,15 +202,17 @@ export function createPresignedUrlPlugin(options) {
202
202
  pgSettings: $pgSettings,
203
203
  });
204
204
  return lambda($combined, async (vals) => {
205
+ const databaseId = await vals.withPgClient(vals.pgSettings, (pgClient) => resolveDatabaseId(pgClient));
206
+ if (!databaseId)
207
+ throw new Error('DATABASE_NOT_FOUND');
208
+ // Module registration is server config, not user data:
209
+ // resolve it without the request role's pgSettings.
210
+ const allConfigs = await vals.withPgClient(null, (pgClient) => loadAllStorageModules(pgClient, databaseId));
211
+ const storageConfig = resolveStorageConfigFromCodec(capturedFilesCodec, allConfigs);
212
+ if (!storageConfig)
213
+ throw new Error('STORAGE_MODULE_NOT_FOUND');
205
214
  return vals.withPgClient(vals.pgSettings, async (pgClient) => {
206
215
  return pgClient.withTransaction(async (txClient) => {
207
- const databaseId = await resolveDatabaseId(txClient);
208
- if (!databaseId)
209
- throw new Error('DATABASE_NOT_FOUND');
210
- const allConfigs = await loadAllStorageModules(txClient, databaseId);
211
- const storageConfig = resolveStorageConfigFromCodec(capturedFilesCodec, allConfigs);
212
- if (!storageConfig)
213
- throw new Error('STORAGE_MODULE_NOT_FOUND');
214
216
  const bucket = await getBucketConfig(txClient, storageConfig, databaseId, vals.bucketKey, vals.ownerId || undefined);
215
217
  if (!bucket)
216
218
  throw new Error('BUCKET_NOT_FOUND');
@@ -289,15 +291,17 @@ export function createPresignedUrlPlugin(options) {
289
291
  pgSettings: $pgSettings,
290
292
  });
291
293
  return lambda($combined, async (vals) => {
294
+ const databaseId = await vals.withPgClient(vals.pgSettings, (pgClient) => resolveDatabaseId(pgClient));
295
+ if (!databaseId)
296
+ throw new Error('DATABASE_NOT_FOUND');
297
+ // Module registration is server config, not user data:
298
+ // resolve it without the request role's pgSettings.
299
+ const allConfigs = await vals.withPgClient(null, (pgClient) => loadAllStorageModules(pgClient, databaseId));
300
+ const storageConfig = resolveStorageConfigFromCodec(capturedFilesCodec, allConfigs);
301
+ if (!storageConfig)
302
+ throw new Error('STORAGE_MODULE_NOT_FOUND');
292
303
  return vals.withPgClient(vals.pgSettings, async (pgClient) => {
293
304
  return pgClient.withTransaction(async (txClient) => {
294
- const databaseId = await resolveDatabaseId(txClient);
295
- if (!databaseId)
296
- throw new Error('DATABASE_NOT_FOUND');
297
- const allConfigs = await loadAllStorageModules(txClient, databaseId);
298
- const storageConfig = resolveStorageConfigFromCodec(capturedFilesCodec, allConfigs);
299
- if (!storageConfig)
300
- throw new Error('STORAGE_MODULE_NOT_FOUND');
301
305
  const bucket = await getBucketConfig(txClient, storageConfig, databaseId, vals.bucketKey, vals.ownerId || undefined);
302
306
  if (!bucket)
303
307
  throw new Error('BUCKET_NOT_FOUND');
@@ -369,23 +373,25 @@ export function createPresignedUrlPlugin(options) {
369
373
  const pgSettings = graphqlContext.pgSettings;
370
374
  if (withPgClient) {
371
375
  try {
372
- await withPgClient(pgSettings, async (pgClient) => {
373
- const databaseId = await resolveDatabaseId(pgClient);
374
- if (!databaseId)
375
- return;
376
- const allConfigs = await loadAllStorageModules(pgClient, databaseId);
377
- const storageConfig = resolveStorageConfigFromCodec(capturedCodec, allConfigs);
378
- if (!storageConfig)
379
- return;
380
- // Read the file row (RLS enforced)
381
- const result = await pgClient.query({
382
- text: `SELECT key, bucket_id FROM ${storageConfig.filesQualifiedName} WHERE id = $1 LIMIT 1`,
383
- values: [fileInput],
376
+ const databaseId = await withPgClient(pgSettings, (pgClient) => resolveDatabaseId(pgClient));
377
+ // Module registration is server config, not user data:
378
+ // resolve it without the request role's pgSettings.
379
+ const allConfigs = databaseId
380
+ ? await withPgClient(null, (pgClient) => loadAllStorageModules(pgClient, databaseId))
381
+ : [];
382
+ const storageConfig = resolveStorageConfigFromCodec(capturedCodec, allConfigs);
383
+ if (storageConfig) {
384
+ await withPgClient(pgSettings, async (pgClient) => {
385
+ // Read the file row (RLS enforced)
386
+ const result = await pgClient.query({
387
+ text: `SELECT key, bucket_id FROM ${storageConfig.filesQualifiedName} WHERE id = $1 LIMIT 1`,
388
+ values: [fileInput],
389
+ });
390
+ if (result.rows.length > 0) {
391
+ fileRow = result.rows[0];
392
+ }
384
393
  });
385
- if (result.rows.length > 0) {
386
- fileRow = result.rows[0];
387
- }
388
- });
394
+ }
389
395
  }
390
396
  catch (err) {
391
397
  log.warn(`Pre-delete file lookup failed: ${err.message}`);
@@ -400,39 +406,40 @@ export function createPresignedUrlPlugin(options) {
400
406
  const pgSettings = graphqlContext.pgSettings;
401
407
  if (withPgClient) {
402
408
  try {
403
- await withPgClient(pgSettings, async (pgClient) => {
404
- const databaseId = await resolveDatabaseId(pgClient);
405
- if (!databaseId)
406
- return;
407
- const allConfigs = await loadAllStorageModules(pgClient, databaseId);
408
- const storageConfig = resolveStorageConfigFromCodec(capturedCodec, allConfigs);
409
- if (!storageConfig)
410
- return;
411
- // Check refcount: any other file with the same key in this bucket?
412
- const refResult = await pgClient.query({
413
- text: `SELECT COUNT(*)::int AS ref_count FROM ${storageConfig.filesQualifiedName} WHERE key = $1 AND bucket_id = $2`,
414
- values: [fileRow.key, fileRow.bucket_id],
415
- });
416
- const refCount = refResult.rows[0]?.ref_count ?? 0;
417
- if (refCount > 0) {
418
- log.info(`File deleted from DB; S3 key ${fileRow.key} still referenced by ${refCount} file(s)`);
419
- return;
420
- }
421
- // No other references — attempt sync S3 delete
422
- // Look up the bucket key for scoped S3 resolution
423
- const bucketResult = await pgClient.query({
424
- text: `SELECT key FROM ${storageConfig.bucketsQualifiedName} WHERE id = $1 LIMIT 1`,
425
- values: [fileRow.bucket_id],
409
+ const databaseId = await withPgClient(pgSettings, (pgClient) => resolveDatabaseId(pgClient));
410
+ // Module registration is server config, not user data:
411
+ // resolve it without the request role's pgSettings.
412
+ const allConfigs = databaseId
413
+ ? await withPgClient(null, (pgClient) => loadAllStorageModules(pgClient, databaseId))
414
+ : [];
415
+ const storageConfig = resolveStorageConfigFromCodec(capturedCodec, allConfigs);
416
+ if (storageConfig)
417
+ await withPgClient(pgSettings, async (pgClient) => {
418
+ // Check refcount: any other file with the same key in this bucket?
419
+ const refResult = await pgClient.query({
420
+ text: `SELECT COUNT(*)::int AS ref_count FROM ${storageConfig.filesQualifiedName} WHERE key = $1 AND bucket_id = $2`,
421
+ values: [fileRow.key, fileRow.bucket_id],
422
+ });
423
+ const refCount = refResult.rows[0]?.ref_count ?? 0;
424
+ if (refCount > 0) {
425
+ log.info(`File deleted from DB; S3 key ${fileRow.key} still referenced by ${refCount} file(s)`);
426
+ return;
427
+ }
428
+ // No other references — attempt sync S3 delete
429
+ // Look up the bucket key for scoped S3 resolution
430
+ const bucketResult = await pgClient.query({
431
+ text: `SELECT key FROM ${storageConfig.bucketsQualifiedName} WHERE id = $1 LIMIT 1`,
432
+ values: [fileRow.bucket_id],
433
+ });
434
+ const bucketKey = bucketResult.rows[0]?.key;
435
+ if (!bucketKey) {
436
+ log.warn(`Bucket not found for bucket_id=${fileRow.bucket_id}; skipping S3 delete`);
437
+ return;
438
+ }
439
+ const s3ForDb = resolveS3ForDatabase(options, storageConfig, databaseId, bucketKey);
440
+ await deleteS3Object(s3ForDb, fileRow.key);
441
+ log.info(`Sync S3 delete succeeded for key=${fileRow.key}`);
426
442
  });
427
- const bucketKey = bucketResult.rows[0]?.key;
428
- if (!bucketKey) {
429
- log.warn(`Bucket not found for bucket_id=${fileRow.bucket_id}; skipping S3 delete`);
430
- return;
431
- }
432
- const s3ForDb = resolveS3ForDatabase(options, storageConfig, databaseId, bucketKey);
433
- await deleteS3Object(s3ForDb, fileRow.key);
434
- log.info(`Sync S3 delete succeeded for key=${fileRow.key}`);
435
- });
436
443
  }
437
444
  catch (err) {
438
445
  // Sync S3 delete failed — the AFTER DELETE trigger has enqueued an async GC job
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphile-presigned-url-plugin",
3
- "version": "0.22.0",
3
+ "version": "0.22.1",
4
4
  "description": "Presigned URL upload plugin for PostGraphile v5 — requestUploadUrl mutation and downloadUrl computed field",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "homepage": "https://github.com/constructive-io/constructive",
@@ -60,5 +60,5 @@
60
60
  "@types/node": "^22.19.11",
61
61
  "makage": "^0.3.0"
62
62
  },
63
- "gitHead": "7eb34a3baa10ba3476575967b87ff51b606e042a"
63
+ "gitHead": "dd2accb683fe20c7278a0d58fd36a04a3b07ac63"
64
64
  }
package/plugin.js CHANGED
@@ -206,15 +206,17 @@ function createPresignedUrlPlugin(options) {
206
206
  pgSettings: $pgSettings,
207
207
  });
208
208
  return (0, grafast_1.lambda)($combined, async (vals) => {
209
+ const databaseId = await vals.withPgClient(vals.pgSettings, (pgClient) => resolveDatabaseId(pgClient));
210
+ if (!databaseId)
211
+ throw new Error('DATABASE_NOT_FOUND');
212
+ // Module registration is server config, not user data:
213
+ // resolve it without the request role's pgSettings.
214
+ const allConfigs = await vals.withPgClient(null, (pgClient) => (0, storage_module_cache_1.loadAllStorageModules)(pgClient, databaseId));
215
+ const storageConfig = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedFilesCodec, allConfigs);
216
+ if (!storageConfig)
217
+ throw new Error('STORAGE_MODULE_NOT_FOUND');
209
218
  return vals.withPgClient(vals.pgSettings, async (pgClient) => {
210
219
  return pgClient.withTransaction(async (txClient) => {
211
- const databaseId = await resolveDatabaseId(txClient);
212
- if (!databaseId)
213
- throw new Error('DATABASE_NOT_FOUND');
214
- const allConfigs = await (0, storage_module_cache_1.loadAllStorageModules)(txClient, databaseId);
215
- const storageConfig = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedFilesCodec, allConfigs);
216
- if (!storageConfig)
217
- throw new Error('STORAGE_MODULE_NOT_FOUND');
218
220
  const bucket = await (0, storage_module_cache_1.getBucketConfig)(txClient, storageConfig, databaseId, vals.bucketKey, vals.ownerId || undefined);
219
221
  if (!bucket)
220
222
  throw new Error('BUCKET_NOT_FOUND');
@@ -293,15 +295,17 @@ function createPresignedUrlPlugin(options) {
293
295
  pgSettings: $pgSettings,
294
296
  });
295
297
  return (0, grafast_1.lambda)($combined, async (vals) => {
298
+ const databaseId = await vals.withPgClient(vals.pgSettings, (pgClient) => resolveDatabaseId(pgClient));
299
+ if (!databaseId)
300
+ throw new Error('DATABASE_NOT_FOUND');
301
+ // Module registration is server config, not user data:
302
+ // resolve it without the request role's pgSettings.
303
+ const allConfigs = await vals.withPgClient(null, (pgClient) => (0, storage_module_cache_1.loadAllStorageModules)(pgClient, databaseId));
304
+ const storageConfig = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedFilesCodec, allConfigs);
305
+ if (!storageConfig)
306
+ throw new Error('STORAGE_MODULE_NOT_FOUND');
296
307
  return vals.withPgClient(vals.pgSettings, async (pgClient) => {
297
308
  return pgClient.withTransaction(async (txClient) => {
298
- const databaseId = await resolveDatabaseId(txClient);
299
- if (!databaseId)
300
- throw new Error('DATABASE_NOT_FOUND');
301
- const allConfigs = await (0, storage_module_cache_1.loadAllStorageModules)(txClient, databaseId);
302
- const storageConfig = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedFilesCodec, allConfigs);
303
- if (!storageConfig)
304
- throw new Error('STORAGE_MODULE_NOT_FOUND');
305
309
  const bucket = await (0, storage_module_cache_1.getBucketConfig)(txClient, storageConfig, databaseId, vals.bucketKey, vals.ownerId || undefined);
306
310
  if (!bucket)
307
311
  throw new Error('BUCKET_NOT_FOUND');
@@ -373,23 +377,25 @@ function createPresignedUrlPlugin(options) {
373
377
  const pgSettings = graphqlContext.pgSettings;
374
378
  if (withPgClient) {
375
379
  try {
376
- await withPgClient(pgSettings, async (pgClient) => {
377
- const databaseId = await resolveDatabaseId(pgClient);
378
- if (!databaseId)
379
- return;
380
- const allConfigs = await (0, storage_module_cache_1.loadAllStorageModules)(pgClient, databaseId);
381
- const storageConfig = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedCodec, allConfigs);
382
- if (!storageConfig)
383
- return;
384
- // Read the file row (RLS enforced)
385
- const result = await pgClient.query({
386
- text: `SELECT key, bucket_id FROM ${storageConfig.filesQualifiedName} WHERE id = $1 LIMIT 1`,
387
- values: [fileInput],
380
+ const databaseId = await withPgClient(pgSettings, (pgClient) => resolveDatabaseId(pgClient));
381
+ // Module registration is server config, not user data:
382
+ // resolve it without the request role's pgSettings.
383
+ const allConfigs = databaseId
384
+ ? await withPgClient(null, (pgClient) => (0, storage_module_cache_1.loadAllStorageModules)(pgClient, databaseId))
385
+ : [];
386
+ const storageConfig = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedCodec, allConfigs);
387
+ if (storageConfig) {
388
+ await withPgClient(pgSettings, async (pgClient) => {
389
+ // Read the file row (RLS enforced)
390
+ const result = await pgClient.query({
391
+ text: `SELECT key, bucket_id FROM ${storageConfig.filesQualifiedName} WHERE id = $1 LIMIT 1`,
392
+ values: [fileInput],
393
+ });
394
+ if (result.rows.length > 0) {
395
+ fileRow = result.rows[0];
396
+ }
388
397
  });
389
- if (result.rows.length > 0) {
390
- fileRow = result.rows[0];
391
- }
392
- });
398
+ }
393
399
  }
394
400
  catch (err) {
395
401
  log.warn(`Pre-delete file lookup failed: ${err.message}`);
@@ -404,39 +410,40 @@ function createPresignedUrlPlugin(options) {
404
410
  const pgSettings = graphqlContext.pgSettings;
405
411
  if (withPgClient) {
406
412
  try {
407
- await withPgClient(pgSettings, async (pgClient) => {
408
- const databaseId = await resolveDatabaseId(pgClient);
409
- if (!databaseId)
410
- return;
411
- const allConfigs = await (0, storage_module_cache_1.loadAllStorageModules)(pgClient, databaseId);
412
- const storageConfig = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedCodec, allConfigs);
413
- if (!storageConfig)
414
- return;
415
- // Check refcount: any other file with the same key in this bucket?
416
- const refResult = await pgClient.query({
417
- text: `SELECT COUNT(*)::int AS ref_count FROM ${storageConfig.filesQualifiedName} WHERE key = $1 AND bucket_id = $2`,
418
- values: [fileRow.key, fileRow.bucket_id],
419
- });
420
- const refCount = refResult.rows[0]?.ref_count ?? 0;
421
- if (refCount > 0) {
422
- log.info(`File deleted from DB; S3 key ${fileRow.key} still referenced by ${refCount} file(s)`);
423
- return;
424
- }
425
- // No other references — attempt sync S3 delete
426
- // Look up the bucket key for scoped S3 resolution
427
- const bucketResult = await pgClient.query({
428
- text: `SELECT key FROM ${storageConfig.bucketsQualifiedName} WHERE id = $1 LIMIT 1`,
429
- values: [fileRow.bucket_id],
413
+ const databaseId = await withPgClient(pgSettings, (pgClient) => resolveDatabaseId(pgClient));
414
+ // Module registration is server config, not user data:
415
+ // resolve it without the request role's pgSettings.
416
+ const allConfigs = databaseId
417
+ ? await withPgClient(null, (pgClient) => (0, storage_module_cache_1.loadAllStorageModules)(pgClient, databaseId))
418
+ : [];
419
+ const storageConfig = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedCodec, allConfigs);
420
+ if (storageConfig)
421
+ await withPgClient(pgSettings, async (pgClient) => {
422
+ // Check refcount: any other file with the same key in this bucket?
423
+ const refResult = await pgClient.query({
424
+ text: `SELECT COUNT(*)::int AS ref_count FROM ${storageConfig.filesQualifiedName} WHERE key = $1 AND bucket_id = $2`,
425
+ values: [fileRow.key, fileRow.bucket_id],
426
+ });
427
+ const refCount = refResult.rows[0]?.ref_count ?? 0;
428
+ if (refCount > 0) {
429
+ log.info(`File deleted from DB; S3 key ${fileRow.key} still referenced by ${refCount} file(s)`);
430
+ return;
431
+ }
432
+ // No other references — attempt sync S3 delete
433
+ // Look up the bucket key for scoped S3 resolution
434
+ const bucketResult = await pgClient.query({
435
+ text: `SELECT key FROM ${storageConfig.bucketsQualifiedName} WHERE id = $1 LIMIT 1`,
436
+ values: [fileRow.bucket_id],
437
+ });
438
+ const bucketKey = bucketResult.rows[0]?.key;
439
+ if (!bucketKey) {
440
+ log.warn(`Bucket not found for bucket_id=${fileRow.bucket_id}; skipping S3 delete`);
441
+ return;
442
+ }
443
+ const s3ForDb = resolveS3ForDatabase(options, storageConfig, databaseId, bucketKey);
444
+ await (0, s3_signer_1.deleteS3Object)(s3ForDb, fileRow.key);
445
+ log.info(`Sync S3 delete succeeded for key=${fileRow.key}`);
430
446
  });
431
- const bucketKey = bucketResult.rows[0]?.key;
432
- if (!bucketKey) {
433
- log.warn(`Bucket not found for bucket_id=${fileRow.bucket_id}; skipping S3 delete`);
434
- return;
435
- }
436
- const s3ForDb = resolveS3ForDatabase(options, storageConfig, databaseId, bucketKey);
437
- await (0, s3_signer_1.deleteS3Object)(s3ForDb, fileRow.key);
438
- log.info(`Sync S3 delete succeeded for key=${fileRow.key}`);
439
- });
440
447
  }
441
448
  catch (err) {
442
449
  // Sync S3 delete failed — the AFTER DELETE trigger has enqueued an async GC job