graphile-presigned-url-plugin 0.10.0 → 0.12.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.
@@ -19,6 +19,7 @@
19
19
  * the plan() function is required for Grafast to execute the S3 signing.
20
20
  */
21
21
  import type { GraphileConfig } from 'graphile-config';
22
+ import 'graphile-build';
22
23
  import type { PresignedUrlPluginOptions } from './types';
23
24
  export declare function createDownloadUrlPlugin(options: PresignedUrlPluginOptions): GraphileConfig.Plugin;
24
25
  export default createDownloadUrlPlugin;
@@ -21,6 +21,7 @@
21
21
  */
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.createDownloadUrlPlugin = createDownloadUrlPlugin;
24
+ require("graphile-build");
24
25
  const grafast_1 = require("grafast");
25
26
  const logger_1 = require("@pgpmjs/logger");
26
27
  const s3_signer_1 = require("./s3-signer");
@@ -86,17 +87,16 @@ function createDownloadUrlPlugin(options) {
86
87
  }
87
88
  log.debug(`Adding downloadUrl field to type: ${pgCodec.name} (has @storageFiles tag)`);
88
89
  const { graphql: { GraphQLString }, } = build;
90
+ const capturedCodec = pgCodec;
89
91
  return build.extend(fields, {
90
92
  downloadUrl: context.fieldWithHooks({ fieldName: 'downloadUrl' }, {
91
93
  description: 'URL to download this file. For public files, returns the public URL. ' +
92
94
  'For private files, returns a time-limited presigned URL.',
93
95
  type: GraphQLString,
94
96
  plan($parent) {
95
- // Access file attributes from the parent PgSelectSingleStep
96
97
  const $key = $parent.get('key');
97
98
  const $isPublic = $parent.get('is_public');
98
99
  const $filename = $parent.get('filename');
99
- // Access GraphQL context for per-database config resolution
100
100
  const $withPgClient = (0, grafast_1.context)().get('withPgClient');
101
101
  const $pgSettings = (0, grafast_1.context)().get('pgSettings');
102
102
  const $combined = (0, grafast_1.object)({
@@ -109,9 +109,8 @@ function createDownloadUrlPlugin(options) {
109
109
  return (0, grafast_1.lambda)($combined, async ({ key, isPublic, filename, withPgClient, pgSettings }) => {
110
110
  if (!key)
111
111
  return null;
112
- // Resolve per-database config (bucket, publicUrlPrefix, expiry)
113
- let s3ForDb = resolveS3(options); // fallback to global
114
- let downloadUrlExpirySeconds = 3600; // fallback default
112
+ let s3ForDb = resolveS3(options);
113
+ let downloadUrlExpirySeconds = 3600;
115
114
  try {
116
115
  if (withPgClient && pgSettings) {
117
116
  const resolved = await withPgClient(null, async (pgClient) => {
@@ -121,7 +120,8 @@ function createDownloadUrlPlugin(options) {
121
120
  const databaseId = dbResult.rows[0]?.id;
122
121
  if (!databaseId)
123
122
  return null;
124
- const config = await (0, storage_module_cache_1.getStorageModuleConfig)(pgClient, databaseId);
123
+ const allConfigs = await (0, storage_module_cache_1.loadAllStorageModules)(pgClient, databaseId);
124
+ const config = (0, storage_module_cache_1.resolveStorageConfigFromCodec)(capturedCodec, allConfigs);
125
125
  if (!config)
126
126
  return null;
127
127
  return { config, databaseId };
@@ -136,10 +136,8 @@ function createDownloadUrlPlugin(options) {
136
136
  // Fall back to global config if lookup fails
137
137
  }
138
138
  if (isPublic && s3ForDb.publicUrlPrefix) {
139
- // Public file: return direct CDN URL (per-database prefix)
140
139
  return `${s3ForDb.publicUrlPrefix}/${key}`;
141
140
  }
142
- // Private file: generate presigned GET URL (per-database bucket)
143
141
  return (0, s3_signer_1.generatePresignedGetUrl)(s3ForDb, key, downloadUrlExpirySeconds, filename || undefined);
144
142
  });
145
143
  },
@@ -19,6 +19,7 @@
19
19
  * the plan() function is required for Grafast to execute the S3 signing.
20
20
  */
21
21
  import type { GraphileConfig } from 'graphile-config';
22
+ import 'graphile-build';
22
23
  import type { PresignedUrlPluginOptions } from './types';
23
24
  export declare function createDownloadUrlPlugin(options: PresignedUrlPluginOptions): GraphileConfig.Plugin;
24
25
  export default createDownloadUrlPlugin;
@@ -18,10 +18,11 @@
18
18
  * lookups. Since downloadUrl is a computed field (not a real column),
19
19
  * the plan() function is required for Grafast to execute the S3 signing.
20
20
  */
21
+ import 'graphile-build';
21
22
  import { context as grafastContext, lambda, object } from 'grafast';
22
23
  import { Logger } from '@pgpmjs/logger';
23
24
  import { generatePresignedGetUrl } from './s3-signer';
24
- import { getStorageModuleConfig } from './storage-module-cache';
25
+ import { loadAllStorageModules, resolveStorageConfigFromCodec } from './storage-module-cache';
25
26
  const log = new Logger('graphile-presigned-url:download-url');
26
27
  /**
27
28
  * Creates the downloadUrl computed field plugin.
@@ -83,17 +84,16 @@ export function createDownloadUrlPlugin(options) {
83
84
  }
84
85
  log.debug(`Adding downloadUrl field to type: ${pgCodec.name} (has @storageFiles tag)`);
85
86
  const { graphql: { GraphQLString }, } = build;
87
+ const capturedCodec = pgCodec;
86
88
  return build.extend(fields, {
87
89
  downloadUrl: context.fieldWithHooks({ fieldName: 'downloadUrl' }, {
88
90
  description: 'URL to download this file. For public files, returns the public URL. ' +
89
91
  'For private files, returns a time-limited presigned URL.',
90
92
  type: GraphQLString,
91
93
  plan($parent) {
92
- // Access file attributes from the parent PgSelectSingleStep
93
94
  const $key = $parent.get('key');
94
95
  const $isPublic = $parent.get('is_public');
95
96
  const $filename = $parent.get('filename');
96
- // Access GraphQL context for per-database config resolution
97
97
  const $withPgClient = grafastContext().get('withPgClient');
98
98
  const $pgSettings = grafastContext().get('pgSettings');
99
99
  const $combined = object({
@@ -106,9 +106,8 @@ export function createDownloadUrlPlugin(options) {
106
106
  return lambda($combined, async ({ key, isPublic, filename, withPgClient, pgSettings }) => {
107
107
  if (!key)
108
108
  return null;
109
- // Resolve per-database config (bucket, publicUrlPrefix, expiry)
110
- let s3ForDb = resolveS3(options); // fallback to global
111
- let downloadUrlExpirySeconds = 3600; // fallback default
109
+ let s3ForDb = resolveS3(options);
110
+ let downloadUrlExpirySeconds = 3600;
112
111
  try {
113
112
  if (withPgClient && pgSettings) {
114
113
  const resolved = await withPgClient(null, async (pgClient) => {
@@ -118,7 +117,8 @@ export function createDownloadUrlPlugin(options) {
118
117
  const databaseId = dbResult.rows[0]?.id;
119
118
  if (!databaseId)
120
119
  return null;
121
- const config = await getStorageModuleConfig(pgClient, databaseId);
120
+ const allConfigs = await loadAllStorageModules(pgClient, databaseId);
121
+ const config = resolveStorageConfigFromCodec(capturedCodec, allConfigs);
122
122
  if (!config)
123
123
  return null;
124
124
  return { config, databaseId };
@@ -133,10 +133,8 @@ export function createDownloadUrlPlugin(options) {
133
133
  // Fall back to global config if lookup fails
134
134
  }
135
135
  if (isPublic && s3ForDb.publicUrlPrefix) {
136
- // Public file: return direct CDN URL (per-database prefix)
137
136
  return `${s3ForDb.publicUrlPrefix}/${key}`;
138
137
  }
139
- // Private file: generate presigned GET URL (per-database bucket)
140
138
  return generatePresignedGetUrl(s3ForDb, key, downloadUrlExpirySeconds, filename || undefined);
141
139
  });
142
140
  },
package/esm/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Presigned URL Plugin for PostGraphile v5
3
3
  *
4
- * Provides presigned URL upload capabilities for PostGraphile v5:
5
- * - requestUploadUrl mutation (presigned PUT URL generation + dedup)
6
- * - downloadUrl computed field (presigned GET URL / public URL)
4
+ * Provides per-table S3 storage middleware for PostGraphile v5:
5
+ * - Upload fields on @storageBuckets types (requestUploadUrl, requestBulkUploadUrls)
6
+ * - Delete middleware on @storageFiles tables (S3 cleanup on delete)
7
+ * - downloadUrl computed field on @storageFiles types
7
8
  *
8
9
  * @example
9
10
  * ```typescript
@@ -28,6 +29,6 @@
28
29
  export { PresignedUrlPlugin, createPresignedUrlPlugin } from './plugin';
29
30
  export { createDownloadUrlPlugin } from './download-url-field';
30
31
  export { PresignedUrlPreset } from './preset';
31
- export { getStorageModuleConfig, getStorageModuleConfigForOwner, getBucketConfig, resolveStorageModuleByFileId, clearStorageModuleCache, clearBucketCache, isS3BucketProvisioned, markS3BucketProvisioned } from './storage-module-cache';
32
- export { generatePresignedPutUrl, generatePresignedGetUrl, headObject } from './s3-signer';
32
+ export { getStorageModuleConfig, getStorageModuleConfigForOwner, getBucketConfig, resolveStorageModuleByFileId, loadAllStorageModules, resolveStorageConfigFromCodec, clearStorageModuleCache, clearBucketCache, isS3BucketProvisioned, markS3BucketProvisioned } from './storage-module-cache';
33
+ export { generatePresignedPutUrl, generatePresignedGetUrl, deleteS3Object, headObject } from './s3-signer';
33
34
  export type { BucketConfig, StorageModuleConfig, RequestUploadUrlInput, RequestUploadUrlPayload, S3Config, S3ConfigOrGetter, PresignedUrlPluginOptions, BucketNameResolver, EnsureBucketProvisioned, } from './types';
package/esm/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Presigned URL Plugin for PostGraphile v5
3
3
  *
4
- * Provides presigned URL upload capabilities for PostGraphile v5:
5
- * - requestUploadUrl mutation (presigned PUT URL generation + dedup)
6
- * - downloadUrl computed field (presigned GET URL / public URL)
4
+ * Provides per-table S3 storage middleware for PostGraphile v5:
5
+ * - Upload fields on @storageBuckets types (requestUploadUrl, requestBulkUploadUrls)
6
+ * - Delete middleware on @storageFiles tables (S3 cleanup on delete)
7
+ * - downloadUrl computed field on @storageFiles types
7
8
  *
8
9
  * @example
9
10
  * ```typescript
@@ -28,5 +29,5 @@
28
29
  export { PresignedUrlPlugin, createPresignedUrlPlugin } from './plugin';
29
30
  export { createDownloadUrlPlugin } from './download-url-field';
30
31
  export { PresignedUrlPreset } from './preset';
31
- export { getStorageModuleConfig, getStorageModuleConfigForOwner, getBucketConfig, resolveStorageModuleByFileId, clearStorageModuleCache, clearBucketCache, isS3BucketProvisioned, markS3BucketProvisioned } from './storage-module-cache';
32
- export { generatePresignedPutUrl, generatePresignedGetUrl, headObject } from './s3-signer';
32
+ export { getStorageModuleConfig, getStorageModuleConfigForOwner, getBucketConfig, resolveStorageModuleByFileId, loadAllStorageModules, resolveStorageConfigFromCodec, clearStorageModuleCache, clearBucketCache, isS3BucketProvisioned, markS3BucketProvisioned } from './storage-module-cache';
33
+ export { generatePresignedPutUrl, generatePresignedGetUrl, deleteS3Object, headObject } from './s3-signer';
package/esm/plugin.d.ts CHANGED
@@ -1,18 +1,25 @@
1
1
  /**
2
- * Presigned URL Plugin for PostGraphile v5
2
+ * Per-Table Storage Middleware Plugin for PostGraphile v5
3
3
  *
4
- * Adds presigned URL upload support to PostGraphile v5:
4
+ * Hooks into PostGraphile's auto-generated CRUD mutations to add S3 operations:
5
5
  *
6
- * 1. `requestUploadUrl` mutationgenerates a presigned PUT URL for direct
7
- * client-to-S3 upload. Checks bucket access via RLS, deduplicates by
8
- * content hash via UNIQUE(bucket_id, key) constraint.
6
+ * 1. Delete middlewarewraps `delete*` mutations on `@storageFiles`-tagged tables
7
+ * with S3 object cleanup (sync + async GC fallback via AFTER DELETE trigger).
9
8
  *
10
- * 2. `downloadUrl` computed field on File types generates presigned GET URLs
11
- * for private files, returns public URL prefix + key for public files.
9
+ * 2. Upload fields adds `requestUploadUrl` and `requestBulkUploadUrls` fields
10
+ * on `@storageBuckets`-tagged types, so clients upload via the typed bucket API.
12
11
  *
13
- * Uses the extendSchema + grafast plan pattern (same as PublicKeySignature).
12
+ * 3. Mutation entry points adds per-bucket mutation fields on the root Mutation
13
+ * type (e.g., `appBucket(key: "public"): AppBucket`), so upload operations
14
+ * can be accessed as proper GraphQL mutations instead of queries.
15
+ *
16
+ * 4. downloadUrl — handled by download-url-field.ts (separate plugin).
17
+ *
18
+ * Scope resolution uses the codec's schema/table name matched against
19
+ * cached storage module configs.
14
20
  */
15
21
  import type { GraphileConfig } from 'graphile-config';
22
+ import 'graphile-build';
16
23
  import type { PresignedUrlPluginOptions } from './types';
17
24
  export declare function createPresignedUrlPlugin(options: PresignedUrlPluginOptions): GraphileConfig.Plugin;
18
25
  export declare const PresignedUrlPlugin: typeof createPresignedUrlPlugin;