graphile-bucket-provisioner-plugin 1.18.1 → 1.19.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 CHANGED
@@ -5,7 +5,7 @@ queueing storage reconciliation.
5
5
 
6
6
  The constructive-db `storage:provision_bucket` job is the only component that
7
7
  mints physical S3 bucket names, provisions S3, and records `physical_name`.
8
- This plugin resolves the logical bucket under RLS and enqueues that job; it
8
+ This plugin resolves the logical bucket under RLS and requests that job; it
9
9
  does not call S3 or derive a physical name.
10
10
 
11
11
  ## Installation
@@ -32,16 +32,18 @@ mutation {
32
32
  bucketId
33
33
  bucketKey
34
34
  physicalName
35
- jobId
35
+ provisionRequestedAt
36
36
  }
37
37
  }
38
38
  ```
39
39
 
40
- `provisionBucket` resolves the bucket row under RLS and enqueues the same
41
- `storage:provision_bucket` job used by the database INSERT trigger. Database
42
- scope jobs include `database_id`, use the `bucket:<uuid>` queue, and have
43
- 25 attempts. Platform scope jobs use the platform trigger payload. Enqueue
44
- failures are returned as GraphQL errors.
40
+ `provisionBucket` resolves the bucket row under RLS and calls the storage
41
+ module's `<buckets>_provision(bucket_id)`, which stamps the row's
42
+ `provision_requested_at` as the caller and returns the row. The bucket's UPDATE
43
+ job trigger then enqueues the same `storage:provision_bucket` job used on
44
+ INSERT, keyed `storage:provision_bucket:<uuid>` on the `bucket:<uuid>` queue, so
45
+ repeated requests while a job is waiting collapse into it. Failures are
46
+ returned as GraphQL errors.
45
47
 
46
48
  The payload fields are:
47
49
 
@@ -50,7 +52,7 @@ The payload fields are:
50
52
  | `bucketId` | Logical bucket row ID |
51
53
  | `bucketKey` | Logical bucket key |
52
54
  | `physicalName` | Recorded physical name, or `null` while reconciliation is pending |
53
- | `jobId` | ID of the queued reconciler job |
55
+ | `provisionRequestedAt` | When reconciliation was last requested |
54
56
 
55
57
  There are no plugin options: S3 connection details, naming, CORS, versioning,
56
58
  and physical-name recording belong to the reconciler.
package/esm/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Bucket Provisioner Plugin for PostGraphile v5
3
3
  *
4
- * Provides an explicit `provisionBucket` reconciliation enqueue mutation for
4
+ * Provides an explicit `provisionBucket` reconciliation request mutation for
5
5
  * PostGraphile v5.
6
6
  */
7
7
  export { BucketProvisionerPlugin, createBucketProvisionerPlugin } from './plugin';
package/esm/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Bucket Provisioner Plugin for PostGraphile v5
3
3
  *
4
- * Provides an explicit `provisionBucket` reconciliation enqueue mutation for
4
+ * Provides an explicit `provisionBucket` reconciliation request mutation for
5
5
  * PostGraphile v5.
6
6
  */
7
7
  export { BucketProvisionerPlugin, createBucketProvisionerPlugin } from './plugin';
package/esm/plugin.d.ts CHANGED
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Adds S3 bucket provisioning support to PostGraphile v5:
5
5
  *
6
- * 1. `provisionBucket` mutation — explicitly enqueue reconciliation for a
7
- * logical bucket row in the database. Reads the bucket config via RLS,
8
- * then queues the same storage reconciler job used by the INSERT trigger.
6
+ * 1. `provisionBucket` mutation — explicitly request reconciliation for a
7
+ * logical bucket row in the database. Stamps the bucket's
8
+ * `provision_requested_at` via RLS; the bucket's own job trigger then
9
+ * queues the same storage reconciler job used on INSERT.
9
10
  *
10
11
  * Detection: Uses the `@storageBuckets` smart tag on the codec (table).
11
12
  * The storage module generator in constructive-db sets this tag on the
@@ -19,7 +20,7 @@ import type { GraphileConfig } from 'graphile-config';
19
20
  * This plugin provides one reconciliation pathway:
20
21
  *
21
22
  * 1. **Explicit `provisionBucket` mutation** — Call this mutation with a
22
- * bucket key to enqueue reconciliation (or re-reconciliation) for the
23
+ * bucket key to request reconciliation (or re-reconciliation) for the
23
24
  * bucket. Protected by RLS on the buckets table.
24
25
  *
25
26
  */
package/esm/plugin.js CHANGED
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Adds S3 bucket provisioning support to PostGraphile v5:
5
5
  *
6
- * 1. `provisionBucket` mutation — explicitly enqueue reconciliation for a
7
- * logical bucket row in the database. Reads the bucket config via RLS,
8
- * then queues the same storage reconciler job used by the INSERT trigger.
6
+ * 1. `provisionBucket` mutation — explicitly request reconciliation for a
7
+ * logical bucket row in the database. Stamps the bucket's
8
+ * `provision_requested_at` via RLS; the bucket's own job trigger then
9
+ * queues the same storage reconciler job used on INSERT.
9
10
  *
10
11
  * Detection: Uses the `@storageBuckets` smart tag on the codec (table).
11
12
  * The storage module generator in constructive-db sets this tag on the
@@ -108,19 +109,21 @@ async function resolveDatabaseId(pgClient) {
108
109
  return result.rows[0]?.id ?? null;
109
110
  }
110
111
  /**
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.
112
+ * Request reconciliation through the storage module's generated
113
+ * `<private>.<buckets>_provision(bucket_id)`. It runs as the request role and
114
+ * stamps the bucket's `provision_requested_at` under the buckets table's own
115
+ * RLS; the bucket's UPDATE job trigger then enqueues the reconciler (keyed and
116
+ * queued on the bucket, so repeated requests collapse into one pending job).
117
+ * It returns the updated bucket row.
115
118
  */
116
- async function enqueueReconciliationJob(pgClient, storageModule, bucket) {
119
+ async function requestReconciliation(pgClient, storageModule, bucket) {
117
120
  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]);
119
- const jobId = result.rows[0]?.id;
120
- if (!jobId) {
121
- throw new Error(`STORAGE_BUCKET_JOB_ID_MISSING: bucket ${bucket.id}`);
121
+ const result = await runQuery(pgClient, `SELECT id, key, physical_name, to_json(provision_requested_at) #>> '{}' AS provision_requested_at FROM ${provisionFunction}($1::uuid)`, [bucket.id]);
122
+ const row = result.rows[0];
123
+ if (!row?.id) {
124
+ throw new Error(`STORAGE_BUCKET_PROVISION_EMPTY: bucket ${bucket.id}`);
122
125
  }
123
- return jobId;
126
+ return row;
124
127
  }
125
128
  // --- Plugin factory ---
126
129
  /**
@@ -129,7 +132,7 @@ async function enqueueReconciliationJob(pgClient, storageModule, bucket) {
129
132
  * This plugin provides one reconciliation pathway:
130
133
  *
131
134
  * 1. **Explicit `provisionBucket` mutation** — Call this mutation with a
132
- * bucket key to enqueue reconciliation (or re-reconciliation) for the
135
+ * bucket key to request reconciliation (or re-reconciliation) for the
133
136
  * bucket. Protected by RLS on the buckets table.
134
137
  *
135
138
  */
@@ -148,22 +151,23 @@ export function createBucketProvisionerPlugin() {
148
151
  }
149
152
 
150
153
  type ProvisionBucketPayload {
151
- """The logical bucket row that was queued for reconciliation."""
154
+ """The logical bucket row whose reconciliation was requested."""
152
155
  bucketId: UUID!
153
156
  bucketKey: String!
154
157
  """The physical bucket name already recorded, or null when reconciliation has not completed."""
155
158
  physicalName: String
156
- """The reconciler job enqueued to provision this bucket."""
157
- jobId: UUID!
159
+ """When reconciliation was last requested for this bucket."""
160
+ provisionRequestedAt: Datetime!
158
161
  }
159
162
 
160
163
  extend type Mutation {
161
164
  """
162
165
  Reconcile an S3 bucket for a logical bucket in the database.
163
- Reads the bucket config via RLS, then enqueues the same
164
- storage:provision_bucket job used by the INSERT trigger. This is
165
- idempotent for an already-reconciled bucket; enqueue failures become
166
- GraphQL errors.
166
+ Records the request on the bucket row via RLS; the bucket's job
167
+ trigger then enqueues the same storage:provision_bucket job used on
168
+ INSERT, and repeated requests collapse into one pending job. This is
169
+ idempotent for an already-reconciled bucket; failures become GraphQL
170
+ errors.
167
171
  """
168
172
  provisionBucket(
169
173
  input: ProvisionBucketInput!
@@ -199,12 +203,12 @@ export function createBucketProvisionerPlugin() {
199
203
  throw new Error('BUCKET_NOT_FOUND');
200
204
  }
201
205
  const { storageModule, bucket } = resolution;
202
- const jobId = await enqueueReconciliationJob(pgClient, storageModule, bucket);
206
+ const provisioned = await requestReconciliation(pgClient, storageModule, bucket);
203
207
  return {
204
- bucketId: bucket.id,
205
- bucketKey: bucket.key,
206
- physicalName: bucket.physical_name,
207
- jobId,
208
+ bucketId: provisioned.id,
209
+ bucketKey: provisioned.key,
210
+ physicalName: provisioned.physical_name,
211
+ provisionRequestedAt: provisioned.provision_requested_at,
208
212
  };
209
213
  });
210
214
  });
package/esm/types.d.ts CHANGED
@@ -14,15 +14,15 @@ export interface ProvisionBucketInput {
14
14
  ownerId?: string;
15
15
  }
16
16
  /**
17
- * Result of the provisionBucket reconciliation enqueue mutation.
17
+ * Result of the provisionBucket mutation: the bucket row it stamped.
18
18
  */
19
19
  export interface ProvisionBucketPayload {
20
- /** The logical bucket row queued for reconciliation */
20
+ /** The logical bucket row whose reconciliation was requested */
21
21
  bucketId: string;
22
22
  /** The logical bucket key */
23
23
  bucketKey: string;
24
24
  /** The physical name already recorded, or null while reconciliation is pending */
25
25
  physicalName: string | null;
26
- /** The queued reconciler job */
27
- jobId: string;
26
+ /** When reconciliation was last requested */
27
+ provisionRequestedAt: string;
28
28
  }
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Bucket Provisioner Plugin for PostGraphile v5
3
3
  *
4
- * Provides an explicit `provisionBucket` reconciliation enqueue mutation for
4
+ * Provides an explicit `provisionBucket` reconciliation request mutation for
5
5
  * PostGraphile v5.
6
6
  */
7
7
  export { BucketProvisionerPlugin, createBucketProvisionerPlugin } from './plugin';
package/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Bucket Provisioner Plugin for PostGraphile v5
4
4
  *
5
- * Provides an explicit `provisionBucket` reconciliation enqueue mutation for
5
+ * Provides an explicit `provisionBucket` reconciliation request mutation for
6
6
  * PostGraphile v5.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphile-bucket-provisioner-plugin",
3
- "version": "1.18.1",
3
+ "version": "1.19.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",
@@ -58,5 +58,5 @@
58
58
  "@types/node": "^22.19.11",
59
59
  "makage": "^0.8.0"
60
60
  },
61
- "gitHead": "0b4ffa11a6d7958ba1068ce8e19616d4b018c0d6"
61
+ "gitHead": "b6c80da2ba0532a4aaf0d0154901efa7911974d8"
62
62
  }
package/plugin.d.ts CHANGED
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Adds S3 bucket provisioning support to PostGraphile v5:
5
5
  *
6
- * 1. `provisionBucket` mutation — explicitly enqueue reconciliation for a
7
- * logical bucket row in the database. Reads the bucket config via RLS,
8
- * then queues the same storage reconciler job used by the INSERT trigger.
6
+ * 1. `provisionBucket` mutation — explicitly request reconciliation for a
7
+ * logical bucket row in the database. Stamps the bucket's
8
+ * `provision_requested_at` via RLS; the bucket's own job trigger then
9
+ * queues the same storage reconciler job used on INSERT.
9
10
  *
10
11
  * Detection: Uses the `@storageBuckets` smart tag on the codec (table).
11
12
  * The storage module generator in constructive-db sets this tag on the
@@ -19,7 +20,7 @@ import type { GraphileConfig } from 'graphile-config';
19
20
  * This plugin provides one reconciliation pathway:
20
21
  *
21
22
  * 1. **Explicit `provisionBucket` mutation** — Call this mutation with a
22
- * bucket key to enqueue reconciliation (or re-reconciliation) for the
23
+ * bucket key to request reconciliation (or re-reconciliation) for the
23
24
  * bucket. Protected by RLS on the buckets table.
24
25
  *
25
26
  */
package/plugin.js CHANGED
@@ -4,9 +4,10 @@
4
4
  *
5
5
  * Adds S3 bucket provisioning support to PostGraphile v5:
6
6
  *
7
- * 1. `provisionBucket` mutation — explicitly enqueue reconciliation for a
8
- * logical bucket row in the database. Reads the bucket config via RLS,
9
- * then queues the same storage reconciler job used by the INSERT trigger.
7
+ * 1. `provisionBucket` mutation — explicitly request reconciliation for a
8
+ * logical bucket row in the database. Stamps the bucket's
9
+ * `provision_requested_at` via RLS; the bucket's own job trigger then
10
+ * queues the same storage reconciler job used on INSERT.
10
11
  *
11
12
  * Detection: Uses the `@storageBuckets` smart tag on the codec (table).
12
13
  * The storage module generator in constructive-db sets this tag on the
@@ -112,19 +113,21 @@ async function resolveDatabaseId(pgClient) {
112
113
  return result.rows[0]?.id ?? null;
113
114
  }
114
115
  /**
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.
116
+ * Request reconciliation through the storage module's generated
117
+ * `<private>.<buckets>_provision(bucket_id)`. It runs as the request role and
118
+ * stamps the bucket's `provision_requested_at` under the buckets table's own
119
+ * RLS; the bucket's UPDATE job trigger then enqueues the reconciler (keyed and
120
+ * queued on the bucket, so repeated requests collapse into one pending job).
121
+ * It returns the updated bucket row.
119
122
  */
120
- async function enqueueReconciliationJob(pgClient, storageModule, bucket) {
123
+ async function requestReconciliation(pgClient, storageModule, bucket) {
121
124
  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]);
123
- const jobId = result.rows[0]?.id;
124
- if (!jobId) {
125
- throw new Error(`STORAGE_BUCKET_JOB_ID_MISSING: bucket ${bucket.id}`);
125
+ const result = await runQuery(pgClient, `SELECT id, key, physical_name, to_json(provision_requested_at) #>> '{}' AS provision_requested_at FROM ${provisionFunction}($1::uuid)`, [bucket.id]);
126
+ const row = result.rows[0];
127
+ if (!row?.id) {
128
+ throw new Error(`STORAGE_BUCKET_PROVISION_EMPTY: bucket ${bucket.id}`);
126
129
  }
127
- return jobId;
130
+ return row;
128
131
  }
129
132
  // --- Plugin factory ---
130
133
  /**
@@ -133,7 +136,7 @@ async function enqueueReconciliationJob(pgClient, storageModule, bucket) {
133
136
  * This plugin provides one reconciliation pathway:
134
137
  *
135
138
  * 1. **Explicit `provisionBucket` mutation** — Call this mutation with a
136
- * bucket key to enqueue reconciliation (or re-reconciliation) for the
139
+ * bucket key to request reconciliation (or re-reconciliation) for the
137
140
  * bucket. Protected by RLS on the buckets table.
138
141
  *
139
142
  */
@@ -152,22 +155,23 @@ function createBucketProvisionerPlugin() {
152
155
  }
153
156
 
154
157
  type ProvisionBucketPayload {
155
- """The logical bucket row that was queued for reconciliation."""
158
+ """The logical bucket row whose reconciliation was requested."""
156
159
  bucketId: UUID!
157
160
  bucketKey: String!
158
161
  """The physical bucket name already recorded, or null when reconciliation has not completed."""
159
162
  physicalName: String
160
- """The reconciler job enqueued to provision this bucket."""
161
- jobId: UUID!
163
+ """When reconciliation was last requested for this bucket."""
164
+ provisionRequestedAt: Datetime!
162
165
  }
163
166
 
164
167
  extend type Mutation {
165
168
  """
166
169
  Reconcile an S3 bucket for a logical bucket in the database.
167
- Reads the bucket config via RLS, then enqueues the same
168
- storage:provision_bucket job used by the INSERT trigger. This is
169
- idempotent for an already-reconciled bucket; enqueue failures become
170
- GraphQL errors.
170
+ Records the request on the bucket row via RLS; the bucket's job
171
+ trigger then enqueues the same storage:provision_bucket job used on
172
+ INSERT, and repeated requests collapse into one pending job. This is
173
+ idempotent for an already-reconciled bucket; failures become GraphQL
174
+ errors.
171
175
  """
172
176
  provisionBucket(
173
177
  input: ProvisionBucketInput!
@@ -203,12 +207,12 @@ function createBucketProvisionerPlugin() {
203
207
  throw new Error('BUCKET_NOT_FOUND');
204
208
  }
205
209
  const { storageModule, bucket } = resolution;
206
- const jobId = await enqueueReconciliationJob(pgClient, storageModule, bucket);
210
+ const provisioned = await requestReconciliation(pgClient, storageModule, bucket);
207
211
  return {
208
- bucketId: bucket.id,
209
- bucketKey: bucket.key,
210
- physicalName: bucket.physical_name,
211
- jobId,
212
+ bucketId: provisioned.id,
213
+ bucketKey: provisioned.key,
214
+ physicalName: provisioned.physical_name,
215
+ provisionRequestedAt: provisioned.provision_requested_at,
212
216
  };
213
217
  });
214
218
  });
package/types.d.ts CHANGED
@@ -14,15 +14,15 @@ export interface ProvisionBucketInput {
14
14
  ownerId?: string;
15
15
  }
16
16
  /**
17
- * Result of the provisionBucket reconciliation enqueue mutation.
17
+ * Result of the provisionBucket mutation: the bucket row it stamped.
18
18
  */
19
19
  export interface ProvisionBucketPayload {
20
- /** The logical bucket row queued for reconciliation */
20
+ /** The logical bucket row whose reconciliation was requested */
21
21
  bucketId: string;
22
22
  /** The logical bucket key */
23
23
  bucketKey: string;
24
24
  /** The physical name already recorded, or null while reconciliation is pending */
25
25
  physicalName: string | null;
26
- /** The queued reconciler job */
27
- jobId: string;
26
+ /** When reconciliation was last requested */
27
+ provisionRequestedAt: string;
28
28
  }