55 * For public files, returns the public URL prefix + key.
66 * For private files, generates a presigned GET URL.
77 *
8- * This plugin uses the GraphQLObjectType_fields hook to detect file tables
9- * (by checking for the storage module's files table) and add the computed field.
8+ * Detection: Uses the `@storageFiles` smart tag on the codec (table).
9+ * The storage module generator in constructive-db sets this tag on the
10+ * generated files table via a smart comment:
11+ * COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
12+ *
13+ * This is explicit and reliable — no duck-typing on column names.
1014 */
1115
1216import type { GraphileConfig } from 'graphile-config' ;
@@ -23,7 +27,8 @@ const log = new Logger('graphile-presigned-url:download-url');
2327 * This is a separate plugin from the main presigned URL plugin because it
2428 * uses the GraphQLObjectType_fields hook (low-level) rather than extendSchema.
2529 * The downloadUrl field needs to be added dynamically to whatever table is
26- * the storage module's files table, which we discover at schema-build time.
30+ * the storage module's files table, which we discover at schema-build time
31+ * via the `@storageFiles` smart tag.
2732 */
2833export function createDownloadUrlPlugin (
2934 options : PresignedUrlPluginOptions ,
@@ -34,7 +39,7 @@ export function createDownloadUrlPlugin(
3439 return {
3540 name : 'PresignedUrlDownloadPlugin' ,
3641 version : '0.1.0' ,
37- description : 'Adds downloadUrl computed field to File types' ,
42+ description : 'Adds downloadUrl computed field to File types tagged with @storageFiles ' ,
3843
3944 schema : {
4045 hooks : {
@@ -48,22 +53,13 @@ export function createDownloadUrlPlugin(
4853 return fields ;
4954 }
5055
51- const attrs = pgCodec . attributes as Record < string , any > ;
52-
53- // Detect if this is a files table by checking for characteristic columns:
54- // key, content_type, content_hash, status, bucket_id, is_public
55- const hasKey = 'key' in attrs ;
56- const hasContentType = 'content_type' in attrs ;
57- const hasContentHash = 'content_hash' in attrs ;
58- const hasStatus = 'status' in attrs ;
59- const hasBucketId = 'bucket_id' in attrs ;
60- const hasIsPublic = 'is_public' in attrs ;
61-
62- if ( ! hasKey || ! hasContentType || ! hasContentHash || ! hasStatus || ! hasBucketId || ! hasIsPublic ) {
56+ // Check for @storageFiles smart tag — set by the storage module generator
57+ const tags = ( pgCodec . extensions as any ) ?. tags ;
58+ if ( ! tags ?. storageFiles ) {
6359 return fields ;
6460 }
6561
66- log . debug ( `Adding downloadUrl field to type: ${ pgCodec . name } ` ) ;
62+ log . debug ( `Adding downloadUrl field to type: ${ pgCodec . name } (has @storageFiles tag) ` ) ;
6763
6864 const {
6965 graphql : { GraphQLString } ,
0 commit comments