@empyria/restate 0.4.8 → 0.4.10
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 +8 -0
- package/index.js +2 -0
- package/lib/Endpoint.js +28 -0
- package/package.json +2 -2
- package/test/Endpoint.test.js +34 -0
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ import { checkServiceHandler } from '@empyria/restate/lib/Admin.js'
|
|
|
50
50
|
| [lib/Cron.js](./lib/Cron.js) | `cronJobInitiator`/`cronJob` — Restate services implementing cron-driven, replay-safe recurring job scheduling on top of `croner`. |
|
|
51
51
|
| [lib/Pubsub.js](./lib/Pubsub.js) | `definePubsub` (register a pubsub Virtual Object on your endpoint), `pubsubPublisher` (publish into it from inside a handler, in-process), `pubsubClient` (publish/pull/subscribe to it from anywhere else, over the network) — thin wrappers around `@restatedev/pubsub`/`@restatedev/pubsub-client`. |
|
|
52
52
|
| [lib/Validation.js](./lib/Validation.js) | `withValidation` — wraps a workflow handler with input/output JSON schema validation via `@empyria/common`. |
|
|
53
|
+
| [lib/Endpoint.js](./lib/Endpoint.js) | `serveEndpoint` — serves services/workflows over HTTP/2 without registering itself or deleting its deployment on exit (for a supervisor that registers the address). |
|
|
53
54
|
|
|
54
55
|
## Agents: `@empyria/restate/agent`
|
|
55
56
|
|
|
@@ -121,6 +122,13 @@ endpoint, then create jobs with either:
|
|
|
121
122
|
Cron expressions are `croner` patterns (five fields, or six with seconds for tests). The target handler is sent the
|
|
122
123
|
`payload` as its input, or no input when there is none.
|
|
123
124
|
|
|
125
|
+
### An endpoint that does not register itself
|
|
126
|
+
|
|
127
|
+
`setupRestate` serves, registers the deployment, and deletes it again on SIGINT/SIGTERM. When something else owns
|
|
128
|
+
registration (a supervisor that registers a process's address once and restarts the process), use
|
|
129
|
+
`serveEndpoint({port, services})` instead: it only serves. Invocations already running on a deployment are pinned to its
|
|
130
|
+
address, so the deployment must survive the process stopping and come back at the same address.
|
|
131
|
+
|
|
124
132
|
### Shaping an agent
|
|
125
133
|
|
|
126
134
|
`defineAgent` options beyond the basics:
|
package/index.js
CHANGED
package/lib/Endpoint.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import http2 from 'node:http2'
|
|
2
|
+
import * as restate from '@restatedev/restate-sdk'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An endpoint that only serves: the given services/workflows over HTTP/2 on `port`, and nothing else.
|
|
6
|
+
* Unlike `setupRestate` it does not register with Restate and does not delete its deployment when it stops. Use it
|
|
7
|
+
* when something else owns registration, for example a supervisor that registers the address once and restarts the
|
|
8
|
+
* process: workflows already running on a deployment are pinned to its address and must be able to wait for it to
|
|
9
|
+
* come back. It stops accepting connections on SIGINT/SIGTERM.
|
|
10
|
+
*
|
|
11
|
+
* @param {{port: number, services: Parameters<typeof restate.createEndpointHandler>[0]['services']}} params
|
|
12
|
+
* @returns {Promise<import('node:http2').Http2Server>} resolves once it is listening
|
|
13
|
+
*/
|
|
14
|
+
export async function serveEndpoint({ port, services }) {
|
|
15
|
+
const server = http2.createServer(restate.createEndpointHandler({ services }))
|
|
16
|
+
await new Promise((resolve, reject) => {
|
|
17
|
+
server.once('error', reject)
|
|
18
|
+
server.listen(port, resolve)
|
|
19
|
+
})
|
|
20
|
+
// Open sessions from Restate would keep close() waiting; in-flight calls are retried by Restate anyway.
|
|
21
|
+
const stop = () => {
|
|
22
|
+
server.close()
|
|
23
|
+
setTimeout(() => process.exit(0), 1000)
|
|
24
|
+
}
|
|
25
|
+
process.once('SIGINT', stop)
|
|
26
|
+
process.once('SIGTERM', stop)
|
|
27
|
+
return server
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empyria/restate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "Restate.dev helpers for the Empyria nanoservice framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Imre Fazekas <imre.fazekas@icloud.com>",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@empyria/classification": "0.1.9",
|
|
30
|
-
"@empyria/common": "0.3.
|
|
30
|
+
"@empyria/common": "0.3.10",
|
|
31
31
|
"@restatedev/pubsub": "0.3.2",
|
|
32
32
|
"@restatedev/pubsub-client": "0.3.2",
|
|
33
33
|
"@restatedev/restate-sdk": "1.17.2",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import http2 from 'node:http2'
|
|
3
|
+
import * as restate from '@restatedev/restate-sdk'
|
|
4
|
+
import { serveEndpoint } from '../lib/Endpoint.js'
|
|
5
|
+
|
|
6
|
+
describe('serveEndpoint', () => {
|
|
7
|
+
test('listens over HTTP/2 and answers Restate discovery, without registering anywhere', async () => {
|
|
8
|
+
const hello = restate.service({ name: 'Hello', handlers: { hi: async () => 'hi' } })
|
|
9
|
+
const server = await serveEndpoint({ port: 0, services: [hello] })
|
|
10
|
+
const { port } = server.address()
|
|
11
|
+
const client = http2.connect(`http://127.0.0.1:${port}`)
|
|
12
|
+
const body = await new Promise((resolve, reject) => {
|
|
13
|
+
const req = client.request({
|
|
14
|
+
':path': '/discover',
|
|
15
|
+
accept: 'application/vnd.restate.endpointmanifest.v3+json',
|
|
16
|
+
})
|
|
17
|
+
let text = ''
|
|
18
|
+
req.on('data', (chunk) => (text += chunk))
|
|
19
|
+
req.on('end', () => resolve(text))
|
|
20
|
+
req.on('error', reject)
|
|
21
|
+
req.end()
|
|
22
|
+
})
|
|
23
|
+
client.close()
|
|
24
|
+
server.close()
|
|
25
|
+
expect(JSON.parse(body).services.map((s) => s.name)).toEqual(['Hello'])
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('fails when the port is taken', async () => {
|
|
29
|
+
const first = await serveEndpoint({ port: 0, services: [] })
|
|
30
|
+
const { port } = first.address()
|
|
31
|
+
await expect(serveEndpoint({ port, services: [] })).rejects.toThrow()
|
|
32
|
+
first.close()
|
|
33
|
+
})
|
|
34
|
+
})
|