graphile-presigned-url-plugin 0.7.0 → 0.9.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/preset.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * PostGraphile v5 Presigned URL Preset
3
3
  *
4
4
  * Provides a convenient preset for including presigned URL upload support
5
- * in PostGraphile. Combines the main mutation plugin (requestUploadUrl,
6
- * confirmUpload) with the downloadUrl computed field plugin.
5
+ * in PostGraphile. Combines the main mutation plugin (requestUploadUrl)
6
+ * with the downloadUrl computed field plugin.
7
7
  */
8
8
  import type { GraphileConfig } from 'graphile-config';
9
9
  import type { PresignedUrlPluginOptions } from './types';
package/esm/preset.js CHANGED
@@ -2,8 +2,8 @@
2
2
  * PostGraphile v5 Presigned URL Preset
3
3
  *
4
4
  * Provides a convenient preset for including presigned URL upload support
5
- * in PostGraphile. Combines the main mutation plugin (requestUploadUrl,
6
- * confirmUpload) with the downloadUrl computed field plugin.
5
+ * in PostGraphile. Combines the main mutation plugin (requestUploadUrl)
6
+ * with the downloadUrl computed field plugin.
7
7
  */
8
8
  import { createPresignedUrlPlugin } from './plugin';
9
9
  import { createDownloadUrlPlugin } from './download-url-field';
@@ -30,8 +30,7 @@ export declare function generatePresignedGetUrl(s3Config: S3Config, key: string,
30
30
  /**
31
31
  * Check if an object exists in S3 and optionally verify its content-type.
32
32
  *
33
- * Used by confirmUpload to verify the file was actually uploaded to S3
34
- * and that the content-type matches what was declared.
33
+ * Checks whether an object exists in S3 and retrieves its content-type.
35
34
  *
36
35
  * @param s3Config - S3 client and bucket configuration
37
36
  * @param key - S3 object key
package/esm/s3-signer.js CHANGED
@@ -56,8 +56,7 @@ export async function generatePresignedGetUrl(s3Config, key, expiresIn = 3600, f
56
56
  /**
57
57
  * Check if an object exists in S3 and optionally verify its content-type.
58
58
  *
59
- * Used by confirmUpload to verify the file was actually uploaded to S3
60
- * and that the content-type matches what was declared.
59
+ * Checks whether an object exists in S3 and retrieves its content-type.
61
60
  *
62
61
  * @param s3Config - S3 client and bucket configuration
63
62
  * @param key - S3 object key
@@ -43,7 +43,6 @@ export declare function getStorageModuleConfigForOwner(pgClient: {
43
43
  /**
44
44
  * Resolve the storage module that owns a specific file by probing all file tables.
45
45
  *
46
- * Used by confirmUpload when only a fileId (UUID) is available.
47
46
  * Since UUIDs are globally unique, exactly one table will contain the file.
48
47
  *
49
48
  * @param pgClient - A pg client from the Graphile context
@@ -64,7 +63,6 @@ export declare function resolveStorageModuleByFileId(pgClient: {
64
63
  id: string;
65
64
  key: string;
66
65
  mime_type: string;
67
- status: string;
68
66
  bucket_id: string;
69
67
  };
70
68
  } | null>;
@@ -8,14 +8,16 @@ const DEFAULT_DOWNLOAD_URL_EXPIRY_SECONDS = 3600; // 1 hour
8
8
  const DEFAULT_MAX_FILE_SIZE = 200 * 1024 * 1024; // 200MB
9
9
  const DEFAULT_MAX_FILENAME_LENGTH = 1024;
10
10
  const DEFAULT_CACHE_TTL_SECONDS = process.env.NODE_ENV === 'development' ? 300 : 3600;
11
+ const DEFAULT_MAX_BULK_FILES = 100;
12
+ const DEFAULT_MAX_BULK_TOTAL_SIZE = 1073741824; // 1GB
11
13
  const FIVE_MINUTES_MS = 1000 * 60 * 5;
12
14
  const ONE_HOUR_MS = 1000 * 60 * 60;
13
15
  /**
14
16
  * LRU cache for per-database StorageModuleConfig.
15
17
  *
16
18
  * Each PostGraphile instance serves a single database, but the presigned URL
17
- * plugin needs to know the generated table names (buckets, files,
18
- * upload_requests) and their schemas. This cache avoids re-querying metaschema
19
+ * plugin needs to know the generated table names (buckets, files)
20
+ * and their schemas. This cache avoids re-querying metaschema
19
21
  * on every request.
20
22
  *
21
23
  * Pattern: same as graphile-cache's LRU with TTL-based eviction.
@@ -42,8 +44,6 @@ const APP_STORAGE_MODULE_QUERY = `
42
44
  bt.name AS buckets_table,
43
45
  fs.schema_name AS files_schema,
44
46
  ft.name AS files_table,
45
- urs.schema_name AS upload_requests_schema,
46
- urt.name AS upload_requests_table,
47
47
  sm.endpoint,
48
48
  sm.public_url_prefix,
49
49
  sm.provider,
@@ -53,6 +53,9 @@ const APP_STORAGE_MODULE_QUERY = `
53
53
  sm.default_max_file_size,
54
54
  sm.max_filename_length,
55
55
  sm.cache_ttl_seconds,
56
+ sm.max_bulk_files,
57
+ sm.max_bulk_total_size,
58
+ sm.has_path_shares,
56
59
  NULL AS entity_schema,
57
60
  NULL AS entity_table
58
61
  FROM metaschema_modules_public.storage_module sm
@@ -60,8 +63,6 @@ const APP_STORAGE_MODULE_QUERY = `
60
63
  JOIN metaschema_public.schema bs ON bs.id = bt.schema_id
61
64
  JOIN metaschema_public.table ft ON ft.id = sm.files_table_id
62
65
  JOIN metaschema_public.schema fs ON fs.id = ft.schema_id
63
- JOIN metaschema_public.table urt ON urt.id = sm.upload_requests_table_id
64
- JOIN metaschema_public.schema urs ON urs.id = urt.schema_id
65
66
  WHERE sm.database_id = $1
66
67
  AND sm.membership_type IS NULL
67
68
  LIMIT 1
@@ -81,8 +82,6 @@ const ALL_STORAGE_MODULES_QUERY = `
81
82
  bt.name AS buckets_table,
82
83
  fs.schema_name AS files_schema,
83
84
  ft.name AS files_table,
84
- urs.schema_name AS upload_requests_schema,
85
- urt.name AS upload_requests_table,
86
85
  sm.endpoint,
87
86
  sm.public_url_prefix,
88
87
  sm.provider,
@@ -92,6 +91,9 @@ const ALL_STORAGE_MODULES_QUERY = `
92
91
  sm.default_max_file_size,
93
92
  sm.max_filename_length,
94
93
  sm.cache_ttl_seconds,
94
+ sm.max_bulk_files,
95
+ sm.max_bulk_total_size,
96
+ sm.has_path_shares,
95
97
  es.schema_name AS entity_schema,
96
98
  et.name AS entity_table
97
99
  FROM metaschema_modules_public.storage_module sm
@@ -99,8 +101,6 @@ const ALL_STORAGE_MODULES_QUERY = `
99
101
  JOIN metaschema_public.schema bs ON bs.id = bt.schema_id
100
102
  JOIN metaschema_public.table ft ON ft.id = sm.files_table_id
101
103
  JOIN metaschema_public.schema fs ON fs.id = ft.schema_id
102
- JOIN metaschema_public.table urt ON urt.id = sm.upload_requests_table_id
103
- JOIN metaschema_public.schema urs ON urs.id = urt.schema_id
104
104
  LEFT JOIN metaschema_public.table et ON et.id = sm.entity_table_id
105
105
  LEFT JOIN metaschema_public.schema es ON es.id = et.schema_id
106
106
  WHERE sm.database_id = $1
@@ -114,11 +114,9 @@ function buildConfig(row) {
114
114
  id: row.id,
115
115
  bucketsQualifiedName: QuoteUtils.quoteQualifiedIdentifier(row.buckets_schema, row.buckets_table),
116
116
  filesQualifiedName: QuoteUtils.quoteQualifiedIdentifier(row.files_schema, row.files_table),
117
- uploadRequestsQualifiedName: QuoteUtils.quoteQualifiedIdentifier(row.upload_requests_schema, row.upload_requests_table),
118
117
  schemaName: row.buckets_schema,
119
118
  bucketsTableName: row.buckets_table,
120
119
  filesTableName: row.files_table,
121
- uploadRequestsTableName: row.upload_requests_table,
122
120
  membershipType: row.membership_type,
123
121
  entityTableId: row.entity_table_id,
124
122
  entityQualifiedName: row.entity_schema && row.entity_table
@@ -133,6 +131,9 @@ function buildConfig(row) {
133
131
  defaultMaxFileSize: row.default_max_file_size ?? DEFAULT_MAX_FILE_SIZE,
134
132
  maxFilenameLength: row.max_filename_length ?? DEFAULT_MAX_FILENAME_LENGTH,
135
133
  cacheTtlSeconds,
134
+ hasPathShares: row.has_path_shares ?? false,
135
+ maxBulkFiles: row.max_bulk_files ?? DEFAULT_MAX_BULK_FILES,
136
+ maxBulkTotalSize: row.max_bulk_total_size ?? DEFAULT_MAX_BULK_TOTAL_SIZE,
136
137
  };
137
138
  }
138
139
  /**
@@ -229,7 +230,6 @@ export async function getStorageModuleConfigForOwner(pgClient, databaseId, owner
229
230
  /**
230
231
  * Resolve the storage module that owns a specific file by probing all file tables.
231
232
  *
232
- * Used by confirmUpload when only a fileId (UUID) is available.
233
233
  * Since UUIDs are globally unique, exactly one table will contain the file.
234
234
  *
235
235
  * @param pgClient - A pg client from the Graphile context
@@ -244,7 +244,7 @@ export async function resolveStorageModuleByFileId(pgClient, databaseId, fileId)
244
244
  // Probe each module's files table for the fileId
245
245
  for (const config of allConfigs) {
246
246
  const fileResult = await pgClient.query({
247
- text: `SELECT id, key, mime_type, status, bucket_id
247
+ text: `SELECT id, key, mime_type, bucket_id
248
248
  FROM ${config.filesQualifiedName}
249
249
  WHERE id = $1
250
250
  LIMIT 1`,
@@ -302,11 +302,11 @@ export async function getBucketConfig(pgClient, storageConfig, databaseId, bucke
302
302
  const hasOwner = ownerId && storageConfig.membershipType !== null;
303
303
  const result = await pgClient.query({
304
304
  text: hasOwner
305
- ? `SELECT id, key, type, is_public, owner_id, allowed_mime_types, max_file_size
305
+ ? `SELECT id, key, type, is_public, owner_id, allowed_mime_types, max_file_size, allow_custom_keys
306
306
  FROM ${storageConfig.bucketsQualifiedName}
307
307
  WHERE key = $1 AND owner_id = $2
308
308
  LIMIT 1`
309
- : `SELECT id, key, type, is_public, ${storageConfig.membershipType !== null ? 'owner_id,' : ''} allowed_mime_types, max_file_size
309
+ : `SELECT id, key, type, is_public, ${storageConfig.membershipType !== null ? 'owner_id,' : ''} allowed_mime_types, max_file_size, allow_custom_keys
310
310
  FROM ${storageConfig.bucketsQualifiedName}
311
311
  WHERE key = $1
312
312
  LIMIT 1`,
@@ -324,6 +324,7 @@ export async function getBucketConfig(pgClient, storageConfig, databaseId, bucke
324
324
  owner_id: row.owner_id ?? null,
325
325
  allowed_mime_types: row.allowed_mime_types,
326
326
  max_file_size: row.max_file_size,
327
+ allow_custom_keys: row.allow_custom_keys ?? false,
327
328
  };
328
329
  bucketCache.set(cacheKey, config);
329
330
  log.debug(`Cached bucket config for ${databaseId}:${bucketKey} (id=${config.id}, scope=${storageConfig.membershipType ?? 'app'})`);
package/esm/types.d.ts CHANGED
@@ -10,6 +10,7 @@ export interface BucketConfig {
10
10
  owner_id: string | null;
11
11
  allowed_mime_types: string[] | null;
12
12
  max_file_size: number | null;
13
+ allow_custom_keys: boolean;
13
14
  }
14
15
  /**
15
16
  * Storage module configuration resolved from metaschema for a given database.
@@ -21,16 +22,12 @@ export interface StorageModuleConfig {
21
22
  bucketsQualifiedName: string;
22
23
  /** Resolved schema.table for files */
23
24
  filesQualifiedName: string;
24
- /** Resolved schema.table for upload_requests */
25
- uploadRequestsQualifiedName: string;
26
25
  /** Schema name (e.g., "app_public") */
27
26
  schemaName: string;
28
27
  /** Buckets table name */
29
28
  bucketsTableName: string;
30
29
  /** Files table name */
31
30
  filesTableName: string;
32
- /** Upload requests table name */
33
- uploadRequestsTableName: string;
34
31
  /** Membership type (NULL for app-level, non-NULL for entity-scoped) */
35
32
  membershipType: number | null;
36
33
  /** Entity table ID for entity-scoped storage (NULL for app-level) */
@@ -55,6 +52,12 @@ export interface StorageModuleConfig {
55
52
  maxFilenameLength: number;
56
53
  /** Cache TTL in seconds for this config entry (default: 300 dev / 3600 prod) */
57
54
  cacheTtlSeconds: number;
55
+ /** Whether this storage module uses ltree path + path shares (determines if path column exists on files) */
56
+ hasPathShares: boolean;
57
+ /** Max files per requestBulkUploadUrls batch (default: 100) */
58
+ maxBulkFiles: number;
59
+ /** Max total size per bulk upload batch in bytes (default: 1GB) */
60
+ maxBulkTotalSize: number;
58
61
  }
59
62
  /**
60
63
  * Input for the requestUploadUrl mutation.
@@ -77,6 +80,13 @@ export interface RequestUploadUrlInput {
77
80
  size: number;
78
81
  /** Original filename (optional, for display/Content-Disposition) */
79
82
  filename?: string;
83
+ /**
84
+ * Custom S3 key for the file (only allowed when bucket has allow_custom_keys=true).
85
+ * When omitted, key defaults to contentHash (content-addressed dedup).
86
+ * When provided, the file is stored at this key; dedup is bypassed.
87
+ * Max 1024 chars. Must not contain path traversal (.. or leading /).
88
+ */
89
+ key?: string;
80
90
  }
81
91
  /**
82
92
  * Result of the requestUploadUrl mutation.
@@ -92,26 +102,8 @@ export interface RequestUploadUrlPayload {
92
102
  deduplicated: boolean;
93
103
  /** Presigned URL expiry time (null if deduplicated) */
94
104
  expiresAt: string | null;
95
- /** File status 'pending' for fresh uploads, 'ready' or 'processed' for deduplicated files */
96
- status: string;
97
- }
98
- /**
99
- * Input for the confirmUpload mutation.
100
- */
101
- export interface ConfirmUploadInput {
102
- /** The file ID returned by requestUploadUrl */
103
- fileId: string;
104
- }
105
- /**
106
- * Result of the confirmUpload mutation.
107
- */
108
- export interface ConfirmUploadPayload {
109
- /** The confirmed file ID */
110
- fileId: string;
111
- /** New file status (should be 'ready') */
112
- status: string;
113
- /** Whether confirmation succeeded */
114
- success: boolean;
105
+ /** ID of the previous version (set when re-uploading to an existing custom key) */
106
+ previousVersionId: string | null;
115
107
  }
116
108
  /**
117
109
  * S3 configuration for the presigned URL plugin.
package/index.d.ts CHANGED
@@ -2,8 +2,7 @@
2
2
  * Presigned URL Plugin for PostGraphile v5
3
3
  *
4
4
  * Provides presigned URL upload capabilities for PostGraphile v5:
5
- * - requestUploadUrl mutation (presigned PUT URL generation)
6
- * - confirmUpload mutation (upload verification + status transition)
5
+ * - requestUploadUrl mutation (presigned PUT URL generation + dedup)
7
6
  * - downloadUrl computed field (presigned GET URL / public URL)
8
7
  *
9
8
  * @example
@@ -31,4 +30,4 @@ export { createDownloadUrlPlugin } from './download-url-field';
31
30
  export { PresignedUrlPreset } from './preset';
32
31
  export { getStorageModuleConfig, getStorageModuleConfigForOwner, getBucketConfig, resolveStorageModuleByFileId, clearStorageModuleCache, clearBucketCache, isS3BucketProvisioned, markS3BucketProvisioned } from './storage-module-cache';
33
32
  export { generatePresignedPutUrl, generatePresignedGetUrl, headObject } from './s3-signer';
34
- export type { BucketConfig, StorageModuleConfig, RequestUploadUrlInput, RequestUploadUrlPayload, ConfirmUploadInput, ConfirmUploadPayload, S3Config, S3ConfigOrGetter, PresignedUrlPluginOptions, BucketNameResolver, EnsureBucketProvisioned, } from './types';
33
+ export type { BucketConfig, StorageModuleConfig, RequestUploadUrlInput, RequestUploadUrlPayload, S3Config, S3ConfigOrGetter, PresignedUrlPluginOptions, BucketNameResolver, EnsureBucketProvisioned, } from './types';
package/index.js CHANGED
@@ -3,8 +3,7 @@
3
3
  * Presigned URL Plugin for PostGraphile v5
4
4
  *
5
5
  * Provides presigned URL upload capabilities for PostGraphile v5:
6
- * - requestUploadUrl mutation (presigned PUT URL generation)
7
- * - confirmUpload mutation (upload verification + status transition)
6
+ * - requestUploadUrl mutation (presigned PUT URL generation + dedup)
8
7
  * - downloadUrl computed field (presigned GET URL / public URL)
9
8
  *
10
9
  * @example
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "graphile-presigned-url-plugin",
3
- "version": "0.7.0",
4
- "description": "Presigned URL upload plugin for PostGraphile v5 — requestUploadUrl, confirmUpload mutations and downloadUrl computed field",
3
+ "version": "0.9.0",
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",
7
7
  "license": "MIT",
@@ -60,5 +60,5 @@
60
60
  "@types/node": "^22.19.11",
61
61
  "makage": "^0.1.10"
62
62
  },
63
- "gitHead": "058b8200e99eb505477d1599ee0d5ab795aa0123"
63
+ "gitHead": "caf38236170287e2ad06d5c47482ce246fb3233e"
64
64
  }
package/plugin.d.ts CHANGED
@@ -5,13 +5,9 @@
5
5
  *
6
6
  * 1. `requestUploadUrl` mutation — generates a presigned PUT URL for direct
7
7
  * client-to-S3 upload. Checks bucket access via RLS, deduplicates by
8
- * content hash, tracks the request in upload_requests.
8
+ * content hash via UNIQUE(bucket_id, key) constraint.
9
9
  *
10
- * 2. `confirmUpload` mutation confirms a file was uploaded to S3, verifies
11
- * the object exists with correct content-type, transitions file status
12
- * from 'pending' to 'ready'.
13
- *
14
- * 3. `downloadUrl` computed field on File types — generates presigned GET URLs
10
+ * 2. `downloadUrl` computed field on File types generates presigned GET URLs
15
11
  * for private files, returns public URL prefix + key for public files.
16
12
  *
17
13
  * Uses the extendSchema + grafast plan pattern (same as PublicKeySignature).