@fedify/netlify 2.4.0-dev.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/LICENSE +20 -0
- package/README.md +169 -0
- package/dist/mod.cjs +3859 -0
- package/dist/mod.d.cts +177 -0
- package/dist/mod.d.ts +177 -0
- package/dist/mod.js +297 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2024–2026 Hong Minhee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<!-- deno-fmt-ignore-file -->
|
|
2
|
+
|
|
3
|
+
@fedify/netlify: Run Fedify queues with Netlify Async Workloads
|
|
4
|
+
===============================================================
|
|
5
|
+
|
|
6
|
+
[![JSR][JSR badge]][JSR]
|
|
7
|
+
[![npm][npm badge]][npm]
|
|
8
|
+
[![@fedify@hackers.pub][@fedify@hackers.pub badge]][@fedify@hackers.pub]
|
|
9
|
+
|
|
10
|
+
*This package is available since Fedify 2.4.0.*
|
|
11
|
+
|
|
12
|
+
This package connects [Fedify]'s [`MessageQueue`] API to
|
|
13
|
+
[Netlify Async Workloads]. `NetlifyMessageQueue` publishes durable events,
|
|
14
|
+
while `createNetlifyQueueHandler()` turns a Netlify Function into their
|
|
15
|
+
consumer.
|
|
16
|
+
|
|
17
|
+
The initial release targets Netlify Functions, not Netlify Edge Functions.
|
|
18
|
+
|
|
19
|
+
[JSR badge]: https://jsr.io/badges/@fedify/netlify
|
|
20
|
+
[JSR]: https://jsr.io/@fedify/netlify
|
|
21
|
+
[npm badge]: https://img.shields.io/npm/v/@fedify/netlify?logo=npm
|
|
22
|
+
[npm]: https://www.npmjs.com/package/@fedify/netlify
|
|
23
|
+
[@fedify@hackers.pub badge]: https://fedi-badge.minhee.org/@fedify@hackers.pub/followers.svg
|
|
24
|
+
[@fedify@hackers.pub]: https://hackers.pub/@fedify
|
|
25
|
+
[Fedify]: https://fedify.dev/
|
|
26
|
+
[`MessageQueue`]: https://jsr.io/@fedify/fedify/doc/federation/~/MessageQueue
|
|
27
|
+
[Netlify Async Workloads]: https://docs.netlify.com/build/async-workloads/get-started/
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
Installation
|
|
31
|
+
------------
|
|
32
|
+
|
|
33
|
+
~~~~ sh
|
|
34
|
+
deno add jsr:@fedify/netlify npm:@netlify/async-workloads # Deno
|
|
35
|
+
npm add @fedify/netlify @netlify/async-workloads # npm
|
|
36
|
+
pnpm add @fedify/netlify @netlify/async-workloads # pnpm
|
|
37
|
+
yarn add @fedify/netlify @netlify/async-workloads # Yarn
|
|
38
|
+
bun add @fedify/netlify @netlify/async-workloads # Bun
|
|
39
|
+
~~~~
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Usage
|
|
43
|
+
-----
|
|
44
|
+
|
|
45
|
+
Create one queue for both the web application and the workload function. A
|
|
46
|
+
CAS-capable `KvStore`, such as `PostgresKvStore`, is required if Fedify emits
|
|
47
|
+
messages with an `orderingKey`:
|
|
48
|
+
|
|
49
|
+
~~~~ typescript
|
|
50
|
+
import { AsyncWorkloadsClient } from "@netlify/async-workloads";
|
|
51
|
+
import { getConnectionString } from "@netlify/database";
|
|
52
|
+
import { NetlifyMessageQueue } from "@fedify/netlify";
|
|
53
|
+
import { PostgresKvStore } from "@fedify/postgres";
|
|
54
|
+
import postgres from "postgres";
|
|
55
|
+
|
|
56
|
+
const sql = postgres(getConnectionString());
|
|
57
|
+
export const kv = new PostgresKvStore(sql);
|
|
58
|
+
export const queue = new NetlifyMessageQueue({
|
|
59
|
+
client: new AsyncWorkloadsClient(),
|
|
60
|
+
orderingKv: kv,
|
|
61
|
+
});
|
|
62
|
+
~~~~
|
|
63
|
+
|
|
64
|
+
Pass the queue to Fedify with `manuallyStartQueue: true`. Async Workloads
|
|
65
|
+
invokes the consumer, so `NetlifyMessageQueue.listen()` is intentionally not
|
|
66
|
+
available:
|
|
67
|
+
|
|
68
|
+
~~~~ typescript
|
|
69
|
+
const federation = await builder.build({
|
|
70
|
+
kv,
|
|
71
|
+
queue,
|
|
72
|
+
manuallyStartQueue: true,
|
|
73
|
+
});
|
|
74
|
+
~~~~
|
|
75
|
+
|
|
76
|
+
Then export the consumer from a file under *netlify/functions/*:
|
|
77
|
+
|
|
78
|
+
~~~~ typescript
|
|
79
|
+
import type { AsyncWorkloadConfig } from "@netlify/async-workloads";
|
|
80
|
+
import { createNetlifyQueueHandler } from "@fedify/netlify";
|
|
81
|
+
import { builder, kv, queue } from "../../src/federation.ts";
|
|
82
|
+
|
|
83
|
+
export default createNetlifyQueueHandler({
|
|
84
|
+
queue,
|
|
85
|
+
maxRetries: 4,
|
|
86
|
+
federation: () => builder.build({
|
|
87
|
+
kv,
|
|
88
|
+
queue,
|
|
89
|
+
manuallyStartQueue: true,
|
|
90
|
+
}),
|
|
91
|
+
contextData: (event) => ({
|
|
92
|
+
deployId: event.request.headers.get("x-nf-deploy-id"),
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const asyncWorkloadConfig: AsyncWorkloadConfig = {
|
|
97
|
+
events: [queue.eventName],
|
|
98
|
+
maxRetries: 4,
|
|
99
|
+
};
|
|
100
|
+
~~~~
|
|
101
|
+
|
|
102
|
+
Async Workloads retries exceptions raised by `processQueuedTask()` and moves
|
|
103
|
+
exhausted events to its dead-letter store. Malformed envelopes are marked as
|
|
104
|
+
non-retryable. Configure retry count and backoff in `asyncWorkloadConfig`.
|
|
105
|
+
|
|
106
|
+
For each `orderingKey`, the producer reserves a monotonic sequence in
|
|
107
|
+
`orderingKv`. A consumer whose predecessor is still running waits with Async
|
|
108
|
+
Workloads' durable `step.sleep()` rather than throwing a retryable error. This
|
|
109
|
+
preserves FIFO order without consuming `maxRetries`, and a long-running task
|
|
110
|
+
continues to exclude later tasks without relying on an expiring lock. The
|
|
111
|
+
`orderingRetryDelay` option controls the durable sleep interval.
|
|
112
|
+
|
|
113
|
+
Set the handler's `maxRetries` to the same value as
|
|
114
|
+
`asyncWorkloadConfig.maxRetries`. When `processQueuedTask()` throws on its
|
|
115
|
+
last configured attempt, the handler releases the failed sequence before the
|
|
116
|
+
event is dead-lettered, allowing later messages to continue. A Function
|
|
117
|
+
timeout or abrupt process termination cannot run this cleanup. After
|
|
118
|
+
confirming that such an event is permanently dead-lettered, advance the queue
|
|
119
|
+
explicitly using the ordering metadata stored in the dead-lettered event:
|
|
120
|
+
|
|
121
|
+
~~~~ typescript
|
|
122
|
+
await queue.skipOrderingSequence(
|
|
123
|
+
event.eventData.orderingKey,
|
|
124
|
+
event.eventData.orderingSequence,
|
|
125
|
+
);
|
|
126
|
+
~~~~
|
|
127
|
+
|
|
128
|
+
Do not skip an event that may still be delivered or retried. An unacknowledged
|
|
129
|
+
send throws `NetlifyMessageQueueSendError`; its sequence remains reserved
|
|
130
|
+
because a lost response does not prove that the router rejected the event.
|
|
131
|
+
Use the error's `orderingKey` and `orderingSequence` for manual recovery only
|
|
132
|
+
after ruling out delivery.
|
|
133
|
+
|
|
134
|
+
Ordering state must use crash-safe storage. `PostgresKvStore` creates a logged
|
|
135
|
+
table by default; do not pass `unlogged: true` for `orderingKv`.
|
|
136
|
+
|
|
137
|
+
Netlify limits an event payload to 500 KB. Fedify messages, including any
|
|
138
|
+
embedded activity, must remain below that limit.
|
|
139
|
+
|
|
140
|
+
See the [deployment manual] and the [Netlify Astro example] for a complete
|
|
141
|
+
setup with Netlify Database.
|
|
142
|
+
|
|
143
|
+
[deployment manual]: https://fedify.dev/manual/deploy#netlify-functions
|
|
144
|
+
[Netlify Astro example]: https://github.com/fedify-dev/fedify/tree/main/examples/netlify-astro
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
Integration testing
|
|
148
|
+
-------------------
|
|
149
|
+
|
|
150
|
+
The repository includes a Netlify Dev integration test that exercises real
|
|
151
|
+
Async Workloads delivery, retries, and ordering. To enable it, create an
|
|
152
|
+
otherwise empty Netlify project, install the Async Workloads extension on it,
|
|
153
|
+
and put these values in the repository root's untracked *.env* file:
|
|
154
|
+
|
|
155
|
+
~~~~ dotenv
|
|
156
|
+
NETLIFY_AUTH_TOKEN=your-personal-access-token
|
|
157
|
+
NETLIFY_SITE_ID=your-project-id
|
|
158
|
+
~~~~
|
|
159
|
+
|
|
160
|
+
Then run:
|
|
161
|
+
|
|
162
|
+
~~~~ sh
|
|
163
|
+
mise run test-each netlify
|
|
164
|
+
~~~~
|
|
165
|
+
|
|
166
|
+
The test generates a temporary `AWL_API_KEY` for Netlify Dev, so it must not be
|
|
167
|
+
added to *.env*. When either required variable is absent, the integration test
|
|
168
|
+
is skipped automatically. Deno and Bun also skip this Node.js-only Netlify Dev
|
|
169
|
+
test while continuing to run the package's portable unit tests.
|