graphile-bucket-provisioner-plugin 1.14.2 → 1.15.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/README.md +28 -137
- package/esm/index.d.ts +3 -29
- package/esm/index.js +2 -28
- package/esm/plugin.d.ts +6 -12
- package/esm/plugin.js +129 -150
- package/esm/preset.d.ts +2 -30
- package/esm/preset.js +3 -30
- package/esm/types.d.ts +9 -57
- package/index.d.ts +3 -29
- package/index.js +2 -28
- package/package.json +3 -3
- package/plugin.d.ts +6 -12
- package/plugin.js +129 -150
- package/preset.d.ts +2 -30
- package/preset.js +3 -30
- package/types.d.ts +9 -57
package/esm/plugin.js
CHANGED
|
@@ -3,26 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Adds S3 bucket provisioning support to PostGraphile v5:
|
|
5
5
|
*
|
|
6
|
-
* 1. `provisionBucket` mutation — explicitly
|
|
6
|
+
* 1. `provisionBucket` mutation — explicitly enqueue reconciliation for a
|
|
7
7
|
* logical bucket row in the database. Reads the bucket config via RLS,
|
|
8
|
-
* then
|
|
9
|
-
*
|
|
10
|
-
* This plugin uses `@constructive-io/bucket-provisioner` for the actual
|
|
11
|
-
* S3 operations (bucket creation, Block Public Access, CORS, policies,
|
|
12
|
-
* versioning, lifecycle rules).
|
|
8
|
+
* then queues the same storage reconciler job used by the INSERT trigger.
|
|
13
9
|
*
|
|
14
10
|
* Detection: Uses the `@storageBuckets` smart tag on the codec (table).
|
|
15
11
|
* The storage module generator in constructive-db sets this tag on the
|
|
16
12
|
* generated buckets table via a smart comment:
|
|
17
13
|
* COMMENT ON TABLE buckets IS E'@storageBuckets\nStorage buckets table';
|
|
18
14
|
*/
|
|
19
|
-
import { BucketProvisioner, } from '@constructive-io/bucket-provisioner';
|
|
20
|
-
import { Logger } from '@pgpmjs/logger';
|
|
21
15
|
import { QuoteUtils } from '@pgsql/quotes';
|
|
22
16
|
import { context as grafastContext, lambda, object } from 'grafast';
|
|
23
|
-
import { recordPhysicalName as recordPhysicalBucketName } from 'graphile-storage-registry';
|
|
24
17
|
import { extendSchema, gql } from 'graphile-utils';
|
|
25
|
-
const log = new Logger('graphile-bucket-provisioner:plugin');
|
|
26
18
|
// --- Storage module queries ---
|
|
27
19
|
/**
|
|
28
20
|
* Resolve ALL storage modules for a database (for bucket-key and ownerId-based
|
|
@@ -31,7 +23,10 @@ const log = new Logger('graphile-bucket-provisioner:plugin');
|
|
|
31
23
|
const ALL_STORAGE_MODULES_QUERY = `
|
|
32
24
|
SELECT
|
|
33
25
|
sm.id,
|
|
26
|
+
sm.database_id,
|
|
27
|
+
sm.buckets_table_id,
|
|
34
28
|
sm.scope,
|
|
29
|
+
sm.entity_field,
|
|
35
30
|
sm.entity_table_id,
|
|
36
31
|
bs.schema_name AS buckets_schema,
|
|
37
32
|
bt.name AS buckets_table,
|
|
@@ -59,6 +54,17 @@ const ALL_STORAGE_MODULES_QUERY = `
|
|
|
59
54
|
function runQuery(pgClient, text, values) {
|
|
60
55
|
return pgClient.query(values === undefined ? { text } : { text, values });
|
|
61
56
|
}
|
|
57
|
+
function scopeKeySelect(storageModule) {
|
|
58
|
+
return storageModule.entity_field === null
|
|
59
|
+
? ''
|
|
60
|
+
: `, ${QuoteUtils.quoteIdentifier(storageModule.entity_field)} AS scope_key`;
|
|
61
|
+
}
|
|
62
|
+
function scopeKeyColumn(storageModule) {
|
|
63
|
+
if (storageModule.entity_field === null) {
|
|
64
|
+
throw new Error(`STORAGE_BUCKET_SCOPE_KEY_MISSING: storage module ${storageModule.id} has no entity field`);
|
|
65
|
+
}
|
|
66
|
+
return QuoteUtils.quoteIdentifier(storageModule.entity_field);
|
|
67
|
+
}
|
|
62
68
|
/**
|
|
63
69
|
* The explicit provisionBucket mutation's resolution: find the plane that
|
|
64
70
|
* actually holds the named bucket row.
|
|
@@ -83,9 +89,9 @@ async function resolveBucketByKey(pgClient, databaseId, bucketKey, ownerId) {
|
|
|
83
89
|
if (probe.rows.length === 0)
|
|
84
90
|
continue;
|
|
85
91
|
const bucketsTable = QuoteUtils.quoteQualifiedIdentifier(mod.buckets_schema, mod.buckets_table);
|
|
86
|
-
const bucketResult = await runQuery(pgClient, `SELECT id, key, type, is_public, allowed_origins, physical_name
|
|
92
|
+
const bucketResult = await runQuery(pgClient, `SELECT id, key, type, is_public, allowed_origins, physical_name${scopeKeySelect(mod)}
|
|
87
93
|
FROM ${bucketsTable}
|
|
88
|
-
WHERE key = $1 AND
|
|
94
|
+
WHERE key = $1 AND ${scopeKeyColumn(mod)} = $2
|
|
89
95
|
LIMIT 1`, [bucketKey, ownerId]);
|
|
90
96
|
if (bucketResult.rows.length === 0)
|
|
91
97
|
return null;
|
|
@@ -96,7 +102,7 @@ async function resolveBucketByKey(pgClient, databaseId, bucketKey, ownerId) {
|
|
|
96
102
|
const matches = [];
|
|
97
103
|
for (const mod of modules.filter((m) => m.entity_table_id === null)) {
|
|
98
104
|
const bucketsTable = QuoteUtils.quoteQualifiedIdentifier(mod.buckets_schema, mod.buckets_table);
|
|
99
|
-
const bucketResult = await runQuery(pgClient, `SELECT id, key, type, is_public, allowed_origins, physical_name
|
|
105
|
+
const bucketResult = await runQuery(pgClient, `SELECT id, key, type, is_public, allowed_origins, physical_name${scopeKeySelect(mod)}
|
|
100
106
|
FROM ${bucketsTable}
|
|
101
107
|
WHERE key = $1
|
|
102
108
|
LIMIT 1`, [bucketKey]);
|
|
@@ -113,43 +119,7 @@ async function resolveBucketByKey(pgClient, databaseId, bucketKey, ownerId) {
|
|
|
113
119
|
}
|
|
114
120
|
return matches[0];
|
|
115
121
|
}
|
|
116
|
-
/**
|
|
117
|
-
* Normalize the recorded physical coordinate at the DB boundary.
|
|
118
|
-
*
|
|
119
|
-
* A bucket row either carries a recorded coordinate or it does not; SQL nulls
|
|
120
|
-
* and absent columns both mean "never provisioned". Collapsing them here is
|
|
121
|
-
* the single place that shape is interpreted — callers branch on `string`
|
|
122
|
-
* vs `null` and never coalesce a bucket name into existence.
|
|
123
|
-
*/
|
|
124
|
-
function storedPhysicalName(row) {
|
|
125
|
-
return row.physical_name == null ? null : row.physical_name;
|
|
126
|
-
}
|
|
127
122
|
// --- Helpers ---
|
|
128
|
-
/**
|
|
129
|
-
* Resolve the connection config from the options. If the option is a lazy
|
|
130
|
-
* getter function, call it (and cache the result).
|
|
131
|
-
*/
|
|
132
|
-
function resolveConnection(options) {
|
|
133
|
-
if (typeof options.connection === 'function') {
|
|
134
|
-
const resolved = options.connection();
|
|
135
|
-
// Cache so subsequent calls don't re-evaluate
|
|
136
|
-
options.connection = resolved;
|
|
137
|
-
return resolved;
|
|
138
|
-
}
|
|
139
|
-
return options.connection;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Resolve the S3 bucket name from a logical bucket key.
|
|
143
|
-
*/
|
|
144
|
-
function resolveBucketName(databaseId, bucketKey, options) {
|
|
145
|
-
if (!options.resolveBucketName) {
|
|
146
|
-
throw new Error('STORAGE_BUCKET_NAME_POLICY_MISSING: no resolveBucketName was configured, so there is ' +
|
|
147
|
-
`no name to provision for bucket "${bucketKey}" of database ${databaseId}. ` +
|
|
148
|
-
'Physical bucket naming is a deployment policy; the configured s3.bucket is a ' +
|
|
149
|
-
'connection default and is never a tenant bucket.');
|
|
150
|
-
}
|
|
151
|
-
return options.resolveBucketName(databaseId, bucketKey);
|
|
152
|
-
}
|
|
153
123
|
/**
|
|
154
124
|
* Resolve the database_id from the JWT context.
|
|
155
125
|
*/
|
|
@@ -157,72 +127,110 @@ async function resolveDatabaseId(pgClient) {
|
|
|
157
127
|
const result = await runQuery(pgClient, `SELECT jwt_private.current_database_id() AS id`);
|
|
158
128
|
return result.rows[0]?.id ?? null;
|
|
159
129
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (bucketOrigins && bucketOrigins.length > 0) {
|
|
168
|
-
return bucketOrigins;
|
|
169
|
-
}
|
|
170
|
-
if (storageModuleOrigins && storageModuleOrigins.length > 0) {
|
|
171
|
-
return storageModuleOrigins;
|
|
172
|
-
}
|
|
173
|
-
return pluginOrigins;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Build a BucketProvisioner with per-database connection overrides.
|
|
177
|
-
*/
|
|
178
|
-
function buildProvisioner(options, storageModule, effectiveOrigins) {
|
|
179
|
-
const connection = resolveConnection(options);
|
|
180
|
-
const effectiveConnection = {
|
|
181
|
-
...connection,
|
|
182
|
-
...(storageModule?.endpoint ? { endpoint: storageModule.endpoint } : {}),
|
|
183
|
-
...(storageModule?.provider
|
|
184
|
-
? { provider: storageModule.provider }
|
|
185
|
-
: {}),
|
|
130
|
+
async function resolveEntityContext(pgClient, storageModule) {
|
|
131
|
+
const result = await runQuery(pgClient, `SELECT r.entity_type, r.get_org_fn_schema, r.get_org_fn
|
|
132
|
+
FROM metaschema.resolve_entity_context_by_field($1::uuid, $2::uuid, $3::text) r`, [storageModule.database_id, storageModule.buckets_table_id, storageModule.entity_field]);
|
|
133
|
+
return result.rows[0] ?? {
|
|
134
|
+
entity_type: null,
|
|
135
|
+
get_org_fn_schema: null,
|
|
136
|
+
get_org_fn: null,
|
|
186
137
|
};
|
|
187
|
-
return new BucketProvisioner({
|
|
188
|
-
connection: effectiveConnection,
|
|
189
|
-
allowedOrigins: effectiveOrigins,
|
|
190
|
-
});
|
|
191
138
|
}
|
|
192
139
|
/**
|
|
193
|
-
*
|
|
140
|
+
* Mirror the storage module generator's data_job_trigger shape from
|
|
141
|
+
* `packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/storage_module.sql`
|
|
142
|
+
* and the add_job argument assembly in
|
|
143
|
+
* `packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/job_trigger.sql`.
|
|
144
|
+
* A callable enqueue function beside the trigger would be the durable fix for
|
|
145
|
+
* this deliberate mirroring, but is out of scope here.
|
|
194
146
|
*/
|
|
195
|
-
async function
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
`
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
147
|
+
async function enqueueReconciliationJob(pgClient, storageModule, bucket) {
|
|
148
|
+
const scope = storageModule.scope;
|
|
149
|
+
const entityField = storageModule.entity_field;
|
|
150
|
+
let text;
|
|
151
|
+
let values;
|
|
152
|
+
if (entityField === null) {
|
|
153
|
+
text = `SELECT (app_jobs.add_job(
|
|
154
|
+
identifier => 'storage:provision_bucket',
|
|
155
|
+
payload => json_build_object(
|
|
156
|
+
'id', $1::uuid,
|
|
157
|
+
'scope', $2::text
|
|
158
|
+
),
|
|
159
|
+
queue_name => 'bucket:' || $1::text,
|
|
160
|
+
max_attempts => 25,
|
|
161
|
+
priority => 0
|
|
162
|
+
)).id AS id`;
|
|
163
|
+
values = [bucket.id, scope];
|
|
164
|
+
}
|
|
165
|
+
else if (entityField === 'database_id') {
|
|
166
|
+
const databaseId = bucket.scope_key;
|
|
167
|
+
if (!databaseId) {
|
|
168
|
+
throw new Error(`STORAGE_BUCKET_SCOPE_KEY_MISSING: bucket ${bucket.id} has no database_id`);
|
|
169
|
+
}
|
|
170
|
+
text = `SELECT (app_jobs.add_job(
|
|
171
|
+
identifier => 'storage:provision_bucket',
|
|
172
|
+
payload => json_build_object(
|
|
173
|
+
'database_id', $2::uuid,
|
|
174
|
+
'id', $1::uuid,
|
|
175
|
+
'scope', $3::text
|
|
176
|
+
),
|
|
177
|
+
db_id => $2,
|
|
178
|
+
queue_name => 'bucket:' || $1::text,
|
|
179
|
+
max_attempts => 25,
|
|
180
|
+
priority => 0,
|
|
181
|
+
entity_id => $2,
|
|
182
|
+
organization_id => NULL,
|
|
183
|
+
entity_type => $3
|
|
184
|
+
)).id AS id`;
|
|
185
|
+
values = [bucket.id, databaseId, scope];
|
|
186
|
+
}
|
|
187
|
+
else if (entityField === 'owner_id') {
|
|
188
|
+
const ownerId = bucket.scope_key;
|
|
189
|
+
if (!ownerId) {
|
|
190
|
+
throw new Error(`STORAGE_BUCKET_SCOPE_KEY_MISSING: bucket ${bucket.id} has no owner_id`);
|
|
191
|
+
}
|
|
192
|
+
const context = await resolveEntityContext(pgClient, storageModule);
|
|
193
|
+
const orgFunction = context.get_org_fn_schema && context.get_org_fn
|
|
194
|
+
? `${QuoteUtils.quoteQualifiedIdentifier(context.get_org_fn_schema, context.get_org_fn)}($3::text, $2::uuid)`
|
|
195
|
+
: 'NULL';
|
|
196
|
+
text = `SELECT (app_jobs.add_job(
|
|
197
|
+
identifier => 'storage:provision_bucket',
|
|
198
|
+
payload => json_build_object(
|
|
199
|
+
'id', $1::uuid,
|
|
200
|
+
'owner_id', $2::uuid,
|
|
201
|
+
'scope', $3::text
|
|
202
|
+
),
|
|
203
|
+
queue_name => 'bucket:' || $1::text,
|
|
204
|
+
max_attempts => 25,
|
|
205
|
+
priority => 0,
|
|
206
|
+
entity_id => $2,
|
|
207
|
+
organization_id => ${orgFunction},
|
|
208
|
+
entity_type => $3
|
|
209
|
+
)).id AS id`;
|
|
210
|
+
values = [bucket.id, ownerId, scope];
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
throw new Error(`STORAGE_BUCKET_ENTITY_FIELD_UNSUPPORTED: ${entityField}`);
|
|
214
|
+
}
|
|
215
|
+
const result = await runQuery(pgClient, text, values);
|
|
216
|
+
const jobId = result.rows[0]?.id;
|
|
217
|
+
if (!jobId) {
|
|
218
|
+
throw new Error(`STORAGE_BUCKET_JOB_ID_MISSING: bucket ${bucket.id}`);
|
|
219
|
+
}
|
|
220
|
+
return jobId;
|
|
212
221
|
}
|
|
213
222
|
// --- Plugin factory ---
|
|
214
223
|
/**
|
|
215
224
|
* Creates the bucket provisioner plugin.
|
|
216
225
|
*
|
|
217
|
-
* This plugin provides one
|
|
226
|
+
* This plugin provides one reconciliation pathway:
|
|
218
227
|
*
|
|
219
228
|
* 1. **Explicit `provisionBucket` mutation** — Call this mutation with a
|
|
220
|
-
* bucket key to
|
|
221
|
-
* by RLS on the buckets table.
|
|
229
|
+
* bucket key to enqueue reconciliation (or re-reconciliation) for the
|
|
230
|
+
* bucket. Protected by RLS on the buckets table.
|
|
222
231
|
*
|
|
223
|
-
* @param options - Plugin configuration (S3 credentials, CORS origins, naming)
|
|
224
232
|
*/
|
|
225
|
-
export function createBucketProvisionerPlugin(
|
|
233
|
+
export function createBucketProvisionerPlugin() {
|
|
226
234
|
// The extendSchema plugin adds the explicit provisionBucket mutation
|
|
227
235
|
const mutationPlugin = extendSchema(() => ({
|
|
228
236
|
typeDefs: gql `
|
|
@@ -237,26 +245,22 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
type ProvisionBucketPayload {
|
|
240
|
-
"""
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
provider: String!
|
|
248
|
-
"""The S3 endpoint (null for AWS S3 default)"""
|
|
249
|
-
endpoint: String
|
|
250
|
-
"""Error message if provisioning failed"""
|
|
251
|
-
error: String
|
|
248
|
+
"""The logical bucket row that was queued for reconciliation."""
|
|
249
|
+
bucketId: UUID!
|
|
250
|
+
bucketKey: String!
|
|
251
|
+
"""The physical bucket name already recorded, or null when reconciliation has not completed."""
|
|
252
|
+
physicalName: String
|
|
253
|
+
"""The reconciler job enqueued to provision this bucket."""
|
|
254
|
+
jobId: UUID!
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
extend type Mutation {
|
|
255
258
|
"""
|
|
256
|
-
|
|
257
|
-
Reads the bucket config via RLS, then
|
|
258
|
-
|
|
259
|
-
|
|
259
|
+
Reconcile an S3 bucket for a logical bucket in the database.
|
|
260
|
+
Reads the bucket config via RLS, then enqueues the same
|
|
261
|
+
storage:provision_bucket job used by the INSERT trigger. This is
|
|
262
|
+
idempotent for an already-reconciled bucket; enqueue failures become
|
|
263
|
+
GraphQL errors.
|
|
260
264
|
"""
|
|
261
265
|
provisionBucket(
|
|
262
266
|
input: ProvisionBucketInput!
|
|
@@ -292,38 +296,13 @@ export function createBucketProvisionerPlugin(options) {
|
|
|
292
296
|
throw new Error('BUCKET_NOT_FOUND');
|
|
293
297
|
}
|
|
294
298
|
const { storageModule, bucket } = resolution;
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
const result = await provisionBucketForRow(storageModule, databaseId, bucket.key, bucket.type, bucket.allowed_origins, options, s3BucketName);
|
|
304
|
-
// Record the exact provisioned name on the source row.
|
|
305
|
-
await withPgClient(null, (client) => recordPhysicalBucketName((query) => runQuery(client, query.text, query.values), bucketsTable, bucket.id, result.bucketName));
|
|
306
|
-
log.info(`Recorded physical_name="${result.bucketName}" on bucket ${bucket.id}`);
|
|
307
|
-
return {
|
|
308
|
-
success: true,
|
|
309
|
-
bucketName: result.bucketName,
|
|
310
|
-
accessType: result.accessType,
|
|
311
|
-
provider: result.provider,
|
|
312
|
-
endpoint: result.endpoint,
|
|
313
|
-
error: null,
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
catch (err) {
|
|
317
|
-
log.error(`Failed to provision bucket "${bucketKey}": ${err.message}`);
|
|
318
|
-
return {
|
|
319
|
-
success: false,
|
|
320
|
-
bucketName: s3BucketName,
|
|
321
|
-
accessType: bucket.type,
|
|
322
|
-
provider: resolveConnection(options).provider,
|
|
323
|
-
endpoint: resolveConnection(options).endpoint ?? null,
|
|
324
|
-
error: err.message,
|
|
325
|
-
};
|
|
326
|
-
}
|
|
299
|
+
const jobId = await enqueueReconciliationJob(pgClient, storageModule, bucket);
|
|
300
|
+
return {
|
|
301
|
+
bucketId: bucket.id,
|
|
302
|
+
bucketKey: bucket.key,
|
|
303
|
+
physicalName: bucket.physical_name,
|
|
304
|
+
jobId,
|
|
305
|
+
};
|
|
327
306
|
});
|
|
328
307
|
});
|
|
329
308
|
},
|
package/esm/preset.d.ts
CHANGED
|
@@ -5,36 +5,8 @@
|
|
|
5
5
|
* in PostGraphile. Wraps the main plugin with sensible defaults.
|
|
6
6
|
*/
|
|
7
7
|
import type { GraphileConfig } from 'graphile-config';
|
|
8
|
-
import type { BucketProvisionerPluginOptions } from './types';
|
|
9
8
|
/**
|
|
10
|
-
* Creates a preset that includes the bucket
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* import { BucketProvisionerPreset } from 'graphile-bucket-provisioner-plugin';
|
|
15
|
-
* import { getEnvOptions } from '@constructive-io/graphql-env';
|
|
16
|
-
*
|
|
17
|
-
* // Use a lazy getter so env vars are read at runtime, not import time
|
|
18
|
-
* function getConnection() {
|
|
19
|
-
* const { cdn } = getEnvOptions();
|
|
20
|
-
* return {
|
|
21
|
-
* provider: cdn?.provider || 'minio',
|
|
22
|
-
* region: cdn?.awsRegion || 'us-east-1',
|
|
23
|
-
* endpoint: cdn?.endpoint || 'http://minio:9000',
|
|
24
|
-
* accessKeyId: cdn?.awsAccessKey!,
|
|
25
|
-
* secretAccessKey: cdn?.awsSecretKey!,
|
|
26
|
-
* };
|
|
27
|
-
* }
|
|
28
|
-
*
|
|
29
|
-
* const preset = {
|
|
30
|
-
* extends: [
|
|
31
|
-
* BucketProvisionerPreset({
|
|
32
|
-
* connection: getConnection, // pass function ref, NOT getConnection()
|
|
33
|
-
* allowedOrigins: ['https://app.example.com'],
|
|
34
|
-
* }),
|
|
35
|
-
* ],
|
|
36
|
-
* };
|
|
37
|
-
* ```
|
|
9
|
+
* Creates a preset that includes the bucket reconciliation plugin.
|
|
38
10
|
*/
|
|
39
|
-
export declare function BucketProvisionerPreset(
|
|
11
|
+
export declare function BucketProvisionerPreset(): GraphileConfig.Preset;
|
|
40
12
|
export default BucketProvisionerPreset;
|
package/esm/preset.js
CHANGED
|
@@ -6,38 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { createBucketProvisionerPlugin } from './plugin';
|
|
8
8
|
/**
|
|
9
|
-
* Creates a preset that includes the bucket
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```typescript
|
|
13
|
-
* import { BucketProvisionerPreset } from 'graphile-bucket-provisioner-plugin';
|
|
14
|
-
* import { getEnvOptions } from '@constructive-io/graphql-env';
|
|
15
|
-
*
|
|
16
|
-
* // Use a lazy getter so env vars are read at runtime, not import time
|
|
17
|
-
* function getConnection() {
|
|
18
|
-
* const { cdn } = getEnvOptions();
|
|
19
|
-
* return {
|
|
20
|
-
* provider: cdn?.provider || 'minio',
|
|
21
|
-
* region: cdn?.awsRegion || 'us-east-1',
|
|
22
|
-
* endpoint: cdn?.endpoint || 'http://minio:9000',
|
|
23
|
-
* accessKeyId: cdn?.awsAccessKey!,
|
|
24
|
-
* secretAccessKey: cdn?.awsSecretKey!,
|
|
25
|
-
* };
|
|
26
|
-
* }
|
|
27
|
-
*
|
|
28
|
-
* const preset = {
|
|
29
|
-
* extends: [
|
|
30
|
-
* BucketProvisionerPreset({
|
|
31
|
-
* connection: getConnection, // pass function ref, NOT getConnection()
|
|
32
|
-
* allowedOrigins: ['https://app.example.com'],
|
|
33
|
-
* }),
|
|
34
|
-
* ],
|
|
35
|
-
* };
|
|
36
|
-
* ```
|
|
9
|
+
* Creates a preset that includes the bucket reconciliation plugin.
|
|
37
10
|
*/
|
|
38
|
-
export function BucketProvisionerPreset(
|
|
11
|
+
export function BucketProvisionerPreset() {
|
|
39
12
|
return {
|
|
40
|
-
plugins: [createBucketProvisionerPlugin(
|
|
13
|
+
plugins: [createBucketProvisionerPlugin()],
|
|
41
14
|
};
|
|
42
15
|
}
|
|
43
16
|
export default BucketProvisionerPreset;
|
package/esm/types.d.ts
CHANGED
|
@@ -1,50 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the bucket provisioner plugin.
|
|
3
3
|
*/
|
|
4
|
-
import type { BucketAccessType, ProvisionResult, StorageConnectionConfig, StorageProvider } from '@constructive-io/bucket-provisioner';
|
|
5
|
-
export type { BucketAccessType, ProvisionResult, StorageConnectionConfig, StorageProvider };
|
|
6
|
-
/**
|
|
7
|
-
* S3 connection configuration or a lazy getter that returns it on first use.
|
|
8
|
-
*
|
|
9
|
-
* When a function is provided, it will only be called when the first
|
|
10
|
-
* provisioning operation actually needs the S3 client — avoiding eager
|
|
11
|
-
* env-var reads and S3Client creation at module import time.
|
|
12
|
-
*/
|
|
13
|
-
export type ConnectionConfigOrGetter = StorageConnectionConfig | (() => StorageConnectionConfig);
|
|
14
|
-
/**
|
|
15
|
-
* Function to derive the actual S3 bucket name from a logical bucket key.
|
|
16
|
-
*
|
|
17
|
-
* @param databaseId - The metaschema database UUID
|
|
18
|
-
* @param bucketKey - The logical bucket key from the database (e.g., "public", "private")
|
|
19
|
-
* @returns The S3 bucket name to create/configure
|
|
20
|
-
*/
|
|
21
|
-
export type BucketNameResolver = (databaseId: string, bucketKey: string) => string;
|
|
22
|
-
/**
|
|
23
|
-
* Plugin options for the bucket provisioner plugin.
|
|
24
|
-
*/
|
|
25
|
-
export interface BucketProvisionerPluginOptions {
|
|
26
|
-
/**
|
|
27
|
-
* S3 connection configuration (credentials, endpoint, provider).
|
|
28
|
-
* Can be a concrete object or a lazy getter function.
|
|
29
|
-
*/
|
|
30
|
-
connection: ConnectionConfigOrGetter;
|
|
31
|
-
/**
|
|
32
|
-
* Allowed origins for CORS rules on provisioned buckets.
|
|
33
|
-
* These are the domains where your app runs (e.g., ["https://app.example.com"]).
|
|
34
|
-
* Required for browser-based presigned URL uploads.
|
|
35
|
-
*/
|
|
36
|
-
allowedOrigins: string[];
|
|
37
|
-
/**
|
|
38
|
-
* Optional custom function to derive S3 bucket names from logical bucket keys.
|
|
39
|
-
* Naming is a deployment policy and must be supplied by the caller.
|
|
40
|
-
*/
|
|
41
|
-
resolveBucketName?: BucketNameResolver;
|
|
42
|
-
/**
|
|
43
|
-
* Whether to enable versioning on provisioned buckets.
|
|
44
|
-
* Default: false
|
|
45
|
-
*/
|
|
46
|
-
versioning?: boolean;
|
|
47
|
-
}
|
|
48
4
|
/**
|
|
49
5
|
* Input for the provisionBucket mutation.
|
|
50
6
|
*/
|
|
@@ -58,19 +14,15 @@ export interface ProvisionBucketInput {
|
|
|
58
14
|
ownerId?: string;
|
|
59
15
|
}
|
|
60
16
|
/**
|
|
61
|
-
* Result of the provisionBucket mutation.
|
|
17
|
+
* Result of the provisionBucket reconciliation enqueue mutation.
|
|
62
18
|
*/
|
|
63
19
|
export interface ProvisionBucketPayload {
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
/** The
|
|
67
|
-
|
|
68
|
-
/** The
|
|
69
|
-
|
|
70
|
-
/** The
|
|
71
|
-
|
|
72
|
-
/** The S3 endpoint (null for AWS S3 default) */
|
|
73
|
-
endpoint: string | null;
|
|
74
|
-
/** Error message if provisioning failed */
|
|
75
|
-
error: string | null;
|
|
20
|
+
/** The logical bucket row queued for reconciliation */
|
|
21
|
+
bucketId: string;
|
|
22
|
+
/** The logical bucket key */
|
|
23
|
+
bucketKey: string;
|
|
24
|
+
/** The physical name already recorded, or null while reconciliation is pending */
|
|
25
|
+
physicalName: string | null;
|
|
26
|
+
/** The queued reconciler job */
|
|
27
|
+
jobId: string;
|
|
76
28
|
}
|
package/index.d.ts
CHANGED
|
@@ -1,35 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bucket Provisioner Plugin for PostGraphile v5
|
|
3
3
|
*
|
|
4
|
-
* Provides an explicit `provisionBucket` mutation for
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* import { BucketProvisionerPreset } from 'graphile-bucket-provisioner-plugin';
|
|
9
|
-
* import { getEnvOptions } from '@constructive-io/graphql-env';
|
|
10
|
-
*
|
|
11
|
-
* // Use a lazy getter so env vars are read at runtime, not import time
|
|
12
|
-
* function getConnection() {
|
|
13
|
-
* const { cdn } = getEnvOptions();
|
|
14
|
-
* return {
|
|
15
|
-
* provider: cdn?.provider || 'minio',
|
|
16
|
-
* region: cdn?.awsRegion || 'us-east-1',
|
|
17
|
-
* endpoint: cdn?.endpoint || 'http://minio:9000',
|
|
18
|
-
* accessKeyId: cdn?.awsAccessKey!,
|
|
19
|
-
* secretAccessKey: cdn?.awsSecretKey!,
|
|
20
|
-
* };
|
|
21
|
-
* }
|
|
22
|
-
*
|
|
23
|
-
* const preset = {
|
|
24
|
-
* extends: [
|
|
25
|
-
* BucketProvisionerPreset({
|
|
26
|
-
* connection: getConnection, // pass function ref, NOT getConnection()
|
|
27
|
-
* allowedOrigins: ['https://app.example.com'],
|
|
28
|
-
* }),
|
|
29
|
-
* ],
|
|
30
|
-
* };
|
|
31
|
-
* ```
|
|
4
|
+
* Provides an explicit `provisionBucket` reconciliation enqueue mutation for
|
|
5
|
+
* PostGraphile v5.
|
|
32
6
|
*/
|
|
33
7
|
export { BucketProvisionerPlugin, createBucketProvisionerPlugin } from './plugin';
|
|
34
8
|
export { BucketProvisionerPreset } from './preset';
|
|
35
|
-
export type {
|
|
9
|
+
export type { ProvisionBucketInput, ProvisionBucketPayload, } from './types';
|
package/index.js
CHANGED
|
@@ -2,34 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Bucket Provisioner Plugin for PostGraphile v5
|
|
4
4
|
*
|
|
5
|
-
* Provides an explicit `provisionBucket` mutation for
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { BucketProvisionerPreset } from 'graphile-bucket-provisioner-plugin';
|
|
10
|
-
* import { getEnvOptions } from '@constructive-io/graphql-env';
|
|
11
|
-
*
|
|
12
|
-
* // Use a lazy getter so env vars are read at runtime, not import time
|
|
13
|
-
* function getConnection() {
|
|
14
|
-
* const { cdn } = getEnvOptions();
|
|
15
|
-
* return {
|
|
16
|
-
* provider: cdn?.provider || 'minio',
|
|
17
|
-
* region: cdn?.awsRegion || 'us-east-1',
|
|
18
|
-
* endpoint: cdn?.endpoint || 'http://minio:9000',
|
|
19
|
-
* accessKeyId: cdn?.awsAccessKey!,
|
|
20
|
-
* secretAccessKey: cdn?.awsSecretKey!,
|
|
21
|
-
* };
|
|
22
|
-
* }
|
|
23
|
-
*
|
|
24
|
-
* const preset = {
|
|
25
|
-
* extends: [
|
|
26
|
-
* BucketProvisionerPreset({
|
|
27
|
-
* connection: getConnection, // pass function ref, NOT getConnection()
|
|
28
|
-
* allowedOrigins: ['https://app.example.com'],
|
|
29
|
-
* }),
|
|
30
|
-
* ],
|
|
31
|
-
* };
|
|
32
|
-
* ```
|
|
5
|
+
* Provides an explicit `provisionBucket` reconciliation enqueue mutation for
|
|
6
|
+
* PostGraphile v5.
|
|
33
7
|
*/
|
|
34
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
9
|
exports.BucketProvisionerPreset = exports.createBucketProvisionerPlugin = exports.BucketProvisionerPlugin = void 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-bucket-provisioner-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"url": "https://github.com/constructive-io/constructive/issues"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@constructive-io/bucket-provisioner": "^0.
|
|
43
|
+
"@constructive-io/bucket-provisioner": "^0.27.0",
|
|
44
44
|
"@pgpmjs/logger": "^2.25.0",
|
|
45
45
|
"@pgsql/quotes": "^18.2.4",
|
|
46
46
|
"graphile-storage-registry": "^0.1.1"
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"@types/node": "^22.19.11",
|
|
59
59
|
"makage": "^0.3.0"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "77d58bb93796be91d4e00156bb6847392584d3bc"
|
|
62
62
|
}
|