@empyria/restate 0.1.14 → 0.1.16
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/lib/Admin.js +32 -0
- package/package.json +3 -3
- package/test/Admin.test.js +36 -0
package/lib/Admin.js
CHANGED
|
@@ -127,6 +127,35 @@ export async function listServices({ restateAdminURL }) {
|
|
|
127
127
|
return data.services // Array of { name, handlers, ty, deployment_id, ... }
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Runs a raw SQL query against Restate's own introspection API (`POST {adminURL}/query`) — the
|
|
132
|
+
* `sys_invocation`/`state`/`sys_promise`/... tables Restate exposes specifically so operators
|
|
133
|
+
* and tooling can inspect what's running without an official client for it (see this file's own
|
|
134
|
+
* "No official Admin API client" header note — same "fetch the REST endpoint directly" posture
|
|
135
|
+
* as every other function here; this is simply the one REST endpoint none of them touch yet).
|
|
136
|
+
* The endpoint takes a single JSON string, no parameter binding — build `sql` with any
|
|
137
|
+
* caller-supplied values already escaped (single quotes doubled) before calling this.
|
|
138
|
+
* @param {{restateAdminURL: string, sql: string}} params
|
|
139
|
+
* @returns {Promise<Record<string, unknown>[]>}
|
|
140
|
+
*/
|
|
141
|
+
export async function queryRestate({ restateAdminURL, sql }) {
|
|
142
|
+
const response = await fetch(`${restateAdminURL}/query`, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
145
|
+
body: JSON.stringify({ query: sql }),
|
|
146
|
+
})
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
const body = await response.text().catch(() => '')
|
|
149
|
+
throw new restate.TerminalError(
|
|
150
|
+
`Restate SQL query failed: ${response.status} ${response.statusText} — ${body.slice(0, 500)}`,
|
|
151
|
+
{ errorCode: ERROR_INFRASTRUCTURE },
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
/** @type {{rows: Record<string, unknown>[]}} */
|
|
155
|
+
const { rows } = await response.json()
|
|
156
|
+
return rows
|
|
157
|
+
}
|
|
158
|
+
|
|
130
159
|
/**
|
|
131
160
|
* Force-deletes a deployment from Restate, draining any in-flight invocations.
|
|
132
161
|
* @param {string} restateAdminURL
|
|
@@ -691,6 +720,7 @@ export const sendMessageAsynSafe = async ({ restateURL, callerParams, timeout })
|
|
|
691
720
|
|
|
692
721
|
/**
|
|
693
722
|
* @typedef {Object} RestateAdmin
|
|
723
|
+
* @property {(sql: string) => Promise<Record<string, unknown>[]>} query
|
|
694
724
|
* @property {() => Promise<ServiceMetadata[]>} listServices
|
|
695
725
|
* @property {(params: {name: string}) => Promise<ServiceMetadata>} service
|
|
696
726
|
* @property {(params: {name: string}) => Promise<HandlerMetadata[]>} listHandlers
|
|
@@ -714,6 +744,8 @@ export const sendMessageAsynSafe = async ({ restateURL, callerParams, timeout })
|
|
|
714
744
|
*/
|
|
715
745
|
export function createRestateAdmin({ restateAdminURL, restateURL }) {
|
|
716
746
|
return {
|
|
747
|
+
query: (sql) => queryRestate({ restateAdminURL, sql }),
|
|
748
|
+
|
|
717
749
|
// Services
|
|
718
750
|
listServices: () => listServices({ restateAdminURL }),
|
|
719
751
|
service: ({ name }) => service({ restateAdminURL, name }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empyria/restate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Restate.dev helpers for the Empyria nanoservice framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Imre Fazekas <imre.fazekas@icloud.com>",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"test:watch": "bun test --watch"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@empyria/classification": "0.1.
|
|
28
|
-
"@empyria/common": "0.3.
|
|
27
|
+
"@empyria/classification": "0.1.7",
|
|
28
|
+
"@empyria/common": "0.3.2",
|
|
29
29
|
"@restatedev/pubsub": "0.3.2",
|
|
30
30
|
"@restatedev/pubsub-client": "0.3.2",
|
|
31
31
|
"@restatedev/restate-sdk": "1.17.0",
|
package/test/Admin.test.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
startSubWorkflow,
|
|
5
5
|
callSubWorkflow,
|
|
6
6
|
listServices,
|
|
7
|
+
queryRestate,
|
|
7
8
|
deleteDeployment,
|
|
8
9
|
service,
|
|
9
10
|
listHandlers,
|
|
@@ -173,6 +174,27 @@ describe('listServices', () => {
|
|
|
173
174
|
})
|
|
174
175
|
})
|
|
175
176
|
|
|
177
|
+
describe('queryRestate', () => {
|
|
178
|
+
test('POSTs {query: sql} to /query and returns the rows', async () => {
|
|
179
|
+
const rows = [{ target_service_key: 'CA0001', status: 'suspended' }]
|
|
180
|
+
globalThis.fetch = mock(async (url, init) => {
|
|
181
|
+
expect(String(url)).toBe('http://admin/query')
|
|
182
|
+
expect(JSON.parse(init.body)).toEqual({ query: 'SELECT 1' })
|
|
183
|
+
return jsonResponse({ rows })
|
|
184
|
+
})
|
|
185
|
+
expect(await queryRestate({ restateAdminURL: 'http://admin', sql: 'SELECT 1' })).toEqual(
|
|
186
|
+
rows,
|
|
187
|
+
)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('throws a TerminalError on a failed query', async () => {
|
|
191
|
+
globalThis.fetch = mock(async () => jsonResponse(null, false))
|
|
192
|
+
await expect(
|
|
193
|
+
queryRestate({ restateAdminURL: 'http://admin', sql: 'SELECT 1' }),
|
|
194
|
+
).rejects.toThrow(restate.TerminalError)
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
176
198
|
describe('deleteDeployment', () => {
|
|
177
199
|
test('resolves on success', async () => {
|
|
178
200
|
globalThis.fetch = mock(async () => jsonResponse({}))
|
|
@@ -486,4 +508,18 @@ describe('createRestateAdmin', () => {
|
|
|
486
508
|
})
|
|
487
509
|
expect(await admin.listServices()).toEqual(servicesPayload.services)
|
|
488
510
|
})
|
|
511
|
+
|
|
512
|
+
test('query() binds restateAdminURL and forwards the raw SQL', async () => {
|
|
513
|
+
const rows = [{ status: 'completed' }]
|
|
514
|
+
globalThis.fetch = mock(async (url, init) => {
|
|
515
|
+
expect(String(url)).toBe('http://admin/query')
|
|
516
|
+
expect(JSON.parse(init.body)).toEqual({ query: 'SELECT status FROM sys_invocation' })
|
|
517
|
+
return jsonResponse({ rows })
|
|
518
|
+
})
|
|
519
|
+
const admin = createRestateAdmin({
|
|
520
|
+
restateAdminURL: 'http://admin',
|
|
521
|
+
restateURL: 'http://ingress',
|
|
522
|
+
})
|
|
523
|
+
expect(await admin.query('SELECT status FROM sys_invocation')).toEqual(rows)
|
|
524
|
+
})
|
|
489
525
|
})
|