graphile-bucket-provisioner-plugin 1.9.3 → 1.11.0
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.
- package/esm/plugin.js +71 -16
- package/package.json +2 -2
- package/plugin.js +71 -16
package/esm/plugin.js
CHANGED
|
@@ -81,6 +81,17 @@ const ALL_STORAGE_MODULES_QUERY = `
|
|
|
81
81
|
LEFT JOIN metaschema_public.schema es ON es.id = et.schema_id
|
|
82
82
|
WHERE sm.database_id = $1
|
|
83
83
|
`;
|
|
84
|
+
/**
|
|
85
|
+
* Run a query against a grafast `withPgClient` client.
|
|
86
|
+
*
|
|
87
|
+
* That client's `query` takes the `{ text, values }` object form (the same
|
|
88
|
+
* shape the presigned-url plugin uses) — NOT node-pg's positional
|
|
89
|
+
* `(text, params)` args, which surface as "A query must have either text or a
|
|
90
|
+
* name" at runtime.
|
|
91
|
+
*/
|
|
92
|
+
function runQuery(pgClient, text, values) {
|
|
93
|
+
return pgClient.query(values === undefined ? { text } : { text, values });
|
|
94
|
+
}
|
|
84
95
|
/**
|
|
85
96
|
* Resolve the storage module for a given scope.
|
|
86
97
|
* If ownerId is provided, probes entity tables to find the matching module.
|
|
@@ -89,22 +100,44 @@ const ALL_STORAGE_MODULES_QUERY = `
|
|
|
89
100
|
async function resolveStorageModule(pgClient, databaseId, ownerId) {
|
|
90
101
|
if (!ownerId) {
|
|
91
102
|
// App-level resolution
|
|
92
|
-
const result = await pgClient
|
|
103
|
+
const result = await runQuery(pgClient, APP_STORAGE_MODULE_QUERY, [databaseId]);
|
|
93
104
|
return result.rows[0] ?? null;
|
|
94
105
|
}
|
|
95
106
|
// Entity-scoped: load all modules and probe entity tables
|
|
96
|
-
const result = await pgClient
|
|
107
|
+
const result = await runQuery(pgClient, ALL_STORAGE_MODULES_QUERY, [databaseId]);
|
|
97
108
|
const modules = result.rows;
|
|
98
109
|
const entityModules = modules.filter((m) => m.entity_schema && m.entity_table);
|
|
99
110
|
for (const mod of entityModules) {
|
|
100
111
|
const entityTable = QuoteUtils.quoteQualifiedIdentifier(mod.entity_schema, mod.entity_table);
|
|
101
|
-
const probe = await pgClient
|
|
112
|
+
const probe = await runQuery(pgClient, `SELECT 1 FROM ${entityTable} WHERE id = $1 LIMIT 1`, [ownerId]);
|
|
102
113
|
if (probe.rows.length > 0) {
|
|
103
114
|
return mod;
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
return null;
|
|
107
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Normalize the recorded physical coordinate at the DB boundary.
|
|
121
|
+
*
|
|
122
|
+
* A bucket row either carries a recorded coordinate or it does not; SQL nulls
|
|
123
|
+
* and absent columns both mean "never provisioned". Collapsing them here is
|
|
124
|
+
* the single place that shape is interpreted — callers branch on `string`
|
|
125
|
+
* vs `null` and never coalesce a bucket name into existence.
|
|
126
|
+
*/
|
|
127
|
+
function storedPhysicalName(row) {
|
|
128
|
+
return row.physical_name == null ? null : row.physical_name;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Record the physical S3 bucket name on the source bucket row.
|
|
132
|
+
*
|
|
133
|
+
* Runs in the system lane (`withPgClient(null, ...)`) — server bookkeeping,
|
|
134
|
+
* RLS-independent. Idempotent via the `physical_name IS NULL` guard so a
|
|
135
|
+
* re-provision never clobbers an already-recorded coordinate.
|
|
136
|
+
*/
|
|
137
|
+
async function recordPhysicalName(withPgClient, bucketsTable, bucketId, physicalName) {
|
|
138
|
+
await withPgClient(null, (client) => runQuery(client, `UPDATE ${bucketsTable} SET physical_name = $1 WHERE id = $2 AND physical_name IS NULL`, [physicalName, bucketId]));
|
|
139
|
+
log.info(`Recorded physical_name="${physicalName}" on bucket ${bucketId}`);
|
|
140
|
+
}
|
|
108
141
|
// --- Helpers ---
|
|
109
142
|
/**
|
|
110
143
|
* Resolve the connection config from the options. If the option is a lazy
|
|
@@ -135,7 +168,7 @@ function resolveBucketName(bucketKey, databaseId, options) {
|
|
|
135
168
|
* Resolve the database_id from the JWT context.
|
|
136
169
|
*/
|
|
137
170
|
async function resolveDatabaseId(pgClient) {
|
|
138
|
-
const result = await pgClient
|
|
171
|
+
const result = await runQuery(pgClient, `SELECT jwt_private.current_database_id() AS id`);
|
|
139
172
|
return result.rows[0]?.id ?? null;
|
|
140
173
|
}
|
|
141
174
|
/**
|
|
@@ -174,8 +207,7 @@ function buildProvisioner(options, storageModule, effectiveOrigins) {
|
|
|
174
207
|
* Core provisioning logic shared by both the explicit mutation and the
|
|
175
208
|
* auto-provisioning hook.
|
|
176
209
|
*/
|
|
177
|
-
async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options) {
|
|
178
|
-
const s3BucketName = resolveBucketName(bucketKey, databaseId, options);
|
|
210
|
+
async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options, s3BucketName) {
|
|
179
211
|
const accessType = bucketType;
|
|
180
212
|
// Read storage module config to check for endpoint/provider/CORS overrides
|
|
181
213
|
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
@@ -198,8 +230,7 @@ async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType
|
|
|
198
230
|
/**
|
|
199
231
|
* Update CORS on an existing S3 bucket when allowed_origins changes.
|
|
200
232
|
*/
|
|
201
|
-
async function updateBucketCors(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options) {
|
|
202
|
-
const s3BucketName = resolveBucketName(bucketKey, databaseId, options);
|
|
233
|
+
async function updateBucketCors(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options, s3BucketName) {
|
|
203
234
|
const accessType = bucketType;
|
|
204
235
|
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
205
236
|
const effectiveOrigins = resolveAllowedOrigins(bucketAllowedOrigins, storageModule?.allowed_origins, options.allowedOrigins);
|
|
@@ -303,12 +334,12 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
303
334
|
// Look up the bucket row (RLS enforced via pgSettings)
|
|
304
335
|
const hasOwner = ownerId && storageModule.scope !== 'app';
|
|
305
336
|
const bucketsTable = QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
306
|
-
const bucketResult = await pgClient
|
|
307
|
-
? `SELECT id, key, type, is_public, allowed_origins
|
|
337
|
+
const bucketResult = await runQuery(pgClient, hasOwner
|
|
338
|
+
? `SELECT id, key, type, is_public, allowed_origins, physical_name
|
|
308
339
|
FROM ${bucketsTable}
|
|
309
340
|
WHERE key = $1 AND owner_id = $2
|
|
310
341
|
LIMIT 1`
|
|
311
|
-
: `SELECT id, key, type, is_public, allowed_origins
|
|
342
|
+
: `SELECT id, key, type, is_public, allowed_origins, physical_name
|
|
312
343
|
FROM ${bucketsTable}
|
|
313
344
|
WHERE key = $1
|
|
314
345
|
LIMIT 1`, hasOwner ? [bucketKey, ownerId] : [bucketKey]);
|
|
@@ -316,8 +347,16 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
316
347
|
throw new Error('BUCKET_NOT_FOUND');
|
|
317
348
|
}
|
|
318
349
|
const bucket = bucketResult.rows[0];
|
|
350
|
+
// First provision mints a name; afterwards the stored coordinate
|
|
351
|
+
// is authoritative and the naming hook is never consulted again.
|
|
352
|
+
const recorded = storedPhysicalName(bucket);
|
|
353
|
+
const s3BucketName = recorded === null
|
|
354
|
+
? resolveBucketName(bucket.key, databaseId, options)
|
|
355
|
+
: recorded;
|
|
319
356
|
try {
|
|
320
|
-
const result = await provisionBucketForRow(pgClient, databaseId, bucket.key, bucket.type, bucket.allowed_origins, options);
|
|
357
|
+
const result = await provisionBucketForRow(pgClient, databaseId, bucket.key, bucket.type, bucket.allowed_origins, options, s3BucketName);
|
|
358
|
+
// Record the exact provisioned name on the source row.
|
|
359
|
+
await recordPhysicalName(withPgClient, bucketsTable, bucket.id, result.bucketName);
|
|
321
360
|
return {
|
|
322
361
|
success: true,
|
|
323
362
|
bucketName: result.bucketName,
|
|
@@ -331,7 +370,7 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
331
370
|
log.error(`Failed to provision bucket "${bucketKey}": ${err.message}`);
|
|
332
371
|
return {
|
|
333
372
|
success: false,
|
|
334
|
-
bucketName:
|
|
373
|
+
bucketName: s3BucketName,
|
|
335
374
|
accessType: bucket.type,
|
|
336
375
|
provider: resolveConnection(options).provider,
|
|
337
376
|
endpoint: resolveConnection(options).endpoint ?? null,
|
|
@@ -417,7 +456,17 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
417
456
|
log.warn('Auto-provision skipped: could not resolve database_id');
|
|
418
457
|
return;
|
|
419
458
|
}
|
|
420
|
-
|
|
459
|
+
// Newly-created row has no stored coordinate yet — mint on first provision.
|
|
460
|
+
const result = await provisionBucketForRow(pgClient, databaseId, bucketInput.key, bucketInput.type, bucketInput.allowedOrigins ?? bucketInput.allowed_origins ?? null, options, resolveBucketName(bucketInput.key, databaseId, options));
|
|
461
|
+
// Record the provisioned name on the just-created row.
|
|
462
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
463
|
+
if (storageModule) {
|
|
464
|
+
const bucketsTable = QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
465
|
+
const idResult = await runQuery(pgClient, `SELECT id FROM ${bucketsTable} WHERE key = $1 LIMIT 1`, [bucketInput.key]);
|
|
466
|
+
const bucketId = idResult.rows[0]?.id;
|
|
467
|
+
if (bucketId)
|
|
468
|
+
await recordPhysicalName(withPgClient, bucketsTable, bucketId, result.bucketName);
|
|
469
|
+
}
|
|
421
470
|
});
|
|
422
471
|
}
|
|
423
472
|
else {
|
|
@@ -451,7 +500,7 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
451
500
|
}
|
|
452
501
|
// Read the full bucket row (post-update) to get type + origins
|
|
453
502
|
const bucketsTable = QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
454
|
-
const bucketResult = await pgClient
|
|
503
|
+
const bucketResult = await runQuery(pgClient, `SELECT id, key, type, is_public, allowed_origins, physical_name
|
|
455
504
|
FROM ${bucketsTable}
|
|
456
505
|
WHERE key = $1
|
|
457
506
|
LIMIT 1`, [patchKey]);
|
|
@@ -460,7 +509,13 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
460
509
|
return;
|
|
461
510
|
}
|
|
462
511
|
const bucket = bucketResult.rows[0];
|
|
463
|
-
|
|
512
|
+
// CORS applies to the recorded physical bucket; if the row was
|
|
513
|
+
// never provisioned there is nothing to update yet, so mint the
|
|
514
|
+
// conventional name the first provision would use.
|
|
515
|
+
const recorded = storedPhysicalName(bucket);
|
|
516
|
+
await updateBucketCors(pgClient, databaseId, bucket.key, bucket.type, bucket.allowed_origins, options, recorded === null
|
|
517
|
+
? resolveBucketName(bucket.key, databaseId, options)
|
|
518
|
+
: recorded);
|
|
464
519
|
});
|
|
465
520
|
}
|
|
466
521
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-bucket-provisioner-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Bucket provisioning plugin for PostGraphile v5 — auto-provisions S3 buckets on bucket table mutations",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"homepage": "https://github.com/constructive-io/constructive",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"@types/node": "^22.19.11",
|
|
58
58
|
"makage": "^0.3.0"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "247e48d25b13524d992f00c1b2dc063884d1deb1"
|
|
61
61
|
}
|
package/plugin.js
CHANGED
|
@@ -85,6 +85,17 @@ const ALL_STORAGE_MODULES_QUERY = `
|
|
|
85
85
|
LEFT JOIN metaschema_public.schema es ON es.id = et.schema_id
|
|
86
86
|
WHERE sm.database_id = $1
|
|
87
87
|
`;
|
|
88
|
+
/**
|
|
89
|
+
* Run a query against a grafast `withPgClient` client.
|
|
90
|
+
*
|
|
91
|
+
* That client's `query` takes the `{ text, values }` object form (the same
|
|
92
|
+
* shape the presigned-url plugin uses) — NOT node-pg's positional
|
|
93
|
+
* `(text, params)` args, which surface as "A query must have either text or a
|
|
94
|
+
* name" at runtime.
|
|
95
|
+
*/
|
|
96
|
+
function runQuery(pgClient, text, values) {
|
|
97
|
+
return pgClient.query(values === undefined ? { text } : { text, values });
|
|
98
|
+
}
|
|
88
99
|
/**
|
|
89
100
|
* Resolve the storage module for a given scope.
|
|
90
101
|
* If ownerId is provided, probes entity tables to find the matching module.
|
|
@@ -93,22 +104,44 @@ const ALL_STORAGE_MODULES_QUERY = `
|
|
|
93
104
|
async function resolveStorageModule(pgClient, databaseId, ownerId) {
|
|
94
105
|
if (!ownerId) {
|
|
95
106
|
// App-level resolution
|
|
96
|
-
const result = await pgClient
|
|
107
|
+
const result = await runQuery(pgClient, APP_STORAGE_MODULE_QUERY, [databaseId]);
|
|
97
108
|
return result.rows[0] ?? null;
|
|
98
109
|
}
|
|
99
110
|
// Entity-scoped: load all modules and probe entity tables
|
|
100
|
-
const result = await pgClient
|
|
111
|
+
const result = await runQuery(pgClient, ALL_STORAGE_MODULES_QUERY, [databaseId]);
|
|
101
112
|
const modules = result.rows;
|
|
102
113
|
const entityModules = modules.filter((m) => m.entity_schema && m.entity_table);
|
|
103
114
|
for (const mod of entityModules) {
|
|
104
115
|
const entityTable = quotes_1.QuoteUtils.quoteQualifiedIdentifier(mod.entity_schema, mod.entity_table);
|
|
105
|
-
const probe = await pgClient
|
|
116
|
+
const probe = await runQuery(pgClient, `SELECT 1 FROM ${entityTable} WHERE id = $1 LIMIT 1`, [ownerId]);
|
|
106
117
|
if (probe.rows.length > 0) {
|
|
107
118
|
return mod;
|
|
108
119
|
}
|
|
109
120
|
}
|
|
110
121
|
return null;
|
|
111
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Normalize the recorded physical coordinate at the DB boundary.
|
|
125
|
+
*
|
|
126
|
+
* A bucket row either carries a recorded coordinate or it does not; SQL nulls
|
|
127
|
+
* and absent columns both mean "never provisioned". Collapsing them here is
|
|
128
|
+
* the single place that shape is interpreted — callers branch on `string`
|
|
129
|
+
* vs `null` and never coalesce a bucket name into existence.
|
|
130
|
+
*/
|
|
131
|
+
function storedPhysicalName(row) {
|
|
132
|
+
return row.physical_name == null ? null : row.physical_name;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Record the physical S3 bucket name on the source bucket row.
|
|
136
|
+
*
|
|
137
|
+
* Runs in the system lane (`withPgClient(null, ...)`) — server bookkeeping,
|
|
138
|
+
* RLS-independent. Idempotent via the `physical_name IS NULL` guard so a
|
|
139
|
+
* re-provision never clobbers an already-recorded coordinate.
|
|
140
|
+
*/
|
|
141
|
+
async function recordPhysicalName(withPgClient, bucketsTable, bucketId, physicalName) {
|
|
142
|
+
await withPgClient(null, (client) => runQuery(client, `UPDATE ${bucketsTable} SET physical_name = $1 WHERE id = $2 AND physical_name IS NULL`, [physicalName, bucketId]));
|
|
143
|
+
log.info(`Recorded physical_name="${physicalName}" on bucket ${bucketId}`);
|
|
144
|
+
}
|
|
112
145
|
// --- Helpers ---
|
|
113
146
|
/**
|
|
114
147
|
* Resolve the connection config from the options. If the option is a lazy
|
|
@@ -139,7 +172,7 @@ function resolveBucketName(bucketKey, databaseId, options) {
|
|
|
139
172
|
* Resolve the database_id from the JWT context.
|
|
140
173
|
*/
|
|
141
174
|
async function resolveDatabaseId(pgClient) {
|
|
142
|
-
const result = await pgClient
|
|
175
|
+
const result = await runQuery(pgClient, `SELECT jwt_private.current_database_id() AS id`);
|
|
143
176
|
return result.rows[0]?.id ?? null;
|
|
144
177
|
}
|
|
145
178
|
/**
|
|
@@ -178,8 +211,7 @@ function buildProvisioner(options, storageModule, effectiveOrigins) {
|
|
|
178
211
|
* Core provisioning logic shared by both the explicit mutation and the
|
|
179
212
|
* auto-provisioning hook.
|
|
180
213
|
*/
|
|
181
|
-
async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options) {
|
|
182
|
-
const s3BucketName = resolveBucketName(bucketKey, databaseId, options);
|
|
214
|
+
async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options, s3BucketName) {
|
|
183
215
|
const accessType = bucketType;
|
|
184
216
|
// Read storage module config to check for endpoint/provider/CORS overrides
|
|
185
217
|
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
@@ -202,8 +234,7 @@ async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType
|
|
|
202
234
|
/**
|
|
203
235
|
* Update CORS on an existing S3 bucket when allowed_origins changes.
|
|
204
236
|
*/
|
|
205
|
-
async function updateBucketCors(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options) {
|
|
206
|
-
const s3BucketName = resolveBucketName(bucketKey, databaseId, options);
|
|
237
|
+
async function updateBucketCors(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options, s3BucketName) {
|
|
207
238
|
const accessType = bucketType;
|
|
208
239
|
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
209
240
|
const effectiveOrigins = resolveAllowedOrigins(bucketAllowedOrigins, storageModule?.allowed_origins, options.allowedOrigins);
|
|
@@ -307,12 +338,12 @@ function createBucketProvisionerPlugin(options) {
|
|
|
307
338
|
// Look up the bucket row (RLS enforced via pgSettings)
|
|
308
339
|
const hasOwner = ownerId && storageModule.scope !== 'app';
|
|
309
340
|
const bucketsTable = quotes_1.QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
310
|
-
const bucketResult = await pgClient
|
|
311
|
-
? `SELECT id, key, type, is_public, allowed_origins
|
|
341
|
+
const bucketResult = await runQuery(pgClient, hasOwner
|
|
342
|
+
? `SELECT id, key, type, is_public, allowed_origins, physical_name
|
|
312
343
|
FROM ${bucketsTable}
|
|
313
344
|
WHERE key = $1 AND owner_id = $2
|
|
314
345
|
LIMIT 1`
|
|
315
|
-
: `SELECT id, key, type, is_public, allowed_origins
|
|
346
|
+
: `SELECT id, key, type, is_public, allowed_origins, physical_name
|
|
316
347
|
FROM ${bucketsTable}
|
|
317
348
|
WHERE key = $1
|
|
318
349
|
LIMIT 1`, hasOwner ? [bucketKey, ownerId] : [bucketKey]);
|
|
@@ -320,8 +351,16 @@ function createBucketProvisionerPlugin(options) {
|
|
|
320
351
|
throw new Error('BUCKET_NOT_FOUND');
|
|
321
352
|
}
|
|
322
353
|
const bucket = bucketResult.rows[0];
|
|
354
|
+
// First provision mints a name; afterwards the stored coordinate
|
|
355
|
+
// is authoritative and the naming hook is never consulted again.
|
|
356
|
+
const recorded = storedPhysicalName(bucket);
|
|
357
|
+
const s3BucketName = recorded === null
|
|
358
|
+
? resolveBucketName(bucket.key, databaseId, options)
|
|
359
|
+
: recorded;
|
|
323
360
|
try {
|
|
324
|
-
const result = await provisionBucketForRow(pgClient, databaseId, bucket.key, bucket.type, bucket.allowed_origins, options);
|
|
361
|
+
const result = await provisionBucketForRow(pgClient, databaseId, bucket.key, bucket.type, bucket.allowed_origins, options, s3BucketName);
|
|
362
|
+
// Record the exact provisioned name on the source row.
|
|
363
|
+
await recordPhysicalName(withPgClient, bucketsTable, bucket.id, result.bucketName);
|
|
325
364
|
return {
|
|
326
365
|
success: true,
|
|
327
366
|
bucketName: result.bucketName,
|
|
@@ -335,7 +374,7 @@ function createBucketProvisionerPlugin(options) {
|
|
|
335
374
|
log.error(`Failed to provision bucket "${bucketKey}": ${err.message}`);
|
|
336
375
|
return {
|
|
337
376
|
success: false,
|
|
338
|
-
bucketName:
|
|
377
|
+
bucketName: s3BucketName,
|
|
339
378
|
accessType: bucket.type,
|
|
340
379
|
provider: resolveConnection(options).provider,
|
|
341
380
|
endpoint: resolveConnection(options).endpoint ?? null,
|
|
@@ -421,7 +460,17 @@ function createBucketProvisionerPlugin(options) {
|
|
|
421
460
|
log.warn('Auto-provision skipped: could not resolve database_id');
|
|
422
461
|
return;
|
|
423
462
|
}
|
|
424
|
-
|
|
463
|
+
// Newly-created row has no stored coordinate yet — mint on first provision.
|
|
464
|
+
const result = await provisionBucketForRow(pgClient, databaseId, bucketInput.key, bucketInput.type, bucketInput.allowedOrigins ?? bucketInput.allowed_origins ?? null, options, resolveBucketName(bucketInput.key, databaseId, options));
|
|
465
|
+
// Record the provisioned name on the just-created row.
|
|
466
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
467
|
+
if (storageModule) {
|
|
468
|
+
const bucketsTable = quotes_1.QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
469
|
+
const idResult = await runQuery(pgClient, `SELECT id FROM ${bucketsTable} WHERE key = $1 LIMIT 1`, [bucketInput.key]);
|
|
470
|
+
const bucketId = idResult.rows[0]?.id;
|
|
471
|
+
if (bucketId)
|
|
472
|
+
await recordPhysicalName(withPgClient, bucketsTable, bucketId, result.bucketName);
|
|
473
|
+
}
|
|
425
474
|
});
|
|
426
475
|
}
|
|
427
476
|
else {
|
|
@@ -455,7 +504,7 @@ function createBucketProvisionerPlugin(options) {
|
|
|
455
504
|
}
|
|
456
505
|
// Read the full bucket row (post-update) to get type + origins
|
|
457
506
|
const bucketsTable = quotes_1.QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
458
|
-
const bucketResult = await pgClient
|
|
507
|
+
const bucketResult = await runQuery(pgClient, `SELECT id, key, type, is_public, allowed_origins, physical_name
|
|
459
508
|
FROM ${bucketsTable}
|
|
460
509
|
WHERE key = $1
|
|
461
510
|
LIMIT 1`, [patchKey]);
|
|
@@ -464,7 +513,13 @@ function createBucketProvisionerPlugin(options) {
|
|
|
464
513
|
return;
|
|
465
514
|
}
|
|
466
515
|
const bucket = bucketResult.rows[0];
|
|
467
|
-
|
|
516
|
+
// CORS applies to the recorded physical bucket; if the row was
|
|
517
|
+
// never provisioned there is nothing to update yet, so mint the
|
|
518
|
+
// conventional name the first provision would use.
|
|
519
|
+
const recorded = storedPhysicalName(bucket);
|
|
520
|
+
await updateBucketCors(pgClient, databaseId, bucket.key, bucket.type, bucket.allowed_origins, options, recorded === null
|
|
521
|
+
? resolveBucketName(bucket.key, databaseId, options)
|
|
522
|
+
: recorded);
|
|
468
523
|
});
|
|
469
524
|
}
|
|
470
525
|
}
|