graphile-presigned-url-plugin 0.11.0 → 0.12.1

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.d.ts CHANGED
@@ -9,11 +9,14 @@
9
9
  * 2. Upload fields — adds `requestUploadUrl` and `requestBulkUploadUrls` fields
10
10
  * on `@storageBuckets`-tagged types, so clients upload via the typed bucket API.
11
11
  *
12
- * 3. downloadUrlhandled by download-url-field.ts (separate plugin).
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.
13
15
  *
14
- * No global mutations all S3 operations are scoped to the per-table types that
15
- * PostGraphile already generates. Scope resolution uses the codec's schema/table
16
- * name matched against cached storage module configs.
16
+ * 4. downloadUrlhandled 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.
17
20
  */
18
21
  import type { GraphileConfig } from 'graphile-config';
19
22
  import 'graphile-build';
package/esm/plugin.js CHANGED
@@ -9,11 +9,14 @@
9
9
  * 2. Upload fields — adds `requestUploadUrl` and `requestBulkUploadUrls` fields
10
10
  * on `@storageBuckets`-tagged types, so clients upload via the typed bucket API.
11
11
  *
12
- * 3. downloadUrlhandled by download-url-field.ts (separate plugin).
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.
13
15
  *
14
- * No global mutations all S3 operations are scoped to the per-table types that
15
- * PostGraphile already generates. Scope resolution uses the codec's schema/table
16
- * name matched against cached storage module configs.
16
+ * 4. downloadUrlhandled 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.
17
20
  */
18
21
  import { context as grafastContext, lambda, object } from 'grafast';
19
22
  import 'graphile-build';
@@ -111,7 +114,53 @@ export function createPresignedUrlPlugin(options) {
111
114
  * Add requestUploadUrl and requestBulkUploadUrls fields on @storageBuckets types.
112
115
  */
113
116
  GraphQLObjectType_fields(fields, build, context) {
114
- const { scope: { pgCodec, isPgClassType }, } = context;
117
+ const { scope: { pgCodec, isPgClassType, isRootMutation }, } = context;
118
+ // --- Path 1: Add per-bucket mutation entry points on root Mutation ---
119
+ if (isRootMutation) {
120
+ const { graphql: { GraphQLString, GraphQLNonNull }, } = build;
121
+ const bucketCodecs = Object.values(build.input.pgRegistry.pgCodecs).filter((codec) => codec.attributes && codec.extensions?.tags?.storageBuckets);
122
+ if (bucketCodecs.length === 0)
123
+ return fields;
124
+ const newFields = {};
125
+ for (const codec of bucketCodecs) {
126
+ const typeName = build.inflection.tableType(codec);
127
+ const bucketType = build.getTypeByName(typeName);
128
+ if (!bucketType) {
129
+ log.debug(`Skipping mutation entry point for ${codec.name}: type ${typeName} not found`);
130
+ continue;
131
+ }
132
+ const fieldName = typeName.charAt(0).toLowerCase() + typeName.slice(1);
133
+ const hasOwnerId = !!codec.attributes.owner_id;
134
+ // Find the PgResource for this codec so we can return a proper PgSelectSingleStep
135
+ const bucketResource = Object.values(build.input.pgRegistry.pgResources).find((r) => r.codec === codec && !r.isUnique && !r.isVirtual && !r.parameters);
136
+ if (!bucketResource) {
137
+ log.debug(`Skipping mutation entry point for ${codec.name}: no PgResource found`);
138
+ continue;
139
+ }
140
+ log.debug(`Adding mutation entry point "${fieldName}" for bucket type ${typeName} (entity-scoped=${hasOwnerId})`);
141
+ newFields[fieldName] = context.fieldWithHooks({ fieldName }, {
142
+ description: `Look up a ${typeName} by key for mutation operations (upload, etc.).`,
143
+ type: bucketType,
144
+ args: {
145
+ key: { type: new GraphQLNonNull(GraphQLString), description: 'Bucket key (e.g., "public", "private")' },
146
+ ...(hasOwnerId
147
+ ? { ownerId: { type: new GraphQLNonNull(GraphQLString), description: 'Owner entity ID (required for entity-scoped buckets)' } }
148
+ : {}),
149
+ },
150
+ plan(_$mutation, fieldArgs) {
151
+ const spec = {
152
+ key: fieldArgs.getRaw('key'),
153
+ };
154
+ if (hasOwnerId) {
155
+ spec.owner_id = fieldArgs.getRaw('ownerId');
156
+ }
157
+ return bucketResource.find(spec).single();
158
+ },
159
+ });
160
+ }
161
+ return build.extend(fields, newFields, 'PresignedUrlPlugin adding per-bucket mutation entry points');
162
+ }
163
+ // --- Path 2: Add upload fields on @storageBuckets types ---
115
164
  if (!isPgClassType || !pgCodec || !pgCodec.attributes) {
116
165
  return fields;
117
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphile-presigned-url-plugin",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
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",
@@ -60,5 +60,5 @@
60
60
  "@types/node": "^22.19.11",
61
61
  "makage": "^0.1.10"
62
62
  },
63
- "gitHead": "7409479a981ff63a0937b5406a1c206a07b264ad"
63
+ "gitHead": "3491da5ae42fc87daf9b7c39c8735fcae51218a1"
64
64
  }
package/plugin.d.ts CHANGED
@@ -9,11 +9,14 @@
9
9
  * 2. Upload fields — adds `requestUploadUrl` and `requestBulkUploadUrls` fields
10
10
  * on `@storageBuckets`-tagged types, so clients upload via the typed bucket API.
11
11
  *
12
- * 3. downloadUrlhandled by download-url-field.ts (separate plugin).
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.
13
15
  *
14
- * No global mutations all S3 operations are scoped to the per-table types that
15
- * PostGraphile already generates. Scope resolution uses the codec's schema/table
16
- * name matched against cached storage module configs.
16
+ * 4. downloadUrlhandled 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.
17
20
  */
18
21
  import type { GraphileConfig } from 'graphile-config';
19
22
  import 'graphile-build';
package/plugin.js CHANGED
@@ -10,11 +10,14 @@
10
10
  * 2. Upload fields — adds `requestUploadUrl` and `requestBulkUploadUrls` fields
11
11
  * on `@storageBuckets`-tagged types, so clients upload via the typed bucket API.
12
12
  *
13
- * 3. downloadUrlhandled by download-url-field.ts (separate plugin).
13
+ * 3. Mutation entry points adds per-bucket mutation fields on the root Mutation
14
+ * type (e.g., `appBucket(key: "public"): AppBucket`), so upload operations
15
+ * can be accessed as proper GraphQL mutations instead of queries.
14
16
  *
15
- * No global mutations all S3 operations are scoped to the per-table types that
16
- * PostGraphile already generates. Scope resolution uses the codec's schema/table
17
- * name matched against cached storage module configs.
17
+ * 4. downloadUrlhandled by download-url-field.ts (separate plugin).
18
+ *
19
+ * Scope resolution uses the codec's schema/table name matched against
20
+ * cached storage module configs.
18
21
  */
19
22
  Object.defineProperty(exports, "__esModule", { value: true });
20
23
  exports.PresignedUrlPlugin = void 0;
@@ -115,7 +118,53 @@ function createPresignedUrlPlugin(options) {
115
118
  * Add requestUploadUrl and requestBulkUploadUrls fields on @storageBuckets types.
116
119
  */
117
120
  GraphQLObjectType_fields(fields, build, context) {
118
- const { scope: { pgCodec, isPgClassType }, } = context;
121
+ const { scope: { pgCodec, isPgClassType, isRootMutation }, } = context;
122
+ // --- Path 1: Add per-bucket mutation entry points on root Mutation ---
123
+ if (isRootMutation) {
124
+ const { graphql: { GraphQLString, GraphQLNonNull }, } = build;
125
+ const bucketCodecs = Object.values(build.input.pgRegistry.pgCodecs).filter((codec) => codec.attributes && codec.extensions?.tags?.storageBuckets);
126
+ if (bucketCodecs.length === 0)
127
+ return fields;
128
+ const newFields = {};
129
+ for (const codec of bucketCodecs) {
130
+ const typeName = build.inflection.tableType(codec);
131
+ const bucketType = build.getTypeByName(typeName);
132
+ if (!bucketType) {
133
+ log.debug(`Skipping mutation entry point for ${codec.name}: type ${typeName} not found`);
134
+ continue;
135
+ }
136
+ const fieldName = typeName.charAt(0).toLowerCase() + typeName.slice(1);
137
+ const hasOwnerId = !!codec.attributes.owner_id;
138
+ // Find the PgResource for this codec so we can return a proper PgSelectSingleStep
139
+ const bucketResource = Object.values(build.input.pgRegistry.pgResources).find((r) => r.codec === codec && !r.isUnique && !r.isVirtual && !r.parameters);
140
+ if (!bucketResource) {
141
+ log.debug(`Skipping mutation entry point for ${codec.name}: no PgResource found`);
142
+ continue;
143
+ }
144
+ log.debug(`Adding mutation entry point "${fieldName}" for bucket type ${typeName} (entity-scoped=${hasOwnerId})`);
145
+ newFields[fieldName] = context.fieldWithHooks({ fieldName }, {
146
+ description: `Look up a ${typeName} by key for mutation operations (upload, etc.).`,
147
+ type: bucketType,
148
+ args: {
149
+ key: { type: new GraphQLNonNull(GraphQLString), description: 'Bucket key (e.g., "public", "private")' },
150
+ ...(hasOwnerId
151
+ ? { ownerId: { type: new GraphQLNonNull(GraphQLString), description: 'Owner entity ID (required for entity-scoped buckets)' } }
152
+ : {}),
153
+ },
154
+ plan(_$mutation, fieldArgs) {
155
+ const spec = {
156
+ key: fieldArgs.getRaw('key'),
157
+ };
158
+ if (hasOwnerId) {
159
+ spec.owner_id = fieldArgs.getRaw('ownerId');
160
+ }
161
+ return bucketResource.find(spec).single();
162
+ },
163
+ });
164
+ }
165
+ return build.extend(fields, newFields, 'PresignedUrlPlugin adding per-bucket mutation entry points');
166
+ }
167
+ // --- Path 2: Add upload fields on @storageBuckets types ---
119
168
  if (!isPgClassType || !pgCodec || !pgCodec.attributes) {
120
169
  return fields;
121
170
  }