@empyria/restate 0.4.15 → 0.4.17

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/AGENTS.md CHANGED
@@ -79,6 +79,12 @@ may observe an already-mocked value. See `test/Admin.test.js`.
79
79
  same address registers again, but only `force: true` updates its handler list. Check both against a live server when
80
80
  touching them.
81
81
 
82
+ `setupRestate` registers the single complete `serviceURL` it is given (scheme included — never assemble a URL from
83
+ host + port, the scheme and any proxy mapping are the caller's to state). `port` is only the listen port. `host` is a
84
+ deprecated fallback (`http://host:port`) kept for consumers pinned to older call sites; the URL decision is
85
+ `resolveServiceURL` (unit tested). `createRestateAdmin().setupRestate` must forward every option to `setupRestate` — it
86
+ once dropped `keepDeploymentOnShutdown`/`forceRegistration`/`registrationCallbackFn`.
87
+
82
88
  `setupRestate` (in `lib/Admin.js`) is not unit tested: it binds real HTTP/2 + health-check
83
89
  ports, installs process-wide `SIGTERM`/`SIGINT` handlers, and its returned `forceClose` calls
84
90
  `process.exit()` — none of which are safe to exercise in-process in a test run.
package/README.md CHANGED
@@ -124,6 +124,11 @@ Cron expressions are `croner` patterns (five fields, or six with seconds for tes
124
124
 
125
125
  ### An endpoint that does not register itself
126
126
 
127
+ Tell `setupRestate` where Restate reaches the endpoint with one complete URL, scheme included:
128
+ `setupRestate({ restateAdminURL, port: 9080, serviceURL: 'https://agent-service.internal:9080', services })`. `port` is only
129
+ what the process listens on; `serviceURL` is what gets registered (it may differ behind a proxy). Without `serviceURL`
130
+ nothing is registered. The old `host` option still works (registers `http://<host>:<port>`) but is deprecated.
131
+
127
132
  `setupRestate` serves, registers the deployment, and deletes it again on SIGINT/SIGTERM. Invocations in flight are
128
133
  pinned to their deployment, so deleting it strands them: a restarted process cannot finish them and they can only be
129
134
  killed. For a process restarted at the same address, pass `keepDeploymentOnShutdown: true` and `forceRegistration: true`:
package/lib/Admin.js CHANGED
@@ -592,8 +592,12 @@ export function defineObject(def) {
592
592
  * that was removed or renamed cannot resume.
593
593
  * @param {Object} params
594
594
  * @param {string} params.restateAdminURL
595
- * @param {string} params.host
596
- * @param {number} params.port
595
+ * @param {number} params.port Port this endpoint listens on (the bind, not the advertised address).
596
+ * @param {string} [params.serviceURL] Complete URL Restate uses to reach this endpoint, scheme included
597
+ * (e.g. `https://agent-service.internal:9080`). Registration happens only when it is set — the scheme and
598
+ * any proxy/port mapping are the caller's to state, never guessed here.
599
+ * @param {string} [params.host] @deprecated Use `serviceURL`. Only when `serviceURL` is absent: registers as
600
+ * `http://<host>:<port>` (plain HTTP is assumed).
597
601
  * @param {number} [params.healthPort] - Defaults to `port + 1`.
598
602
  * @param {Parameters<typeof restate.createEndpointHandler>[0]['services']} [params.services]
599
603
  * @param {() => void} [params.shutdownCallbackFn]
@@ -605,6 +609,7 @@ export function defineObject(def) {
605
609
  */
606
610
  export async function setupRestate({
607
611
  restateAdminURL,
612
+ serviceURL: serviceURLParam,
608
613
  host,
609
614
  port,
610
615
  healthPort = port + 1,
@@ -684,9 +689,8 @@ export async function setupRestate({
684
689
 
685
690
  server.listen(port, () => console.log(`Server running on ${port}`))
686
691
 
687
- if (host && port) {
688
- const serviceURL = `http://${host}:${port}`
689
-
692
+ const serviceURL = resolveServiceURL({ serviceURL: serviceURLParam, host, port })
693
+ if (serviceURL) {
690
694
  /**
691
695
  * @param {Object} params
692
696
  * @param {string} params.restateAdminURL
@@ -771,6 +775,31 @@ export async function setupRestate({
771
775
  })
772
776
  }
773
777
 
778
+ /**
779
+ * The URL `setupRestate` registers with Restate: `serviceURL` as given (validated as an absolute http(s) URL),
780
+ * else the deprecated `host` + `port` pair as `http://host:port`, else `undefined` (no registration).
781
+ * @param {{serviceURL?: string, host?: string, port?: number}} params
782
+ * @returns {string|undefined}
783
+ * @throws {Error} If `serviceURL` is not an absolute http(s) URL
784
+ */
785
+ export function resolveServiceURL({ serviceURL, host, port }) {
786
+ if (serviceURL) {
787
+ let url
788
+ try {
789
+ url = new URL(serviceURL)
790
+ } catch {
791
+ throw new Error(
792
+ `serviceURL must be a complete URL including http:// or https://, got '${serviceURL}'`,
793
+ )
794
+ }
795
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
796
+ throw new Error(`serviceURL must use http or https, got '${serviceURL}'`)
797
+ }
798
+ return serviceURL
799
+ }
800
+ return host && port ? `http://${host}:${port}` : undefined
801
+ }
802
+
774
803
  /**
775
804
  * Whether `setupRestate`'s shutdown removes the deployment: only a real one (registered, not `'NA'`), and not when the
776
805
  * caller keeps it for the next start.
@@ -885,15 +914,7 @@ export function createRestateAdmin({ restateAdminURL, restateURL }) {
885
914
  listHandlers: ({ name }) => listHandlers({ restateAdminURL, name }),
886
915
 
887
916
  // Management queries
888
- setupRestate: ({ port, healthPort, host, services, shutdownCallbackFn }) =>
889
- setupRestate({
890
- restateAdminURL,
891
- host,
892
- port,
893
- healthPort: healthPort ?? port + 1,
894
- services,
895
- shutdownCallbackFn,
896
- }),
917
+ setupRestate: (params) => setupRestate({ ...params, restateAdminURL }),
897
918
  registerDeployment: ({ serviceURL }) => registerDeployment({ restateAdminURL, serviceURL }),
898
919
 
899
920
  submitWorkflow: ({ name, key, payload }) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empyria/restate",
3
- "version": "0.4.15",
3
+ "version": "0.4.17",
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.10",
30
- "@empyria/common": "0.3.11",
30
+ "@empyria/common": "0.3.12",
31
31
  "@restatedev/pubsub": "0.3.2",
32
32
  "@restatedev/pubsub-client": "0.3.2",
33
33
  "@restatedev/restate-sdk": "1.17.2",
@@ -36,10 +36,10 @@
36
36
  "croner": "10.0.1"
37
37
  },
38
38
  "devDependencies": {
39
- "@ai-sdk/openai-compatible": "3.0.62",
40
- "@ai-sdk/provider": "4.0.21",
39
+ "@ai-sdk/openai-compatible": "3.0.63",
40
+ "@ai-sdk/provider": "4.0.22",
41
41
  "@restatedev/restate-server": "1.7.13",
42
- "ai": "7.0.127",
42
+ "ai": "7.0.128",
43
43
  "oxfmt": "0.72.0",
44
44
  "oxlint": "1.87.0"
45
45
  },
@@ -21,6 +21,7 @@ import {
21
21
  sendMessageAsyncWithDiscovery,
22
22
  registerDeployment,
23
23
  shouldDeleteOnShutdown,
24
+ resolveServiceURL,
24
25
  sendMessageAsynSafe,
25
26
  defineWorkflow,
26
27
  defineService,
@@ -542,6 +543,31 @@ describe('registerDeployment', () => {
542
543
  })
543
544
  })
544
545
 
546
+ describe('resolveServiceURL', () => {
547
+ test('uses a complete serviceURL as given, scheme included', () => {
548
+ expect(
549
+ resolveServiceURL({ serviceURL: 'https://svc.example.com:9443/base', port: 9080 }),
550
+ ).toBe('https://svc.example.com:9443/base')
551
+ })
552
+
553
+ test('serviceURL wins over the deprecated host', () => {
554
+ expect(resolveServiceURL({ serviceURL: 'https://a', host: 'b', port: 1 })).toBe('https://a')
555
+ })
556
+
557
+ test('falls back to http://host:port for the deprecated host', () => {
558
+ expect(resolveServiceURL({ host: 'svc', port: 9080 })).toBe('http://svc:9080')
559
+ })
560
+
561
+ test('returns undefined (no registration) when nothing is given', () => {
562
+ expect(resolveServiceURL({ port: 9080 })).toBeUndefined()
563
+ })
564
+
565
+ test('rejects a URL without a scheme or with a non-http scheme', () => {
566
+ expect(() => resolveServiceURL({ serviceURL: 'svc:9080' })).toThrow(/complete URL|http/)
567
+ expect(() => resolveServiceURL({ serviceURL: 'ftp://svc' })).toThrow(/http or https/)
568
+ })
569
+ })
570
+
545
571
  describe('shouldDeleteOnShutdown', () => {
546
572
  test('deletes a registered deployment by default', () => {
547
573
  expect(shouldDeleteOnShutdown({ deploymentId: 'dep-1' })).toBe(true)