graphile-bucket-provisioner-plugin 0.3.0 → 0.4.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 +86 -21
- package/esm/types.d.ts +5 -0
- package/package.json +4 -3
- package/plugin.js +86 -21
- package/types.d.ts +5 -0
package/esm/plugin.js
CHANGED
|
@@ -33,12 +33,18 @@
|
|
|
33
33
|
import { context as grafastContext, lambda, object } from 'grafast';
|
|
34
34
|
import { extendSchema, gql } from 'graphile-utils';
|
|
35
35
|
import { Logger } from '@pgpmjs/logger';
|
|
36
|
+
import { QuoteUtils } from '@pgsql/quotes';
|
|
36
37
|
import { BucketProvisioner, } from '@constructive-io/bucket-provisioner';
|
|
37
38
|
const log = new Logger('graphile-bucket-provisioner:plugin');
|
|
38
|
-
// --- Storage module
|
|
39
|
-
|
|
39
|
+
// --- Storage module queries ---
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the app-level storage module (membership_type IS NULL).
|
|
42
|
+
*/
|
|
43
|
+
const APP_STORAGE_MODULE_QUERY = `
|
|
40
44
|
SELECT
|
|
41
45
|
sm.id,
|
|
46
|
+
sm.membership_type,
|
|
47
|
+
sm.entity_table_id,
|
|
42
48
|
bs.schema_name AS buckets_schema,
|
|
43
49
|
bt.name AS buckets_table,
|
|
44
50
|
sm.endpoint,
|
|
@@ -49,8 +55,56 @@ const STORAGE_MODULE_QUERY = `
|
|
|
49
55
|
JOIN metaschema_public.table bt ON bt.id = sm.buckets_table_id
|
|
50
56
|
JOIN metaschema_public.schema bs ON bs.id = bt.schema_id
|
|
51
57
|
WHERE sm.database_id = $1
|
|
58
|
+
AND sm.membership_type IS NULL
|
|
52
59
|
LIMIT 1
|
|
53
60
|
`;
|
|
61
|
+
/**
|
|
62
|
+
* Resolve ALL storage modules for a database (for ownerId-based resolution).
|
|
63
|
+
*/
|
|
64
|
+
const ALL_STORAGE_MODULES_QUERY = `
|
|
65
|
+
SELECT
|
|
66
|
+
sm.id,
|
|
67
|
+
sm.membership_type,
|
|
68
|
+
sm.entity_table_id,
|
|
69
|
+
bs.schema_name AS buckets_schema,
|
|
70
|
+
bt.name AS buckets_table,
|
|
71
|
+
sm.endpoint,
|
|
72
|
+
sm.public_url_prefix,
|
|
73
|
+
sm.provider,
|
|
74
|
+
sm.allowed_origins,
|
|
75
|
+
es.schema_name AS entity_schema,
|
|
76
|
+
et.name AS entity_table
|
|
77
|
+
FROM metaschema_modules_public.storage_module sm
|
|
78
|
+
JOIN metaschema_public.table bt ON bt.id = sm.buckets_table_id
|
|
79
|
+
JOIN metaschema_public.schema bs ON bs.id = bt.schema_id
|
|
80
|
+
LEFT JOIN metaschema_public.table et ON et.id = sm.entity_table_id
|
|
81
|
+
LEFT JOIN metaschema_public.schema es ON es.id = et.schema_id
|
|
82
|
+
WHERE sm.database_id = $1
|
|
83
|
+
`;
|
|
84
|
+
/**
|
|
85
|
+
* Resolve the storage module for a given scope.
|
|
86
|
+
* If ownerId is provided, probes entity tables to find the matching module.
|
|
87
|
+
* Otherwise, returns the app-level module.
|
|
88
|
+
*/
|
|
89
|
+
async function resolveStorageModule(pgClient, databaseId, ownerId) {
|
|
90
|
+
if (!ownerId) {
|
|
91
|
+
// App-level resolution
|
|
92
|
+
const result = await pgClient.query(APP_STORAGE_MODULE_QUERY, [databaseId]);
|
|
93
|
+
return result.rows[0] ?? null;
|
|
94
|
+
}
|
|
95
|
+
// Entity-scoped: load all modules and probe entity tables
|
|
96
|
+
const result = await pgClient.query(ALL_STORAGE_MODULES_QUERY, [databaseId]);
|
|
97
|
+
const modules = result.rows;
|
|
98
|
+
const entityModules = modules.filter((m) => m.entity_schema && m.entity_table);
|
|
99
|
+
for (const mod of entityModules) {
|
|
100
|
+
const entityTable = QuoteUtils.quoteQualifiedIdentifier(mod.entity_schema, mod.entity_table);
|
|
101
|
+
const probe = await pgClient.query(`SELECT 1 FROM ${entityTable} WHERE id = $1 LIMIT 1`, [ownerId]);
|
|
102
|
+
if (probe.rows.length > 0) {
|
|
103
|
+
return mod;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
54
108
|
// --- Helpers ---
|
|
55
109
|
/**
|
|
56
110
|
* Resolve the connection config from the options. If the option is a lazy
|
|
@@ -124,8 +178,7 @@ async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType
|
|
|
124
178
|
const s3BucketName = resolveBucketName(bucketKey, databaseId, options);
|
|
125
179
|
const accessType = bucketType;
|
|
126
180
|
// Read storage module config to check for endpoint/provider/CORS overrides
|
|
127
|
-
const
|
|
128
|
-
const storageModule = smResult.rows[0] ?? null;
|
|
181
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
129
182
|
// Resolve CORS origins using the 3-tier hierarchy
|
|
130
183
|
const effectiveOrigins = resolveAllowedOrigins(bucketAllowedOrigins, storageModule?.allowed_origins, options.allowedOrigins);
|
|
131
184
|
const provisioner = buildProvisioner(options, storageModule, effectiveOrigins);
|
|
@@ -148,8 +201,7 @@ async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType
|
|
|
148
201
|
async function updateBucketCors(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options) {
|
|
149
202
|
const s3BucketName = resolveBucketName(bucketKey, databaseId, options);
|
|
150
203
|
const accessType = bucketType;
|
|
151
|
-
const
|
|
152
|
-
const storageModule = smResult.rows[0] ?? null;
|
|
204
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
153
205
|
const effectiveOrigins = resolveAllowedOrigins(bucketAllowedOrigins, storageModule?.allowed_origins, options.allowedOrigins);
|
|
154
206
|
const provisioner = buildProvisioner(options, storageModule, effectiveOrigins);
|
|
155
207
|
log.info(`Updating CORS on S3 bucket "${s3BucketName}" ` +
|
|
@@ -185,6 +237,11 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
185
237
|
input ProvisionBucketInput {
|
|
186
238
|
"""The logical bucket key (e.g., "public", "private")"""
|
|
187
239
|
bucketKey: String!
|
|
240
|
+
"""
|
|
241
|
+
Owner entity ID for entity-scoped bucket provisioning.
|
|
242
|
+
Omit for app-level (database-wide) storage.
|
|
243
|
+
"""
|
|
244
|
+
ownerId: UUID
|
|
188
245
|
}
|
|
189
246
|
|
|
190
247
|
type ProvisionBucketPayload {
|
|
@@ -226,7 +283,7 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
226
283
|
pgSettings: $pgSettings,
|
|
227
284
|
});
|
|
228
285
|
return lambda($combined, async ({ input, withPgClient, pgSettings }) => {
|
|
229
|
-
const { bucketKey } = input;
|
|
286
|
+
const { bucketKey, ownerId } = input;
|
|
230
287
|
if (!bucketKey || typeof bucketKey !== 'string') {
|
|
231
288
|
throw new Error('INVALID_BUCKET_KEY');
|
|
232
289
|
}
|
|
@@ -236,17 +293,25 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
236
293
|
if (!databaseId) {
|
|
237
294
|
throw new Error('DATABASE_NOT_FOUND');
|
|
238
295
|
}
|
|
239
|
-
//
|
|
240
|
-
const
|
|
241
|
-
if (
|
|
242
|
-
throw new Error(
|
|
296
|
+
// Resolve storage module (app-level or entity-scoped via ownerId)
|
|
297
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId, ownerId);
|
|
298
|
+
if (!storageModule) {
|
|
299
|
+
throw new Error(ownerId
|
|
300
|
+
? 'STORAGE_MODULE_NOT_FOUND_FOR_OWNER: no storage module found for the given ownerId'
|
|
301
|
+
: 'STORAGE_MODULE_NOT_PROVISIONED');
|
|
243
302
|
}
|
|
244
|
-
const storageModule = smResult.rows[0];
|
|
245
303
|
// Look up the bucket row (RLS enforced via pgSettings)
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
304
|
+
const hasOwner = ownerId && storageModule.membership_type !== null;
|
|
305
|
+
const bucketsTable = QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
306
|
+
const bucketResult = await pgClient.query(hasOwner
|
|
307
|
+
? `SELECT id, key, type, is_public, allowed_origins
|
|
308
|
+
FROM ${bucketsTable}
|
|
309
|
+
WHERE key = $1 AND owner_id = $2
|
|
310
|
+
LIMIT 1`
|
|
311
|
+
: `SELECT id, key, type, is_public, allowed_origins
|
|
312
|
+
FROM ${bucketsTable}
|
|
313
|
+
WHERE key = $1
|
|
314
|
+
LIMIT 1`, hasOwner ? [bucketKey, ownerId] : [bucketKey]);
|
|
250
315
|
if (bucketResult.rows.length === 0) {
|
|
251
316
|
throw new Error('BUCKET_NOT_FOUND');
|
|
252
317
|
}
|
|
@@ -369,13 +434,12 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
369
434
|
log.warn('CORS update skipped: could not resolve database_id');
|
|
370
435
|
return;
|
|
371
436
|
}
|
|
372
|
-
// Read the
|
|
373
|
-
const
|
|
374
|
-
if (
|
|
437
|
+
// Read the storage module config (app-level; auto-hook doesn't have ownerId context)
|
|
438
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
439
|
+
if (!storageModule) {
|
|
375
440
|
log.warn('CORS update skipped: storage module not provisioned');
|
|
376
441
|
return;
|
|
377
442
|
}
|
|
378
|
-
const storageModule = smResult.rows[0];
|
|
379
443
|
// We need the bucket key — it may come from input or patch
|
|
380
444
|
// For updates, PostGraphile uses nodeId or the row's PK, so
|
|
381
445
|
// we read the bucket from the patch's key or from the nodeId
|
|
@@ -386,8 +450,9 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
386
450
|
return;
|
|
387
451
|
}
|
|
388
452
|
// Read the full bucket row (post-update) to get type + origins
|
|
453
|
+
const bucketsTable = QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
389
454
|
const bucketResult = await pgClient.query(`SELECT id, key, type, is_public, allowed_origins
|
|
390
|
-
FROM
|
|
455
|
+
FROM ${bucketsTable}
|
|
391
456
|
WHERE key = $1
|
|
392
457
|
LIMIT 1`, [patchKey]);
|
|
393
458
|
if (bucketResult.rows.length === 0) {
|
package/esm/types.d.ts
CHANGED
|
@@ -70,6 +70,11 @@ export interface BucketProvisionerPluginOptions {
|
|
|
70
70
|
export interface ProvisionBucketInput {
|
|
71
71
|
/** The logical bucket key (e.g., "public", "private") */
|
|
72
72
|
bucketKey: string;
|
|
73
|
+
/**
|
|
74
|
+
* Owner entity ID for entity-scoped bucket provisioning.
|
|
75
|
+
* Omit for app-level (database-wide) storage.
|
|
76
|
+
*/
|
|
77
|
+
ownerId?: string;
|
|
73
78
|
}
|
|
74
79
|
/**
|
|
75
80
|
* Result of the provisionBucket mutation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-bucket-provisioner-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@constructive-io/bucket-provisioner": "^0.3.0",
|
|
44
|
-
"@pgpmjs/logger": "^2.6.0"
|
|
44
|
+
"@pgpmjs/logger": "^2.6.0",
|
|
45
|
+
"@pgsql/quotes": "^17.1.0"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
47
48
|
"grafast": "1.0.0",
|
|
@@ -56,5 +57,5 @@
|
|
|
56
57
|
"@types/node": "^22.19.11",
|
|
57
58
|
"makage": "^0.3.0"
|
|
58
59
|
},
|
|
59
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "28734dd71a973b2fe296e8240c8f86c568b4292f"
|
|
60
61
|
}
|
package/plugin.js
CHANGED
|
@@ -37,12 +37,18 @@ exports.createBucketProvisionerPlugin = createBucketProvisionerPlugin;
|
|
|
37
37
|
const grafast_1 = require("grafast");
|
|
38
38
|
const graphile_utils_1 = require("graphile-utils");
|
|
39
39
|
const logger_1 = require("@pgpmjs/logger");
|
|
40
|
+
const quotes_1 = require("@pgsql/quotes");
|
|
40
41
|
const bucket_provisioner_1 = require("@constructive-io/bucket-provisioner");
|
|
41
42
|
const log = new logger_1.Logger('graphile-bucket-provisioner:plugin');
|
|
42
|
-
// --- Storage module
|
|
43
|
-
|
|
43
|
+
// --- Storage module queries ---
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the app-level storage module (membership_type IS NULL).
|
|
46
|
+
*/
|
|
47
|
+
const APP_STORAGE_MODULE_QUERY = `
|
|
44
48
|
SELECT
|
|
45
49
|
sm.id,
|
|
50
|
+
sm.membership_type,
|
|
51
|
+
sm.entity_table_id,
|
|
46
52
|
bs.schema_name AS buckets_schema,
|
|
47
53
|
bt.name AS buckets_table,
|
|
48
54
|
sm.endpoint,
|
|
@@ -53,8 +59,56 @@ const STORAGE_MODULE_QUERY = `
|
|
|
53
59
|
JOIN metaschema_public.table bt ON bt.id = sm.buckets_table_id
|
|
54
60
|
JOIN metaschema_public.schema bs ON bs.id = bt.schema_id
|
|
55
61
|
WHERE sm.database_id = $1
|
|
62
|
+
AND sm.membership_type IS NULL
|
|
56
63
|
LIMIT 1
|
|
57
64
|
`;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve ALL storage modules for a database (for ownerId-based resolution).
|
|
67
|
+
*/
|
|
68
|
+
const ALL_STORAGE_MODULES_QUERY = `
|
|
69
|
+
SELECT
|
|
70
|
+
sm.id,
|
|
71
|
+
sm.membership_type,
|
|
72
|
+
sm.entity_table_id,
|
|
73
|
+
bs.schema_name AS buckets_schema,
|
|
74
|
+
bt.name AS buckets_table,
|
|
75
|
+
sm.endpoint,
|
|
76
|
+
sm.public_url_prefix,
|
|
77
|
+
sm.provider,
|
|
78
|
+
sm.allowed_origins,
|
|
79
|
+
es.schema_name AS entity_schema,
|
|
80
|
+
et.name AS entity_table
|
|
81
|
+
FROM metaschema_modules_public.storage_module sm
|
|
82
|
+
JOIN metaschema_public.table bt ON bt.id = sm.buckets_table_id
|
|
83
|
+
JOIN metaschema_public.schema bs ON bs.id = bt.schema_id
|
|
84
|
+
LEFT JOIN metaschema_public.table et ON et.id = sm.entity_table_id
|
|
85
|
+
LEFT JOIN metaschema_public.schema es ON es.id = et.schema_id
|
|
86
|
+
WHERE sm.database_id = $1
|
|
87
|
+
`;
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the storage module for a given scope.
|
|
90
|
+
* If ownerId is provided, probes entity tables to find the matching module.
|
|
91
|
+
* Otherwise, returns the app-level module.
|
|
92
|
+
*/
|
|
93
|
+
async function resolveStorageModule(pgClient, databaseId, ownerId) {
|
|
94
|
+
if (!ownerId) {
|
|
95
|
+
// App-level resolution
|
|
96
|
+
const result = await pgClient.query(APP_STORAGE_MODULE_QUERY, [databaseId]);
|
|
97
|
+
return result.rows[0] ?? null;
|
|
98
|
+
}
|
|
99
|
+
// Entity-scoped: load all modules and probe entity tables
|
|
100
|
+
const result = await pgClient.query(ALL_STORAGE_MODULES_QUERY, [databaseId]);
|
|
101
|
+
const modules = result.rows;
|
|
102
|
+
const entityModules = modules.filter((m) => m.entity_schema && m.entity_table);
|
|
103
|
+
for (const mod of entityModules) {
|
|
104
|
+
const entityTable = quotes_1.QuoteUtils.quoteQualifiedIdentifier(mod.entity_schema, mod.entity_table);
|
|
105
|
+
const probe = await pgClient.query(`SELECT 1 FROM ${entityTable} WHERE id = $1 LIMIT 1`, [ownerId]);
|
|
106
|
+
if (probe.rows.length > 0) {
|
|
107
|
+
return mod;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
58
112
|
// --- Helpers ---
|
|
59
113
|
/**
|
|
60
114
|
* Resolve the connection config from the options. If the option is a lazy
|
|
@@ -128,8 +182,7 @@ async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType
|
|
|
128
182
|
const s3BucketName = resolveBucketName(bucketKey, databaseId, options);
|
|
129
183
|
const accessType = bucketType;
|
|
130
184
|
// Read storage module config to check for endpoint/provider/CORS overrides
|
|
131
|
-
const
|
|
132
|
-
const storageModule = smResult.rows[0] ?? null;
|
|
185
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
133
186
|
// Resolve CORS origins using the 3-tier hierarchy
|
|
134
187
|
const effectiveOrigins = resolveAllowedOrigins(bucketAllowedOrigins, storageModule?.allowed_origins, options.allowedOrigins);
|
|
135
188
|
const provisioner = buildProvisioner(options, storageModule, effectiveOrigins);
|
|
@@ -152,8 +205,7 @@ async function provisionBucketForRow(pgClient, databaseId, bucketKey, bucketType
|
|
|
152
205
|
async function updateBucketCors(pgClient, databaseId, bucketKey, bucketType, bucketAllowedOrigins, options) {
|
|
153
206
|
const s3BucketName = resolveBucketName(bucketKey, databaseId, options);
|
|
154
207
|
const accessType = bucketType;
|
|
155
|
-
const
|
|
156
|
-
const storageModule = smResult.rows[0] ?? null;
|
|
208
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
157
209
|
const effectiveOrigins = resolveAllowedOrigins(bucketAllowedOrigins, storageModule?.allowed_origins, options.allowedOrigins);
|
|
158
210
|
const provisioner = buildProvisioner(options, storageModule, effectiveOrigins);
|
|
159
211
|
log.info(`Updating CORS on S3 bucket "${s3BucketName}" ` +
|
|
@@ -189,6 +241,11 @@ function createBucketProvisionerPlugin(options) {
|
|
|
189
241
|
input ProvisionBucketInput {
|
|
190
242
|
"""The logical bucket key (e.g., "public", "private")"""
|
|
191
243
|
bucketKey: String!
|
|
244
|
+
"""
|
|
245
|
+
Owner entity ID for entity-scoped bucket provisioning.
|
|
246
|
+
Omit for app-level (database-wide) storage.
|
|
247
|
+
"""
|
|
248
|
+
ownerId: UUID
|
|
192
249
|
}
|
|
193
250
|
|
|
194
251
|
type ProvisionBucketPayload {
|
|
@@ -230,7 +287,7 @@ function createBucketProvisionerPlugin(options) {
|
|
|
230
287
|
pgSettings: $pgSettings,
|
|
231
288
|
});
|
|
232
289
|
return (0, grafast_1.lambda)($combined, async ({ input, withPgClient, pgSettings }) => {
|
|
233
|
-
const { bucketKey } = input;
|
|
290
|
+
const { bucketKey, ownerId } = input;
|
|
234
291
|
if (!bucketKey || typeof bucketKey !== 'string') {
|
|
235
292
|
throw new Error('INVALID_BUCKET_KEY');
|
|
236
293
|
}
|
|
@@ -240,17 +297,25 @@ function createBucketProvisionerPlugin(options) {
|
|
|
240
297
|
if (!databaseId) {
|
|
241
298
|
throw new Error('DATABASE_NOT_FOUND');
|
|
242
299
|
}
|
|
243
|
-
//
|
|
244
|
-
const
|
|
245
|
-
if (
|
|
246
|
-
throw new Error(
|
|
300
|
+
// Resolve storage module (app-level or entity-scoped via ownerId)
|
|
301
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId, ownerId);
|
|
302
|
+
if (!storageModule) {
|
|
303
|
+
throw new Error(ownerId
|
|
304
|
+
? 'STORAGE_MODULE_NOT_FOUND_FOR_OWNER: no storage module found for the given ownerId'
|
|
305
|
+
: 'STORAGE_MODULE_NOT_PROVISIONED');
|
|
247
306
|
}
|
|
248
|
-
const storageModule = smResult.rows[0];
|
|
249
307
|
// Look up the bucket row (RLS enforced via pgSettings)
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
308
|
+
const hasOwner = ownerId && storageModule.membership_type !== null;
|
|
309
|
+
const bucketsTable = quotes_1.QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
310
|
+
const bucketResult = await pgClient.query(hasOwner
|
|
311
|
+
? `SELECT id, key, type, is_public, allowed_origins
|
|
312
|
+
FROM ${bucketsTable}
|
|
313
|
+
WHERE key = $1 AND owner_id = $2
|
|
314
|
+
LIMIT 1`
|
|
315
|
+
: `SELECT id, key, type, is_public, allowed_origins
|
|
316
|
+
FROM ${bucketsTable}
|
|
317
|
+
WHERE key = $1
|
|
318
|
+
LIMIT 1`, hasOwner ? [bucketKey, ownerId] : [bucketKey]);
|
|
254
319
|
if (bucketResult.rows.length === 0) {
|
|
255
320
|
throw new Error('BUCKET_NOT_FOUND');
|
|
256
321
|
}
|
|
@@ -373,13 +438,12 @@ function createBucketProvisionerPlugin(options) {
|
|
|
373
438
|
log.warn('CORS update skipped: could not resolve database_id');
|
|
374
439
|
return;
|
|
375
440
|
}
|
|
376
|
-
// Read the
|
|
377
|
-
const
|
|
378
|
-
if (
|
|
441
|
+
// Read the storage module config (app-level; auto-hook doesn't have ownerId context)
|
|
442
|
+
const storageModule = await resolveStorageModule(pgClient, databaseId);
|
|
443
|
+
if (!storageModule) {
|
|
379
444
|
log.warn('CORS update skipped: storage module not provisioned');
|
|
380
445
|
return;
|
|
381
446
|
}
|
|
382
|
-
const storageModule = smResult.rows[0];
|
|
383
447
|
// We need the bucket key — it may come from input or patch
|
|
384
448
|
// For updates, PostGraphile uses nodeId or the row's PK, so
|
|
385
449
|
// we read the bucket from the patch's key or from the nodeId
|
|
@@ -390,8 +454,9 @@ function createBucketProvisionerPlugin(options) {
|
|
|
390
454
|
return;
|
|
391
455
|
}
|
|
392
456
|
// Read the full bucket row (post-update) to get type + origins
|
|
457
|
+
const bucketsTable = quotes_1.QuoteUtils.quoteQualifiedIdentifier(storageModule.buckets_schema, storageModule.buckets_table);
|
|
393
458
|
const bucketResult = await pgClient.query(`SELECT id, key, type, is_public, allowed_origins
|
|
394
|
-
FROM
|
|
459
|
+
FROM ${bucketsTable}
|
|
395
460
|
WHERE key = $1
|
|
396
461
|
LIMIT 1`, [patchKey]);
|
|
397
462
|
if (bucketResult.rows.length === 0) {
|
package/types.d.ts
CHANGED
|
@@ -70,6 +70,11 @@ export interface BucketProvisionerPluginOptions {
|
|
|
70
70
|
export interface ProvisionBucketInput {
|
|
71
71
|
/** The logical bucket key (e.g., "public", "private") */
|
|
72
72
|
bucketKey: string;
|
|
73
|
+
/**
|
|
74
|
+
* Owner entity ID for entity-scoped bucket provisioning.
|
|
75
|
+
* Omit for app-level (database-wide) storage.
|
|
76
|
+
*/
|
|
77
|
+
ownerId?: string;
|
|
73
78
|
}
|
|
74
79
|
/**
|
|
75
80
|
* Result of the provisionBucket mutation.
|