@fedify/netlify 2.4.0-dev.0 → 2.4.0-pr.934.40
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 +18 -0
- package/dist/mod.cjs +37 -13
- package/dist/mod.d.cts +2 -0
- package/dist/mod.d.ts +2 -0
- package/dist/mod.js +37 -13
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -38,6 +38,17 @@ yarn add @fedify/netlify @netlify/async-workloads # Yarn
|
|
|
38
38
|
bun add @fedify/netlify @netlify/async-workloads # Bun
|
|
39
39
|
~~~~
|
|
40
40
|
|
|
41
|
+
The PostgreSQL-backed `orderingKv` example below also needs
|
|
42
|
+
`@fedify/postgres`, `@netlify/database`, and `postgres`:
|
|
43
|
+
|
|
44
|
+
~~~~ sh
|
|
45
|
+
deno add jsr:@fedify/postgres npm:@netlify/database npm:postgres # Deno
|
|
46
|
+
npm add @fedify/postgres @netlify/database postgres # npm
|
|
47
|
+
pnpm add @fedify/postgres @netlify/database postgres # pnpm
|
|
48
|
+
yarn add @fedify/postgres @netlify/database postgres # Yarn
|
|
49
|
+
bun add @fedify/postgres @netlify/database postgres # Bun
|
|
50
|
+
~~~~
|
|
51
|
+
|
|
41
52
|
|
|
42
53
|
Usage
|
|
43
54
|
-----
|
|
@@ -137,6 +148,13 @@ table by default; do not pass `unlogged: true` for `orderingKv`.
|
|
|
137
148
|
Netlify limits an event payload to 500 KB. Fedify messages, including any
|
|
138
149
|
embedded activity, must remain below that limit.
|
|
139
150
|
|
|
151
|
+
`enqueueMany()` sends one Async Workloads event per message because Netlify's
|
|
152
|
+
public client has no batch operation. The queue therefore declares
|
|
153
|
+
`atomicEnqueueMany` as `false`: ordinary batches still send concurrently, but
|
|
154
|
+
Fedify rejects a multi-message `enqueueTaskMany()` call with one
|
|
155
|
+
`deduplicationKey` before sending anything. Such a key requires an atomic
|
|
156
|
+
batch enqueue so a failed partial send cannot produce duplicates on retry.
|
|
157
|
+
|
|
140
158
|
See the [deployment manual] and the [Netlify Astro example] for a complete
|
|
141
159
|
setup with Netlify Database.
|
|
142
160
|
|
package/dist/mod.cjs
CHANGED
|
@@ -3617,11 +3617,12 @@ function getOrderingKv(queue, orderingKey) {
|
|
|
3617
3617
|
if (orderingKey.length < 1) throw new TypeError("orderingKey must not be empty.");
|
|
3618
3618
|
return requireCas(getOrderingOptions(queue).kv);
|
|
3619
3619
|
}
|
|
3620
|
-
function getOrderingStateKey(orderingKey) {
|
|
3620
|
+
function getOrderingStateKey(queue, orderingKey) {
|
|
3621
3621
|
return [
|
|
3622
3622
|
"fedify",
|
|
3623
3623
|
"netlify",
|
|
3624
3624
|
"ordering",
|
|
3625
|
+
queue.eventName.toLowerCase(),
|
|
3625
3626
|
orderingKey
|
|
3626
3627
|
];
|
|
3627
3628
|
}
|
|
@@ -3654,10 +3655,11 @@ function validateOrderingState(value) {
|
|
|
3654
3655
|
}
|
|
3655
3656
|
async function updateOrderingState(queue, orderingKey, update) {
|
|
3656
3657
|
const kv = getOrderingKv(queue, orderingKey);
|
|
3657
|
-
const key = getOrderingStateKey(orderingKey);
|
|
3658
|
+
const key = getOrderingStateKey(queue, orderingKey);
|
|
3658
3659
|
while (true) {
|
|
3659
3660
|
const stored = await kv.get(key);
|
|
3660
3661
|
const [updated, result] = update(validateOrderingState(stored));
|
|
3662
|
+
if (updated === void 0) return result;
|
|
3661
3663
|
if (await kv.cas(key, stored, updated)) return result;
|
|
3662
3664
|
}
|
|
3663
3665
|
}
|
|
@@ -3670,7 +3672,7 @@ async function reserveOrderingSequence(queue, orderingKey) {
|
|
|
3670
3672
|
async function cancelOrderingSequence(queue, orderingKey, sequence) {
|
|
3671
3673
|
await updateOrderingState(queue, orderingKey, (state) => {
|
|
3672
3674
|
if (sequence >= state.nextSequence) throw new RangeError(`Netlify queue ordering sequence ${sequence} has not been reserved.`);
|
|
3673
|
-
if (sequence <= state.completedSequence) return [
|
|
3675
|
+
if (sequence <= state.completedSequence) return [void 0, void 0];
|
|
3674
3676
|
return [compactOrderingState({
|
|
3675
3677
|
...state,
|
|
3676
3678
|
cancelledSequences: [...state.cancelledSequences, sequence]
|
|
@@ -3679,12 +3681,12 @@ async function cancelOrderingSequence(queue, orderingKey, sequence) {
|
|
|
3679
3681
|
}
|
|
3680
3682
|
/** Gets the last completed sequence for an ordering key. @internal */
|
|
3681
3683
|
async function getCompletedOrderingSequence(queue, orderingKey) {
|
|
3682
|
-
return validateOrderingState(await getOrderingKv(queue, orderingKey).get(getOrderingStateKey(orderingKey))).completedSequence;
|
|
3684
|
+
return validateOrderingState(await getOrderingKv(queue, orderingKey).get(getOrderingStateKey(queue, orderingKey))).completedSequence;
|
|
3683
3685
|
}
|
|
3684
3686
|
/** Marks an ordered message as completed. @internal */
|
|
3685
3687
|
async function completeOrderingSequence(queue, orderingKey, sequence) {
|
|
3686
3688
|
await updateOrderingState(queue, orderingKey, (state) => {
|
|
3687
|
-
if (state.completedSequence >= sequence) return [
|
|
3689
|
+
if (state.completedSequence >= sequence) return [void 0, void 0];
|
|
3688
3690
|
if (state.completedSequence !== sequence - 1) throw new Error(`Cannot complete Netlify queue ordering sequence ${sequence} after ${state.completedSequence}.`);
|
|
3689
3691
|
return [compactOrderingState({
|
|
3690
3692
|
...state,
|
|
@@ -3706,6 +3708,8 @@ var NetlifyMessageQueue = class {
|
|
|
3706
3708
|
eventName;
|
|
3707
3709
|
nativeRetrial = true;
|
|
3708
3710
|
nativeDeduplication = false;
|
|
3711
|
+
/** {@inheritDoc MessageQueue.atomicEnqueueMany} */
|
|
3712
|
+
atomicEnqueueMany = false;
|
|
3709
3713
|
#client;
|
|
3710
3714
|
/** Creates a Netlify Async Workloads-backed message queue. */
|
|
3711
3715
|
constructor(options) {
|
|
@@ -3787,19 +3791,25 @@ const messageTypes = new Set([
|
|
|
3787
3791
|
function isObject(value) {
|
|
3788
3792
|
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
3789
3793
|
}
|
|
3790
|
-
function
|
|
3791
|
-
if (!isObject(value)
|
|
3792
|
-
if (typeof value.message.type !== "string" || !messageTypes.has(value.message.type)) throw new _netlify_async_workloads.ErrorDoNotRetry("Invalid Fedify queue message type.");
|
|
3794
|
+
function decodeOrderingMetadata(value) {
|
|
3795
|
+
if (!isObject(value)) throw new _netlify_async_workloads.ErrorDoNotRetry("Invalid Fedify queue event envelope.");
|
|
3793
3796
|
if (value.orderingKey !== void 0 && (typeof value.orderingKey !== "string" || value.orderingKey.length < 1)) throw new _netlify_async_workloads.ErrorDoNotRetry("Invalid Fedify queue ordering key.");
|
|
3794
3797
|
const orderingSequence = value.orderingSequence;
|
|
3795
3798
|
if (orderingSequence !== void 0 && (typeof orderingSequence !== "number" || !Number.isSafeInteger(orderingSequence) || orderingSequence < 1)) throw new _netlify_async_workloads.ErrorDoNotRetry("Invalid Fedify queue ordering sequence.");
|
|
3796
3799
|
if (value.orderingKey === void 0 !== (orderingSequence === void 0)) throw new _netlify_async_workloads.ErrorDoNotRetry("Incomplete Fedify queue ordering metadata.");
|
|
3797
3800
|
return {
|
|
3798
|
-
message: value.message,
|
|
3799
3801
|
orderingKey: value.orderingKey,
|
|
3800
3802
|
orderingSequence
|
|
3801
3803
|
};
|
|
3802
3804
|
}
|
|
3805
|
+
function decodeEventData(value, ordering) {
|
|
3806
|
+
if (!isObject(value) || !isObject(value.message)) throw new _netlify_async_workloads.ErrorDoNotRetry("Invalid Fedify queue event envelope.");
|
|
3807
|
+
if (typeof value.message.type !== "string" || !messageTypes.has(value.message.type)) throw new _netlify_async_workloads.ErrorDoNotRetry("Invalid Fedify queue message type.");
|
|
3808
|
+
return {
|
|
3809
|
+
message: value.message,
|
|
3810
|
+
...ordering
|
|
3811
|
+
};
|
|
3812
|
+
}
|
|
3803
3813
|
/**
|
|
3804
3814
|
* Creates the event callback that processes one Netlify queue event.
|
|
3805
3815
|
* @internal
|
|
@@ -3808,7 +3818,14 @@ function createNetlifyQueueEventHandler(options) {
|
|
|
3808
3818
|
const maxRetries = options.maxRetries ?? 4;
|
|
3809
3819
|
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0) throw new RangeError("maxRetries must be a non-negative safe integer.");
|
|
3810
3820
|
return async (event) => {
|
|
3811
|
-
const
|
|
3821
|
+
const orderingMetadata = decodeOrderingMetadata(event.eventData);
|
|
3822
|
+
let data;
|
|
3823
|
+
try {
|
|
3824
|
+
data = decodeEventData(event.eventData, orderingMetadata);
|
|
3825
|
+
} catch (error) {
|
|
3826
|
+
if (error instanceof _netlify_async_workloads.ErrorDoNotRetry && orderingMetadata.orderingKey != null && orderingMetadata.orderingSequence != null) await options.queue.skipOrderingSequence(orderingMetadata.orderingKey, orderingMetadata.orderingSequence);
|
|
3827
|
+
throw error;
|
|
3828
|
+
}
|
|
3812
3829
|
const ordering = getOrderingOptions(options.queue);
|
|
3813
3830
|
if (data.orderingKey != null && data.orderingSequence != null) {
|
|
3814
3831
|
let wait = 0;
|
|
@@ -3820,9 +3837,16 @@ function createNetlifyQueueEventHandler(options) {
|
|
|
3820
3837
|
}
|
|
3821
3838
|
}
|
|
3822
3839
|
try {
|
|
3823
|
-
const
|
|
3824
|
-
|
|
3825
|
-
|
|
3840
|
+
const process = async () => {
|
|
3841
|
+
const federation = await options.federation(event);
|
|
3842
|
+
const contextData = options.contextData == null ? void 0 : await options.contextData(event);
|
|
3843
|
+
await federation.processQueuedTask(contextData, data.message);
|
|
3844
|
+
};
|
|
3845
|
+
if (data.orderingKey != null && data.orderingSequence != null) await event.step.run("fedify-process-queued-task", async () => {
|
|
3846
|
+
await process();
|
|
3847
|
+
return null;
|
|
3848
|
+
});
|
|
3849
|
+
else await process();
|
|
3826
3850
|
} catch (error) {
|
|
3827
3851
|
if (data.orderingKey != null && data.orderingSequence != null && (error instanceof _netlify_async_workloads.ErrorDoNotRetry || event.attempt >= maxRetries)) await options.queue.skipOrderingSequence(data.orderingKey, data.orderingSequence);
|
|
3828
3852
|
throw error;
|
package/dist/mod.d.cts
CHANGED
|
@@ -98,6 +98,8 @@ declare class NetlifyMessageQueue implements MessageQueue {
|
|
|
98
98
|
readonly eventName: string;
|
|
99
99
|
readonly nativeRetrial = true;
|
|
100
100
|
readonly nativeDeduplication = false;
|
|
101
|
+
/** {@inheritDoc MessageQueue.atomicEnqueueMany} */
|
|
102
|
+
readonly atomicEnqueueMany = false;
|
|
101
103
|
/** Creates a Netlify Async Workloads-backed message queue. */
|
|
102
104
|
constructor(options: NetlifyMessageQueueOptions);
|
|
103
105
|
/** {@inheritDoc MessageQueue.enqueue} */
|
package/dist/mod.d.ts
CHANGED
|
@@ -98,6 +98,8 @@ declare class NetlifyMessageQueue implements MessageQueue {
|
|
|
98
98
|
readonly eventName: string;
|
|
99
99
|
readonly nativeRetrial = true;
|
|
100
100
|
readonly nativeDeduplication = false;
|
|
101
|
+
/** {@inheritDoc MessageQueue.atomicEnqueueMany} */
|
|
102
|
+
readonly atomicEnqueueMany = false;
|
|
101
103
|
/** Creates a Netlify Async Workloads-backed message queue. */
|
|
102
104
|
constructor(options: NetlifyMessageQueueOptions);
|
|
103
105
|
/** {@inheritDoc MessageQueue.enqueue} */
|
package/dist/mod.js
CHANGED
|
@@ -57,11 +57,12 @@ function getOrderingKv(queue, orderingKey) {
|
|
|
57
57
|
if (orderingKey.length < 1) throw new TypeError("orderingKey must not be empty.");
|
|
58
58
|
return requireCas(getOrderingOptions(queue).kv);
|
|
59
59
|
}
|
|
60
|
-
function getOrderingStateKey(orderingKey) {
|
|
60
|
+
function getOrderingStateKey(queue, orderingKey) {
|
|
61
61
|
return [
|
|
62
62
|
"fedify",
|
|
63
63
|
"netlify",
|
|
64
64
|
"ordering",
|
|
65
|
+
queue.eventName.toLowerCase(),
|
|
65
66
|
orderingKey
|
|
66
67
|
];
|
|
67
68
|
}
|
|
@@ -94,10 +95,11 @@ function validateOrderingState(value) {
|
|
|
94
95
|
}
|
|
95
96
|
async function updateOrderingState(queue, orderingKey, update) {
|
|
96
97
|
const kv = getOrderingKv(queue, orderingKey);
|
|
97
|
-
const key = getOrderingStateKey(orderingKey);
|
|
98
|
+
const key = getOrderingStateKey(queue, orderingKey);
|
|
98
99
|
while (true) {
|
|
99
100
|
const stored = await kv.get(key);
|
|
100
101
|
const [updated, result] = update(validateOrderingState(stored));
|
|
102
|
+
if (updated === void 0) return result;
|
|
101
103
|
if (await kv.cas(key, stored, updated)) return result;
|
|
102
104
|
}
|
|
103
105
|
}
|
|
@@ -110,7 +112,7 @@ async function reserveOrderingSequence(queue, orderingKey) {
|
|
|
110
112
|
async function cancelOrderingSequence(queue, orderingKey, sequence) {
|
|
111
113
|
await updateOrderingState(queue, orderingKey, (state) => {
|
|
112
114
|
if (sequence >= state.nextSequence) throw new RangeError(`Netlify queue ordering sequence ${sequence} has not been reserved.`);
|
|
113
|
-
if (sequence <= state.completedSequence) return [
|
|
115
|
+
if (sequence <= state.completedSequence) return [void 0, void 0];
|
|
114
116
|
return [compactOrderingState({
|
|
115
117
|
...state,
|
|
116
118
|
cancelledSequences: [...state.cancelledSequences, sequence]
|
|
@@ -119,12 +121,12 @@ async function cancelOrderingSequence(queue, orderingKey, sequence) {
|
|
|
119
121
|
}
|
|
120
122
|
/** Gets the last completed sequence for an ordering key. @internal */
|
|
121
123
|
async function getCompletedOrderingSequence(queue, orderingKey) {
|
|
122
|
-
return validateOrderingState(await getOrderingKv(queue, orderingKey).get(getOrderingStateKey(orderingKey))).completedSequence;
|
|
124
|
+
return validateOrderingState(await getOrderingKv(queue, orderingKey).get(getOrderingStateKey(queue, orderingKey))).completedSequence;
|
|
123
125
|
}
|
|
124
126
|
/** Marks an ordered message as completed. @internal */
|
|
125
127
|
async function completeOrderingSequence(queue, orderingKey, sequence) {
|
|
126
128
|
await updateOrderingState(queue, orderingKey, (state) => {
|
|
127
|
-
if (state.completedSequence >= sequence) return [
|
|
129
|
+
if (state.completedSequence >= sequence) return [void 0, void 0];
|
|
128
130
|
if (state.completedSequence !== sequence - 1) throw new Error(`Cannot complete Netlify queue ordering sequence ${sequence} after ${state.completedSequence}.`);
|
|
129
131
|
return [compactOrderingState({
|
|
130
132
|
...state,
|
|
@@ -146,6 +148,8 @@ var NetlifyMessageQueue = class {
|
|
|
146
148
|
eventName;
|
|
147
149
|
nativeRetrial = true;
|
|
148
150
|
nativeDeduplication = false;
|
|
151
|
+
/** {@inheritDoc MessageQueue.atomicEnqueueMany} */
|
|
152
|
+
atomicEnqueueMany = false;
|
|
149
153
|
#client;
|
|
150
154
|
/** Creates a Netlify Async Workloads-backed message queue. */
|
|
151
155
|
constructor(options) {
|
|
@@ -227,19 +231,25 @@ const messageTypes = new Set([
|
|
|
227
231
|
function isObject(value) {
|
|
228
232
|
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
229
233
|
}
|
|
230
|
-
function
|
|
231
|
-
if (!isObject(value)
|
|
232
|
-
if (typeof value.message.type !== "string" || !messageTypes.has(value.message.type)) throw new ErrorDoNotRetry("Invalid Fedify queue message type.");
|
|
234
|
+
function decodeOrderingMetadata(value) {
|
|
235
|
+
if (!isObject(value)) throw new ErrorDoNotRetry("Invalid Fedify queue event envelope.");
|
|
233
236
|
if (value.orderingKey !== void 0 && (typeof value.orderingKey !== "string" || value.orderingKey.length < 1)) throw new ErrorDoNotRetry("Invalid Fedify queue ordering key.");
|
|
234
237
|
const orderingSequence = value.orderingSequence;
|
|
235
238
|
if (orderingSequence !== void 0 && (typeof orderingSequence !== "number" || !Number.isSafeInteger(orderingSequence) || orderingSequence < 1)) throw new ErrorDoNotRetry("Invalid Fedify queue ordering sequence.");
|
|
236
239
|
if (value.orderingKey === void 0 !== (orderingSequence === void 0)) throw new ErrorDoNotRetry("Incomplete Fedify queue ordering metadata.");
|
|
237
240
|
return {
|
|
238
|
-
message: value.message,
|
|
239
241
|
orderingKey: value.orderingKey,
|
|
240
242
|
orderingSequence
|
|
241
243
|
};
|
|
242
244
|
}
|
|
245
|
+
function decodeEventData(value, ordering) {
|
|
246
|
+
if (!isObject(value) || !isObject(value.message)) throw new ErrorDoNotRetry("Invalid Fedify queue event envelope.");
|
|
247
|
+
if (typeof value.message.type !== "string" || !messageTypes.has(value.message.type)) throw new ErrorDoNotRetry("Invalid Fedify queue message type.");
|
|
248
|
+
return {
|
|
249
|
+
message: value.message,
|
|
250
|
+
...ordering
|
|
251
|
+
};
|
|
252
|
+
}
|
|
243
253
|
/**
|
|
244
254
|
* Creates the event callback that processes one Netlify queue event.
|
|
245
255
|
* @internal
|
|
@@ -248,7 +258,14 @@ function createNetlifyQueueEventHandler(options) {
|
|
|
248
258
|
const maxRetries = options.maxRetries ?? 4;
|
|
249
259
|
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0) throw new RangeError("maxRetries must be a non-negative safe integer.");
|
|
250
260
|
return async (event) => {
|
|
251
|
-
const
|
|
261
|
+
const orderingMetadata = decodeOrderingMetadata(event.eventData);
|
|
262
|
+
let data;
|
|
263
|
+
try {
|
|
264
|
+
data = decodeEventData(event.eventData, orderingMetadata);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error instanceof ErrorDoNotRetry && orderingMetadata.orderingKey != null && orderingMetadata.orderingSequence != null) await options.queue.skipOrderingSequence(orderingMetadata.orderingKey, orderingMetadata.orderingSequence);
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
252
269
|
const ordering = getOrderingOptions(options.queue);
|
|
253
270
|
if (data.orderingKey != null && data.orderingSequence != null) {
|
|
254
271
|
let wait = 0;
|
|
@@ -260,9 +277,16 @@ function createNetlifyQueueEventHandler(options) {
|
|
|
260
277
|
}
|
|
261
278
|
}
|
|
262
279
|
try {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
280
|
+
const process = async () => {
|
|
281
|
+
const federation = await options.federation(event);
|
|
282
|
+
const contextData = options.contextData == null ? void 0 : await options.contextData(event);
|
|
283
|
+
await federation.processQueuedTask(contextData, data.message);
|
|
284
|
+
};
|
|
285
|
+
if (data.orderingKey != null && data.orderingSequence != null) await event.step.run("fedify-process-queued-task", async () => {
|
|
286
|
+
await process();
|
|
287
|
+
return null;
|
|
288
|
+
});
|
|
289
|
+
else await process();
|
|
266
290
|
} catch (error) {
|
|
267
291
|
if (data.orderingKey != null && data.orderingSequence != null && (error instanceof ErrorDoNotRetry || event.attempt >= maxRetries)) await options.queue.skipOrderingSequence(data.orderingKey, data.orderingSequence);
|
|
268
292
|
throw error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/netlify",
|
|
3
|
-
"version": "2.4.0-
|
|
3
|
+
"version": "2.4.0-pr.934.40+3331d302",
|
|
4
4
|
"description": "Integrate Fedify with Netlify Async Workloads",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Fedify",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@netlify/async-workloads": "^0.0.106",
|
|
54
|
-
"@fedify/fedify": "^2.4.0"
|
|
54
|
+
"@fedify/fedify": "^2.4.0-pr.934.40+3331d302"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@js-temporal/polyfill": "^0.5.1",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"postgres": "^3.4.7",
|
|
63
63
|
"tsdown": "^0.22.0",
|
|
64
64
|
"typescript": "^6.0.0",
|
|
65
|
-
"@fedify/postgres": "^2.4.0"
|
|
65
|
+
"@fedify/postgres": "^2.4.0-pr.934.40+3331d302"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build:self": "tsdown",
|