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 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.membershipType !== null;
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 membership_type IS NULL (app-level / database-wide).
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 (membership_type IS NULL) by default.
34
+ * Filters to app-level (scope = 'app') by default.
35
35
  *
36
- * Requires the multi-scope schema (membership_type column on storage_module).
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.membership_type,
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.membership_type IS NULL
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.membership_type,
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
- membershipType: row.membership_type,
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 membership_type IS NULL (app-level / database-wide).
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 membership type
204
+ // Cache each individual config by its scope
205
205
  for (const config of allConfigs) {
206
- const key = config.membershipType === null
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
- `(membershipType=${mod.membershipType}, table=${mod.bucketsQualifiedName})`);
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 membership type
273
+ // Cache each individual config by its scope
276
274
  for (const config of configs) {
277
- const key = config.membershipType === null
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 hasOwner = ownerId && storageConfig.membershipType !== null;
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, ${storageConfig.membershipType !== null ? 'owner_id,' : ''} allowed_mime_types, max_file_size, allow_custom_keys
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.membershipType ?? 'app'})`);
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
- /** Membership type (NULL for app-level, non-NULL for entity-scoped) */
32
- membershipType: number | null;
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.19.1",
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.11.0",
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.17.1",
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": "030e1144acbd4e288ee74eff2ac0021ca0382ef7"
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.membershipType !== null;
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 membership_type IS NULL (app-level / database-wide).
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
@@ -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 (membership_type IS NULL) by default.
46
+ * Filters to app-level (scope = 'app') by default.
47
47
  *
48
- * Requires the multi-scope schema (membership_type column on storage_module).
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.membership_type,
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.membership_type IS NULL
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.membership_type,
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
- membershipType: row.membership_type,
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 membership_type IS NULL (app-level / database-wide).
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 membership type
216
+ // Cache each individual config by its scope
217
217
  for (const config of allConfigs) {
218
- const key = config.membershipType === null
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
- `(membershipType=${mod.membershipType}, table=${mod.bucketsQualifiedName})`);
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 membership type
285
+ // Cache each individual config by its scope
288
286
  for (const config of configs) {
289
- const key = config.membershipType === null
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 hasOwner = ownerId && storageConfig.membershipType !== null;
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, ${storageConfig.membershipType !== null ? 'owner_id,' : ''} allowed_mime_types, max_file_size, allow_custom_keys
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.membershipType ?? 'app'})`);
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
- /** Membership type (NULL for app-level, non-NULL for entity-scoped) */
32
- membershipType: number | null;
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) */