graphile-presigned-url-plugin 0.19.1 → 0.20.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 +1 -1
- package/esm/storage-module-cache.d.ts +1 -1
- package/esm/storage-module-cache.js +16 -19
- package/esm/types.d.ts +2 -2
- package/package.json +4 -4
- package/plugin.js +1 -1
- package/storage-module-cache.d.ts +1 -1
- package/storage-module-cache.js +16 -19
- package/types.d.ts +2 -2
package/esm/plugin.js
CHANGED
|
@@ -559,7 +559,7 @@ async function processSingleFile(options, txClient, storageConfig, databaseId, b
|
|
|
559
559
|
// Auto-derive ltree path from custom key directory (only when has_path_shares)
|
|
560
560
|
const derivedPath = isCustomKey && storageConfig.hasPathShares ? derivePathFromKey(s3Key) : null;
|
|
561
561
|
// Create file record
|
|
562
|
-
const hasOwnerColumn = storageConfig.
|
|
562
|
+
const hasOwnerColumn = storageConfig.scope !== 'app';
|
|
563
563
|
const columns = ['bucket_id', 'key', 'content_hash', 'mime_type', 'size', 'filename', 'is_public'];
|
|
564
564
|
const values = [bucket.id, s3Key, contentHash, contentType, size, filename || null, bucket.is_public];
|
|
565
565
|
if (hasOwnerColumn) {
|
|
@@ -3,7 +3,7 @@ import type { StorageModuleConfig, BucketConfig } from './types';
|
|
|
3
3
|
* Resolve the app-level storage module config for a database, using the LRU cache.
|
|
4
4
|
*
|
|
5
5
|
* This is the default path when no ownerId is provided. It returns the
|
|
6
|
-
* storage module with
|
|
6
|
+
* storage module with scope = 'app' (app-level / database-wide).
|
|
7
7
|
*
|
|
8
8
|
* @param pgClient - A pg client from the Graphile context (withPgClient or pgClient)
|
|
9
9
|
* @param databaseId - The metaschema database UUID
|
|
@@ -31,14 +31,14 @@ const storageModuleCache = new LRUCache({
|
|
|
31
31
|
* SQL query to resolve the app-level storage module config for a database.
|
|
32
32
|
*
|
|
33
33
|
* Joins storage_module → table → schema to get fully-qualified table names.
|
|
34
|
-
* Filters to app-level (
|
|
34
|
+
* Filters to app-level (scope = 'app') by default.
|
|
35
35
|
*
|
|
36
|
-
* Requires the multi-scope schema (
|
|
36
|
+
* Requires the multi-scope schema (scope column on storage_module).
|
|
37
37
|
*/
|
|
38
38
|
const APP_STORAGE_MODULE_QUERY = `
|
|
39
39
|
SELECT
|
|
40
40
|
sm.id,
|
|
41
|
-
sm.
|
|
41
|
+
sm.scope,
|
|
42
42
|
sm.entity_table_id,
|
|
43
43
|
bs.schema_name AS buckets_schema,
|
|
44
44
|
bt.name AS buckets_table,
|
|
@@ -64,7 +64,7 @@ const APP_STORAGE_MODULE_QUERY = `
|
|
|
64
64
|
JOIN metaschema_public.table ft ON ft.id = sm.files_table_id
|
|
65
65
|
JOIN metaschema_public.schema fs ON fs.id = ft.schema_id
|
|
66
66
|
WHERE sm.database_id = $1
|
|
67
|
-
AND sm.
|
|
67
|
+
AND sm.scope = 'app'
|
|
68
68
|
LIMIT 1
|
|
69
69
|
`;
|
|
70
70
|
/**
|
|
@@ -76,7 +76,7 @@ const APP_STORAGE_MODULE_QUERY = `
|
|
|
76
76
|
const ALL_STORAGE_MODULES_QUERY = `
|
|
77
77
|
SELECT
|
|
78
78
|
sm.id,
|
|
79
|
-
sm.
|
|
79
|
+
sm.scope,
|
|
80
80
|
sm.entity_table_id,
|
|
81
81
|
bs.schema_name AS buckets_schema,
|
|
82
82
|
bt.name AS buckets_table,
|
|
@@ -117,7 +117,7 @@ function buildConfig(row) {
|
|
|
117
117
|
schemaName: row.buckets_schema,
|
|
118
118
|
bucketsTableName: row.buckets_table,
|
|
119
119
|
filesTableName: row.files_table,
|
|
120
|
-
|
|
120
|
+
scope: row.scope,
|
|
121
121
|
entityTableId: row.entity_table_id,
|
|
122
122
|
entityQualifiedName: row.entity_schema && row.entity_table
|
|
123
123
|
? QuoteUtils.quoteQualifiedIdentifier(row.entity_schema, row.entity_table)
|
|
@@ -140,7 +140,7 @@ function buildConfig(row) {
|
|
|
140
140
|
* Resolve the app-level storage module config for a database, using the LRU cache.
|
|
141
141
|
*
|
|
142
142
|
* This is the default path when no ownerId is provided. It returns the
|
|
143
|
-
* storage module with
|
|
143
|
+
* storage module with scope = 'app' (app-level / database-wide).
|
|
144
144
|
*
|
|
145
145
|
* @param pgClient - A pg client from the Graphile context (withPgClient or pgClient)
|
|
146
146
|
* @param databaseId - The metaschema database UUID
|
|
@@ -201,11 +201,9 @@ export async function getStorageModuleConfigForOwner(pgClient, databaseId, owner
|
|
|
201
201
|
log.debug(`Loading all storage modules for database ${databaseId} to resolve ownerId ${ownerId}`);
|
|
202
202
|
const result = await pgClient.query({ text: ALL_STORAGE_MODULES_QUERY, values: [databaseId] });
|
|
203
203
|
allConfigs = result.rows.map(buildConfig);
|
|
204
|
-
// Cache each individual config by its
|
|
204
|
+
// Cache each individual config by its scope
|
|
205
205
|
for (const config of allConfigs) {
|
|
206
|
-
const key = config.
|
|
207
|
-
? `storage:${databaseId}:app`
|
|
208
|
-
: `storage:${databaseId}:mt:${config.membershipType}`;
|
|
206
|
+
const key = `storage:${databaseId}:scope:${config.scope}`;
|
|
209
207
|
storageModuleCache.set(key, config);
|
|
210
208
|
}
|
|
211
209
|
}
|
|
@@ -220,7 +218,7 @@ export async function getStorageModuleConfigForOwner(pgClient, databaseId, owner
|
|
|
220
218
|
// Found the matching module — cache the ownerId→module mapping
|
|
221
219
|
storageModuleCache.set(ownerCacheKey, mod);
|
|
222
220
|
log.debug(`Resolved ownerId ${ownerId} to storage module ${mod.id} ` +
|
|
223
|
-
`(
|
|
221
|
+
`(scope=${mod.scope}, table=${mod.bucketsQualifiedName})`);
|
|
224
222
|
return mod;
|
|
225
223
|
}
|
|
226
224
|
}
|
|
@@ -272,11 +270,9 @@ export async function loadAllStorageModules(pgClient, databaseId) {
|
|
|
272
270
|
log.debug(`Loading all storage modules for database ${databaseId}`);
|
|
273
271
|
const result = await pgClient.query({ text: ALL_STORAGE_MODULES_QUERY, values: [databaseId] });
|
|
274
272
|
const configs = result.rows.map(buildConfig);
|
|
275
|
-
// Cache each individual config by its
|
|
273
|
+
// Cache each individual config by its scope
|
|
276
274
|
for (const config of configs) {
|
|
277
|
-
const key = config.
|
|
278
|
-
? `storage:${databaseId}:app`
|
|
279
|
-
: `storage:${databaseId}:mt:${config.membershipType}`;
|
|
275
|
+
const key = `storage:${databaseId}:scope:${config.scope}`;
|
|
280
276
|
storageModuleCache.set(key, config);
|
|
281
277
|
}
|
|
282
278
|
// Store the full list under a sentinel key
|
|
@@ -344,14 +340,15 @@ export async function getBucketConfig(pgClient, storageConfig, databaseId, bucke
|
|
|
344
340
|
log.debug(`Bucket cache miss for ${databaseId}:${bucketKey}${ownerId ? ` (owner=${ownerId})` : ''}, querying DB...`);
|
|
345
341
|
// Entity-scoped buckets use (owner_id, key) composite lookup;
|
|
346
342
|
// app-level buckets just use key.
|
|
347
|
-
const
|
|
343
|
+
const isEntityScoped = storageConfig.scope !== 'app';
|
|
344
|
+
const hasOwner = ownerId && isEntityScoped;
|
|
348
345
|
const result = await pgClient.query({
|
|
349
346
|
text: hasOwner
|
|
350
347
|
? `SELECT id, key, type, is_public, owner_id, allowed_mime_types, max_file_size, allow_custom_keys
|
|
351
348
|
FROM ${storageConfig.bucketsQualifiedName}
|
|
352
349
|
WHERE key = $1 AND owner_id = $2
|
|
353
350
|
LIMIT 1`
|
|
354
|
-
: `SELECT id, key, type, is_public, ${
|
|
351
|
+
: `SELECT id, key, type, is_public, ${isEntityScoped ? 'owner_id,' : ''} allowed_mime_types, max_file_size, allow_custom_keys
|
|
355
352
|
FROM ${storageConfig.bucketsQualifiedName}
|
|
356
353
|
WHERE key = $1
|
|
357
354
|
LIMIT 1`,
|
|
@@ -372,7 +369,7 @@ export async function getBucketConfig(pgClient, storageConfig, databaseId, bucke
|
|
|
372
369
|
allow_custom_keys: row.allow_custom_keys ?? false,
|
|
373
370
|
};
|
|
374
371
|
bucketCache.set(cacheKey, config);
|
|
375
|
-
log.debug(`Cached bucket config for ${databaseId}:${bucketKey} (id=${config.id}, scope=${storageConfig.
|
|
372
|
+
log.debug(`Cached bucket config for ${databaseId}:${bucketKey} (id=${config.id}, scope=${storageConfig.scope})`);
|
|
376
373
|
return config;
|
|
377
374
|
}
|
|
378
375
|
// --- S3 bucket existence cache ---
|
package/esm/types.d.ts
CHANGED
|
@@ -28,8 +28,8 @@ export interface StorageModuleConfig {
|
|
|
28
28
|
bucketsTableName: string;
|
|
29
29
|
/** Files table name */
|
|
30
30
|
filesTableName: string;
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/** Scope name (e.g., 'app', 'org', 'team') */
|
|
32
|
+
scope: string;
|
|
33
33
|
/** Entity table ID for entity-scoped storage (NULL for app-level) */
|
|
34
34
|
entityTableId: string | null;
|
|
35
35
|
/** Qualified entity table name for ownerId lookups (NULL for app-level) */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-presigned-url-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
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",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@aws-sdk/client-s3": "^3.1052.0",
|
|
44
44
|
"@aws-sdk/s3-request-presigner": "^3.1052.0",
|
|
45
|
-
"@pgpmjs/logger": "^2.
|
|
45
|
+
"@pgpmjs/logger": "^2.12.0",
|
|
46
46
|
"@pgsql/quotes": "^17.1.0",
|
|
47
47
|
"lru-cache": "^11.2.7"
|
|
48
48
|
},
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"postgraphile": "5.0.3"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@constructive-io/s3-utils": "^2.
|
|
59
|
+
"@constructive-io/s3-utils": "^2.18.0",
|
|
60
60
|
"@types/node": "^22.19.11",
|
|
61
61
|
"makage": "^0.3.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "0503916f414a3b8a6f6cbe46e03a59988b2f3cb5"
|
|
64
64
|
}
|
package/plugin.js
CHANGED
|
@@ -563,7 +563,7 @@ async function processSingleFile(options, txClient, storageConfig, databaseId, b
|
|
|
563
563
|
// Auto-derive ltree path from custom key directory (only when has_path_shares)
|
|
564
564
|
const derivedPath = isCustomKey && storageConfig.hasPathShares ? derivePathFromKey(s3Key) : null;
|
|
565
565
|
// Create file record
|
|
566
|
-
const hasOwnerColumn = storageConfig.
|
|
566
|
+
const hasOwnerColumn = storageConfig.scope !== 'app';
|
|
567
567
|
const columns = ['bucket_id', 'key', 'content_hash', 'mime_type', 'size', 'filename', 'is_public'];
|
|
568
568
|
const values = [bucket.id, s3Key, contentHash, contentType, size, filename || null, bucket.is_public];
|
|
569
569
|
if (hasOwnerColumn) {
|
|
@@ -3,7 +3,7 @@ import type { StorageModuleConfig, BucketConfig } from './types';
|
|
|
3
3
|
* Resolve the app-level storage module config for a database, using the LRU cache.
|
|
4
4
|
*
|
|
5
5
|
* This is the default path when no ownerId is provided. It returns the
|
|
6
|
-
* storage module with
|
|
6
|
+
* storage module with scope = 'app' (app-level / database-wide).
|
|
7
7
|
*
|
|
8
8
|
* @param pgClient - A pg client from the Graphile context (withPgClient or pgClient)
|
|
9
9
|
* @param databaseId - The metaschema database UUID
|
package/storage-module-cache.js
CHANGED
|
@@ -43,14 +43,14 @@ const storageModuleCache = new lru_cache_1.LRUCache({
|
|
|
43
43
|
* SQL query to resolve the app-level storage module config for a database.
|
|
44
44
|
*
|
|
45
45
|
* Joins storage_module → table → schema to get fully-qualified table names.
|
|
46
|
-
* Filters to app-level (
|
|
46
|
+
* Filters to app-level (scope = 'app') by default.
|
|
47
47
|
*
|
|
48
|
-
* Requires the multi-scope schema (
|
|
48
|
+
* Requires the multi-scope schema (scope column on storage_module).
|
|
49
49
|
*/
|
|
50
50
|
const APP_STORAGE_MODULE_QUERY = `
|
|
51
51
|
SELECT
|
|
52
52
|
sm.id,
|
|
53
|
-
sm.
|
|
53
|
+
sm.scope,
|
|
54
54
|
sm.entity_table_id,
|
|
55
55
|
bs.schema_name AS buckets_schema,
|
|
56
56
|
bt.name AS buckets_table,
|
|
@@ -76,7 +76,7 @@ const APP_STORAGE_MODULE_QUERY = `
|
|
|
76
76
|
JOIN metaschema_public.table ft ON ft.id = sm.files_table_id
|
|
77
77
|
JOIN metaschema_public.schema fs ON fs.id = ft.schema_id
|
|
78
78
|
WHERE sm.database_id = $1
|
|
79
|
-
AND sm.
|
|
79
|
+
AND sm.scope = 'app'
|
|
80
80
|
LIMIT 1
|
|
81
81
|
`;
|
|
82
82
|
/**
|
|
@@ -88,7 +88,7 @@ const APP_STORAGE_MODULE_QUERY = `
|
|
|
88
88
|
const ALL_STORAGE_MODULES_QUERY = `
|
|
89
89
|
SELECT
|
|
90
90
|
sm.id,
|
|
91
|
-
sm.
|
|
91
|
+
sm.scope,
|
|
92
92
|
sm.entity_table_id,
|
|
93
93
|
bs.schema_name AS buckets_schema,
|
|
94
94
|
bt.name AS buckets_table,
|
|
@@ -129,7 +129,7 @@ function buildConfig(row) {
|
|
|
129
129
|
schemaName: row.buckets_schema,
|
|
130
130
|
bucketsTableName: row.buckets_table,
|
|
131
131
|
filesTableName: row.files_table,
|
|
132
|
-
|
|
132
|
+
scope: row.scope,
|
|
133
133
|
entityTableId: row.entity_table_id,
|
|
134
134
|
entityQualifiedName: row.entity_schema && row.entity_table
|
|
135
135
|
? quotes_1.QuoteUtils.quoteQualifiedIdentifier(row.entity_schema, row.entity_table)
|
|
@@ -152,7 +152,7 @@ function buildConfig(row) {
|
|
|
152
152
|
* Resolve the app-level storage module config for a database, using the LRU cache.
|
|
153
153
|
*
|
|
154
154
|
* This is the default path when no ownerId is provided. It returns the
|
|
155
|
-
* storage module with
|
|
155
|
+
* storage module with scope = 'app' (app-level / database-wide).
|
|
156
156
|
*
|
|
157
157
|
* @param pgClient - A pg client from the Graphile context (withPgClient or pgClient)
|
|
158
158
|
* @param databaseId - The metaschema database UUID
|
|
@@ -213,11 +213,9 @@ async function getStorageModuleConfigForOwner(pgClient, databaseId, ownerId) {
|
|
|
213
213
|
log.debug(`Loading all storage modules for database ${databaseId} to resolve ownerId ${ownerId}`);
|
|
214
214
|
const result = await pgClient.query({ text: ALL_STORAGE_MODULES_QUERY, values: [databaseId] });
|
|
215
215
|
allConfigs = result.rows.map(buildConfig);
|
|
216
|
-
// Cache each individual config by its
|
|
216
|
+
// Cache each individual config by its scope
|
|
217
217
|
for (const config of allConfigs) {
|
|
218
|
-
const key = config.
|
|
219
|
-
? `storage:${databaseId}:app`
|
|
220
|
-
: `storage:${databaseId}:mt:${config.membershipType}`;
|
|
218
|
+
const key = `storage:${databaseId}:scope:${config.scope}`;
|
|
221
219
|
storageModuleCache.set(key, config);
|
|
222
220
|
}
|
|
223
221
|
}
|
|
@@ -232,7 +230,7 @@ async function getStorageModuleConfigForOwner(pgClient, databaseId, ownerId) {
|
|
|
232
230
|
// Found the matching module — cache the ownerId→module mapping
|
|
233
231
|
storageModuleCache.set(ownerCacheKey, mod);
|
|
234
232
|
log.debug(`Resolved ownerId ${ownerId} to storage module ${mod.id} ` +
|
|
235
|
-
`(
|
|
233
|
+
`(scope=${mod.scope}, table=${mod.bucketsQualifiedName})`);
|
|
236
234
|
return mod;
|
|
237
235
|
}
|
|
238
236
|
}
|
|
@@ -284,11 +282,9 @@ async function loadAllStorageModules(pgClient, databaseId) {
|
|
|
284
282
|
log.debug(`Loading all storage modules for database ${databaseId}`);
|
|
285
283
|
const result = await pgClient.query({ text: ALL_STORAGE_MODULES_QUERY, values: [databaseId] });
|
|
286
284
|
const configs = result.rows.map(buildConfig);
|
|
287
|
-
// Cache each individual config by its
|
|
285
|
+
// Cache each individual config by its scope
|
|
288
286
|
for (const config of configs) {
|
|
289
|
-
const key = config.
|
|
290
|
-
? `storage:${databaseId}:app`
|
|
291
|
-
: `storage:${databaseId}:mt:${config.membershipType}`;
|
|
287
|
+
const key = `storage:${databaseId}:scope:${config.scope}`;
|
|
292
288
|
storageModuleCache.set(key, config);
|
|
293
289
|
}
|
|
294
290
|
// Store the full list under a sentinel key
|
|
@@ -356,14 +352,15 @@ async function getBucketConfig(pgClient, storageConfig, databaseId, bucketKey, o
|
|
|
356
352
|
log.debug(`Bucket cache miss for ${databaseId}:${bucketKey}${ownerId ? ` (owner=${ownerId})` : ''}, querying DB...`);
|
|
357
353
|
// Entity-scoped buckets use (owner_id, key) composite lookup;
|
|
358
354
|
// app-level buckets just use key.
|
|
359
|
-
const
|
|
355
|
+
const isEntityScoped = storageConfig.scope !== 'app';
|
|
356
|
+
const hasOwner = ownerId && isEntityScoped;
|
|
360
357
|
const result = await pgClient.query({
|
|
361
358
|
text: hasOwner
|
|
362
359
|
? `SELECT id, key, type, is_public, owner_id, allowed_mime_types, max_file_size, allow_custom_keys
|
|
363
360
|
FROM ${storageConfig.bucketsQualifiedName}
|
|
364
361
|
WHERE key = $1 AND owner_id = $2
|
|
365
362
|
LIMIT 1`
|
|
366
|
-
: `SELECT id, key, type, is_public, ${
|
|
363
|
+
: `SELECT id, key, type, is_public, ${isEntityScoped ? 'owner_id,' : ''} allowed_mime_types, max_file_size, allow_custom_keys
|
|
367
364
|
FROM ${storageConfig.bucketsQualifiedName}
|
|
368
365
|
WHERE key = $1
|
|
369
366
|
LIMIT 1`,
|
|
@@ -384,7 +381,7 @@ async function getBucketConfig(pgClient, storageConfig, databaseId, bucketKey, o
|
|
|
384
381
|
allow_custom_keys: row.allow_custom_keys ?? false,
|
|
385
382
|
};
|
|
386
383
|
bucketCache.set(cacheKey, config);
|
|
387
|
-
log.debug(`Cached bucket config for ${databaseId}:${bucketKey} (id=${config.id}, scope=${storageConfig.
|
|
384
|
+
log.debug(`Cached bucket config for ${databaseId}:${bucketKey} (id=${config.id}, scope=${storageConfig.scope})`);
|
|
388
385
|
return config;
|
|
389
386
|
}
|
|
390
387
|
// --- S3 bucket existence cache ---
|
package/types.d.ts
CHANGED
|
@@ -28,8 +28,8 @@ export interface StorageModuleConfig {
|
|
|
28
28
|
bucketsTableName: string;
|
|
29
29
|
/** Files table name */
|
|
30
30
|
filesTableName: string;
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/** Scope name (e.g., 'app', 'org', 'team') */
|
|
32
|
+
scope: string;
|
|
33
33
|
/** Entity table ID for entity-scoped storage (NULL for app-level) */
|
|
34
34
|
entityTableId: string | null;
|
|
35
35
|
/** Qualified entity table name for ownerId lookups (NULL for app-level) */
|