graphile-bucket-provisioner-plugin 1.18.0 → 1.18.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.js +8 -91
- package/package.json +2 -2
- package/plugin.js +8 -91
package/esm/plugin.js
CHANGED
|
@@ -21,24 +21,7 @@ import { extendSchema, gql } from 'graphile-utils';
|
|
|
21
21
|
* resolution in the explicit provisionBucket mutation).
|
|
22
22
|
*/
|
|
23
23
|
const ALL_STORAGE_MODULES_QUERY = `
|
|
24
|
-
SELECT
|
|
25
|
-
sm.id,
|
|
26
|
-
sm.database_id,
|
|
27
|
-
sm.buckets_table_id,
|
|
28
|
-
sm.scope,
|
|
29
|
-
sm.entity_field,
|
|
30
|
-
sm.entity_table_id,
|
|
31
|
-
bs.schema_name AS buckets_schema,
|
|
32
|
-
bt.name AS buckets_table,
|
|
33
|
-
sm.allowed_origins,
|
|
34
|
-
es.schema_name AS entity_schema,
|
|
35
|
-
et.name AS entity_table
|
|
36
|
-
FROM metaschema_modules_public.storage_module sm
|
|
37
|
-
JOIN metaschema_public.table bt ON bt.id = sm.buckets_table_id
|
|
38
|
-
JOIN metaschema_public.schema bs ON bs.id = bt.schema_id
|
|
39
|
-
LEFT JOIN metaschema_public.table et ON et.id = sm.entity_table_id
|
|
40
|
-
LEFT JOIN metaschema_public.schema es ON es.id = et.schema_id
|
|
41
|
-
WHERE sm.database_id = $1
|
|
24
|
+
SELECT * FROM function_resolution.storage_modules()
|
|
42
25
|
`;
|
|
43
26
|
/**
|
|
44
27
|
* Run a query against a grafast `withPgClient` client.
|
|
@@ -73,7 +56,7 @@ function scopeKeyColumn(storageModule) {
|
|
|
73
56
|
* silently picking.
|
|
74
57
|
*/
|
|
75
58
|
async function resolveBucketByKey(pgClient, databaseId, bucketKey, ownerId) {
|
|
76
|
-
const result = await runQuery(pgClient, ALL_STORAGE_MODULES_QUERY
|
|
59
|
+
const result = await runQuery(pgClient, ALL_STORAGE_MODULES_QUERY);
|
|
77
60
|
const modules = result.rows;
|
|
78
61
|
if (modules.length === 0) {
|
|
79
62
|
throw new Error(`STORAGE_MODULE_NOT_PROVISIONED: database ${databaseId} has no storage modules`);
|
|
@@ -124,81 +107,15 @@ async function resolveDatabaseId(pgClient) {
|
|
|
124
107
|
const result = await runQuery(pgClient, `SELECT jwt_private.current_database_id() AS id`);
|
|
125
108
|
return result.rows[0]?.id ?? null;
|
|
126
109
|
}
|
|
127
|
-
async function resolveEntityContext(pgClient, storageModule) {
|
|
128
|
-
const result = await runQuery(pgClient, `SELECT r.entity_type, r.get_org_fn_schema, r.get_org_fn
|
|
129
|
-
FROM metaschema.resolve_entity_context_by_field($1::uuid, $2::uuid, $3::text) r`, [storageModule.database_id, storageModule.buckets_table_id, storageModule.entity_field]);
|
|
130
|
-
return result.rows[0] ?? {
|
|
131
|
-
entity_type: null,
|
|
132
|
-
get_org_fn_schema: null,
|
|
133
|
-
get_org_fn: null,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
110
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* A callable enqueue function beside the trigger would be the durable fix for
|
|
142
|
-
* this deliberate mirroring, but is out of scope here.
|
|
143
|
-
*
|
|
144
|
-
* The three scope shapes differ only in the payload entries and the identity
|
|
145
|
-
* arguments, so those are the only parts a caller supplies — the identifier,
|
|
146
|
-
* queue, retry, and priority policy exist exactly once.
|
|
111
|
+
* Enqueue reconciliation through the storage module's generated
|
|
112
|
+
* `<private>.<buckets>_provision(bucket_id)`. It runs as the request role: it
|
|
113
|
+
* locks the bucket row FOR UPDATE under the buckets table's own RLS, then
|
|
114
|
+
* enqueues exactly the job the buckets INSERT trigger does.
|
|
147
115
|
*/
|
|
148
|
-
function addJobStatement(payloadEntries, identityArgs) {
|
|
149
|
-
return `SELECT (app_jobs.add_job(
|
|
150
|
-
identifier => 'storage:provision_bucket',
|
|
151
|
-
payload => json_build_object(${payloadEntries}),
|
|
152
|
-
queue_name => 'bucket:' || $1::text,
|
|
153
|
-
max_attempts => 25,
|
|
154
|
-
priority => 0${identityArgs}
|
|
155
|
-
)).id AS id`;
|
|
156
|
-
}
|
|
157
116
|
async function enqueueReconciliationJob(pgClient, storageModule, bucket) {
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
let text;
|
|
161
|
-
let values;
|
|
162
|
-
if (entityField === null) {
|
|
163
|
-
text = addJobStatement(`'id', $1::uuid,
|
|
164
|
-
'scope', $2::text`, '');
|
|
165
|
-
values = [bucket.id, scope];
|
|
166
|
-
}
|
|
167
|
-
else if (entityField === 'database_id') {
|
|
168
|
-
const databaseId = bucket.scope_key;
|
|
169
|
-
if (!databaseId) {
|
|
170
|
-
throw new Error(`STORAGE_BUCKET_SCOPE_KEY_MISSING: bucket ${bucket.id} has no database_id`);
|
|
171
|
-
}
|
|
172
|
-
text = addJobStatement(`'database_id', $2::uuid,
|
|
173
|
-
'id', $1::uuid,
|
|
174
|
-
'scope', $3::text`, `,
|
|
175
|
-
db_id => $2,
|
|
176
|
-
entity_id => $2,
|
|
177
|
-
organization_id => NULL,
|
|
178
|
-
entity_type => $3`);
|
|
179
|
-
values = [bucket.id, databaseId, scope];
|
|
180
|
-
}
|
|
181
|
-
else if (entityField === 'owner_id') {
|
|
182
|
-
const ownerId = bucket.scope_key;
|
|
183
|
-
if (!ownerId) {
|
|
184
|
-
throw new Error(`STORAGE_BUCKET_SCOPE_KEY_MISSING: bucket ${bucket.id} has no owner_id`);
|
|
185
|
-
}
|
|
186
|
-
const context = await resolveEntityContext(pgClient, storageModule);
|
|
187
|
-
const orgFunction = context.get_org_fn_schema && context.get_org_fn
|
|
188
|
-
? `${QuoteUtils.quoteQualifiedIdentifier(context.get_org_fn_schema, context.get_org_fn)}($3::text, $2::uuid)`
|
|
189
|
-
: 'NULL';
|
|
190
|
-
text = addJobStatement(`'id', $1::uuid,
|
|
191
|
-
'owner_id', $2::uuid,
|
|
192
|
-
'scope', $3::text`, `,
|
|
193
|
-
entity_id => $2,
|
|
194
|
-
organization_id => ${orgFunction},
|
|
195
|
-
entity_type => $3`);
|
|
196
|
-
values = [bucket.id, ownerId, scope];
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
throw new Error(`STORAGE_BUCKET_ENTITY_FIELD_UNSUPPORTED: ${entityField}`);
|
|
200
|
-
}
|
|
201
|
-
const result = await runQuery(pgClient, text, values);
|
|
117
|
+
const provisionFunction = QuoteUtils.quoteQualifiedIdentifier(storageModule.private_schema, `${storageModule.buckets_table}_provision`);
|
|
118
|
+
const result = await runQuery(pgClient, `SELECT ${provisionFunction}($1::uuid) AS id`, [bucket.id]);
|
|
202
119
|
const jobId = result.rows[0]?.id;
|
|
203
120
|
if (!jobId) {
|
|
204
121
|
throw new Error(`STORAGE_BUCKET_JOB_ID_MISSING: bucket ${bucket.id}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-bucket-provisioner-plugin",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.1",
|
|
4
4
|
"description": "Bucket provisioning plugin for PostGraphile v5 — explicitly provisions S3 buckets via a GraphQL mutation",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"homepage": "https://github.com/constructive-io/constructive",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"@types/node": "^22.19.11",
|
|
59
59
|
"makage": "^0.8.0"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "0b4ffa11a6d7958ba1068ce8e19616d4b018c0d6"
|
|
62
62
|
}
|
package/plugin.js
CHANGED
|
@@ -25,24 +25,7 @@ const graphile_utils_1 = require("graphile-utils");
|
|
|
25
25
|
* resolution in the explicit provisionBucket mutation).
|
|
26
26
|
*/
|
|
27
27
|
const ALL_STORAGE_MODULES_QUERY = `
|
|
28
|
-
SELECT
|
|
29
|
-
sm.id,
|
|
30
|
-
sm.database_id,
|
|
31
|
-
sm.buckets_table_id,
|
|
32
|
-
sm.scope,
|
|
33
|
-
sm.entity_field,
|
|
34
|
-
sm.entity_table_id,
|
|
35
|
-
bs.schema_name AS buckets_schema,
|
|
36
|
-
bt.name AS buckets_table,
|
|
37
|
-
sm.allowed_origins,
|
|
38
|
-
es.schema_name AS entity_schema,
|
|
39
|
-
et.name AS entity_table
|
|
40
|
-
FROM metaschema_modules_public.storage_module sm
|
|
41
|
-
JOIN metaschema_public.table bt ON bt.id = sm.buckets_table_id
|
|
42
|
-
JOIN metaschema_public.schema bs ON bs.id = bt.schema_id
|
|
43
|
-
LEFT JOIN metaschema_public.table et ON et.id = sm.entity_table_id
|
|
44
|
-
LEFT JOIN metaschema_public.schema es ON es.id = et.schema_id
|
|
45
|
-
WHERE sm.database_id = $1
|
|
28
|
+
SELECT * FROM function_resolution.storage_modules()
|
|
46
29
|
`;
|
|
47
30
|
/**
|
|
48
31
|
* Run a query against a grafast `withPgClient` client.
|
|
@@ -77,7 +60,7 @@ function scopeKeyColumn(storageModule) {
|
|
|
77
60
|
* silently picking.
|
|
78
61
|
*/
|
|
79
62
|
async function resolveBucketByKey(pgClient, databaseId, bucketKey, ownerId) {
|
|
80
|
-
const result = await runQuery(pgClient, ALL_STORAGE_MODULES_QUERY
|
|
63
|
+
const result = await runQuery(pgClient, ALL_STORAGE_MODULES_QUERY);
|
|
81
64
|
const modules = result.rows;
|
|
82
65
|
if (modules.length === 0) {
|
|
83
66
|
throw new Error(`STORAGE_MODULE_NOT_PROVISIONED: database ${databaseId} has no storage modules`);
|
|
@@ -128,81 +111,15 @@ async function resolveDatabaseId(pgClient) {
|
|
|
128
111
|
const result = await runQuery(pgClient, `SELECT jwt_private.current_database_id() AS id`);
|
|
129
112
|
return result.rows[0]?.id ?? null;
|
|
130
113
|
}
|
|
131
|
-
async function resolveEntityContext(pgClient, storageModule) {
|
|
132
|
-
const result = await runQuery(pgClient, `SELECT r.entity_type, r.get_org_fn_schema, r.get_org_fn
|
|
133
|
-
FROM metaschema.resolve_entity_context_by_field($1::uuid, $2::uuid, $3::text) r`, [storageModule.database_id, storageModule.buckets_table_id, storageModule.entity_field]);
|
|
134
|
-
return result.rows[0] ?? {
|
|
135
|
-
entity_type: null,
|
|
136
|
-
get_org_fn_schema: null,
|
|
137
|
-
get_org_fn: null,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
114
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* A callable enqueue function beside the trigger would be the durable fix for
|
|
146
|
-
* this deliberate mirroring, but is out of scope here.
|
|
147
|
-
*
|
|
148
|
-
* The three scope shapes differ only in the payload entries and the identity
|
|
149
|
-
* arguments, so those are the only parts a caller supplies — the identifier,
|
|
150
|
-
* queue, retry, and priority policy exist exactly once.
|
|
115
|
+
* Enqueue reconciliation through the storage module's generated
|
|
116
|
+
* `<private>.<buckets>_provision(bucket_id)`. It runs as the request role: it
|
|
117
|
+
* locks the bucket row FOR UPDATE under the buckets table's own RLS, then
|
|
118
|
+
* enqueues exactly the job the buckets INSERT trigger does.
|
|
151
119
|
*/
|
|
152
|
-
function addJobStatement(payloadEntries, identityArgs) {
|
|
153
|
-
return `SELECT (app_jobs.add_job(
|
|
154
|
-
identifier => 'storage:provision_bucket',
|
|
155
|
-
payload => json_build_object(${payloadEntries}),
|
|
156
|
-
queue_name => 'bucket:' || $1::text,
|
|
157
|
-
max_attempts => 25,
|
|
158
|
-
priority => 0${identityArgs}
|
|
159
|
-
)).id AS id`;
|
|
160
|
-
}
|
|
161
120
|
async function enqueueReconciliationJob(pgClient, storageModule, bucket) {
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
let text;
|
|
165
|
-
let values;
|
|
166
|
-
if (entityField === null) {
|
|
167
|
-
text = addJobStatement(`'id', $1::uuid,
|
|
168
|
-
'scope', $2::text`, '');
|
|
169
|
-
values = [bucket.id, scope];
|
|
170
|
-
}
|
|
171
|
-
else if (entityField === 'database_id') {
|
|
172
|
-
const databaseId = bucket.scope_key;
|
|
173
|
-
if (!databaseId) {
|
|
174
|
-
throw new Error(`STORAGE_BUCKET_SCOPE_KEY_MISSING: bucket ${bucket.id} has no database_id`);
|
|
175
|
-
}
|
|
176
|
-
text = addJobStatement(`'database_id', $2::uuid,
|
|
177
|
-
'id', $1::uuid,
|
|
178
|
-
'scope', $3::text`, `,
|
|
179
|
-
db_id => $2,
|
|
180
|
-
entity_id => $2,
|
|
181
|
-
organization_id => NULL,
|
|
182
|
-
entity_type => $3`);
|
|
183
|
-
values = [bucket.id, databaseId, scope];
|
|
184
|
-
}
|
|
185
|
-
else if (entityField === 'owner_id') {
|
|
186
|
-
const ownerId = bucket.scope_key;
|
|
187
|
-
if (!ownerId) {
|
|
188
|
-
throw new Error(`STORAGE_BUCKET_SCOPE_KEY_MISSING: bucket ${bucket.id} has no owner_id`);
|
|
189
|
-
}
|
|
190
|
-
const context = await resolveEntityContext(pgClient, storageModule);
|
|
191
|
-
const orgFunction = context.get_org_fn_schema && context.get_org_fn
|
|
192
|
-
? `${quotes_1.QuoteUtils.quoteQualifiedIdentifier(context.get_org_fn_schema, context.get_org_fn)}($3::text, $2::uuid)`
|
|
193
|
-
: 'NULL';
|
|
194
|
-
text = addJobStatement(`'id', $1::uuid,
|
|
195
|
-
'owner_id', $2::uuid,
|
|
196
|
-
'scope', $3::text`, `,
|
|
197
|
-
entity_id => $2,
|
|
198
|
-
organization_id => ${orgFunction},
|
|
199
|
-
entity_type => $3`);
|
|
200
|
-
values = [bucket.id, ownerId, scope];
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
throw new Error(`STORAGE_BUCKET_ENTITY_FIELD_UNSUPPORTED: ${entityField}`);
|
|
204
|
-
}
|
|
205
|
-
const result = await runQuery(pgClient, text, values);
|
|
121
|
+
const provisionFunction = quotes_1.QuoteUtils.quoteQualifiedIdentifier(storageModule.private_schema, `${storageModule.buckets_table}_provision`);
|
|
122
|
+
const result = await runQuery(pgClient, `SELECT ${provisionFunction}($1::uuid) AS id`, [bucket.id]);
|
|
206
123
|
const jobId = result.rows[0]?.id;
|
|
207
124
|
if (!jobId) {
|
|
208
125
|
throw new Error(`STORAGE_BUCKET_JOB_ID_MISSING: bucket ${bucket.id}`);
|