@livequery/rest 2.0.91 → 2.0.96
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 +418 -43
- package/dist/RestTransporter.js +158 -0
- package/dist/RestTransporter.js.map +1 -0
- package/dist/Socket.js +77 -0
- package/dist/Socket.js.map +1 -0
- package/dist/helpers/parseJson.js +9 -0
- package/dist/helpers/parseJson.js.map +1 -0
- package/dist/index.js +3 -2551
- package/dist/index.js.map +1 -93
- package/package.json +21 -8
package/README.md
CHANGED
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
# @livequery/rest
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@livequery/rest` is a REST + WebSocket transporter for `@livequery/core`.
|
|
4
|
+
|
|
5
|
+
It adapts the `LivequeryTransporter` interface to:
|
|
6
|
+
|
|
7
|
+
- HTTP requests for `query`, `add`, `update`, `delete`, and `trigger`
|
|
8
|
+
- optional WebSocket subscriptions for realtime collection updates
|
|
9
|
+
- request/response hooks for auth, caching, logging, or mocking
|
|
10
|
+
|
|
11
|
+
This package does not implement local cache or collection state management. That stays in `@livequery/core`. This package is only the transport layer between a livequery client and your backend.
|
|
4
12
|
|
|
5
13
|
## Installation
|
|
6
14
|
|
|
7
15
|
```bash
|
|
8
16
|
npm install @livequery/rest
|
|
9
|
-
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```bash
|
|
10
20
|
bun add @livequery/rest
|
|
11
21
|
```
|
|
12
22
|
|
|
13
|
-
##
|
|
23
|
+
## What It Implements
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
`RestTransporter` implements the `LivequeryTransporter` contract from `@livequery/core`:
|
|
26
|
+
|
|
27
|
+
- `query()` returns an `Observable<Partial<LivequeryQueryResult>>`
|
|
28
|
+
- `add()` sends `POST`
|
|
29
|
+
- `update()` sends `PATCH`
|
|
30
|
+
- `delete()` sends `DELETE`
|
|
31
|
+
- `trigger()` sends `POST /<ref>/~<action>`
|
|
32
|
+
|
|
33
|
+
When a WebSocket endpoint is configured, `query()` can also attach a realtime subscription and merge server-pushed `DataChangeEvent`s into the observable stream.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### REST only
|
|
16
38
|
|
|
17
39
|
```ts
|
|
18
40
|
import { RestTransporter } from '@livequery/rest'
|
|
@@ -22,88 +44,430 @@ const transporter = new RestTransporter({
|
|
|
22
44
|
})
|
|
23
45
|
```
|
|
24
46
|
|
|
25
|
-
###
|
|
47
|
+
### REST + realtime
|
|
26
48
|
|
|
27
49
|
```ts
|
|
50
|
+
import { RestTransporter } from '@livequery/rest'
|
|
51
|
+
|
|
52
|
+
const transporter = new RestTransporter({
|
|
53
|
+
api: 'https://api.example.com',
|
|
54
|
+
ws: 'wss://api.example.com/ws'
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### With `@livequery/core`
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { LivequeryCore } from '@livequery/core'
|
|
62
|
+
import { RestTransporter } from '@livequery/rest'
|
|
63
|
+
|
|
28
64
|
const transporter = new RestTransporter({
|
|
29
65
|
api: 'https://api.example.com',
|
|
30
66
|
ws: 'wss://api.example.com/ws'
|
|
31
67
|
})
|
|
68
|
+
|
|
69
|
+
const core = new LivequeryCore({
|
|
70
|
+
storage,
|
|
71
|
+
transporters: {
|
|
72
|
+
rest: transporter
|
|
73
|
+
}
|
|
74
|
+
})
|
|
32
75
|
```
|
|
33
76
|
|
|
34
|
-
|
|
77
|
+
## Constructor
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
type RestTransporterConfig = {
|
|
81
|
+
api: string
|
|
82
|
+
ws?: string
|
|
83
|
+
onRequest?: (
|
|
84
|
+
request: RestTransporterRequest & { ref: string }
|
|
85
|
+
) =>
|
|
86
|
+
| void
|
|
87
|
+
| Partial<RestTransporterRequest & { response?: LivequeryResult<any> }>
|
|
88
|
+
| Promise<void | Partial<RestTransporterRequest & { response?: LivequeryResult<any> }>>
|
|
89
|
+
onResponse?: (
|
|
90
|
+
request: RestTransporterRequest & { ref: string },
|
|
91
|
+
response: LivequeryResult<any>
|
|
92
|
+
) => void | Promise<void>
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Options
|
|
97
|
+
|
|
98
|
+
| Option | Type | Description |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `api` | `string` | Base HTTP URL used for all REST calls. |
|
|
101
|
+
| `ws` | `string` | Optional WebSocket endpoint for realtime sync. |
|
|
102
|
+
| `onRequest` | `function` | Optional interceptor before `fetch()`. Can override request fields or return a fake response. |
|
|
103
|
+
| `onResponse` | `function` | Optional hook called after the response is resolved. |
|
|
35
104
|
|
|
36
|
-
|
|
105
|
+
## Request Model
|
|
106
|
+
|
|
107
|
+
Outgoing requests use this shape internally:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
type RestTransporterRequest = {
|
|
111
|
+
url: string
|
|
112
|
+
method: string
|
|
113
|
+
query?: Record<string, any>
|
|
114
|
+
body?: Record<string, any> | string
|
|
115
|
+
headers?: Record<string, string | undefined>
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The transporter builds URLs like this:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
<api>/<ref>
|
|
123
|
+
<api>/<ref>?<query>
|
|
124
|
+
<api>/<ref>/~<action>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
GET /users
|
|
131
|
+
GET /users/123
|
|
132
|
+
POST /users
|
|
133
|
+
PATCH /users/123
|
|
134
|
+
DELETE /users/123
|
|
135
|
+
POST /users/~ban
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Hooks
|
|
139
|
+
|
|
140
|
+
### `onRequest`
|
|
141
|
+
|
|
142
|
+
Use `onRequest` to:
|
|
143
|
+
|
|
144
|
+
- inject auth headers
|
|
145
|
+
- override request body or URL
|
|
146
|
+
- short-circuit requests from cache
|
|
147
|
+
- mock server responses in tests
|
|
37
148
|
|
|
38
149
|
```ts
|
|
39
150
|
const transporter = new RestTransporter({
|
|
40
151
|
api: 'https://api.example.com',
|
|
41
152
|
ws: 'wss://api.example.com/ws',
|
|
42
|
-
|
|
43
|
-
onRequest: async ({ url, method, headers, ref }) => {
|
|
153
|
+
onRequest: async ({ headers }) => {
|
|
44
154
|
const token = await getAccessToken()
|
|
155
|
+
|
|
45
156
|
return {
|
|
46
|
-
headers: {
|
|
157
|
+
headers: {
|
|
158
|
+
...headers,
|
|
159
|
+
Authorization: `Bearer ${token}`
|
|
160
|
+
}
|
|
47
161
|
}
|
|
48
|
-
}
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
To skip the network completely, return a `response`:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const transporter = new RestTransporter({
|
|
170
|
+
api: 'https://api.example.com',
|
|
171
|
+
onRequest: ({ ref }) => {
|
|
172
|
+
const cached = cache.get(ref)
|
|
173
|
+
if (!cached) return
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
response: {
|
|
177
|
+
data: cached
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
```
|
|
49
183
|
|
|
184
|
+
### `onResponse`
|
|
185
|
+
|
|
186
|
+
Use `onResponse` for logging, metrics, or centralized error inspection:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const transporter = new RestTransporter({
|
|
190
|
+
api: 'https://api.example.com',
|
|
50
191
|
onResponse: async (request, response) => {
|
|
51
|
-
if (response.error)
|
|
192
|
+
if (response.error) {
|
|
193
|
+
console.error('Livequery REST error', {
|
|
194
|
+
url: request.url,
|
|
195
|
+
method: request.method,
|
|
196
|
+
error: response.error
|
|
197
|
+
})
|
|
198
|
+
}
|
|
52
199
|
}
|
|
53
200
|
})
|
|
54
201
|
```
|
|
55
202
|
|
|
56
|
-
|
|
203
|
+
## REST Response Contract
|
|
204
|
+
|
|
205
|
+
The transporter expects your backend to return a `LivequeryResult<T>` envelope:
|
|
57
206
|
|
|
58
207
|
```ts
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
208
|
+
type LivequeryResult<T> = {
|
|
209
|
+
data: T
|
|
210
|
+
error?: {
|
|
211
|
+
code: string
|
|
212
|
+
message: string
|
|
213
|
+
}
|
|
62
214
|
}
|
|
63
215
|
```
|
|
64
216
|
|
|
65
|
-
|
|
217
|
+
### Collection query response
|
|
66
218
|
|
|
67
|
-
|
|
219
|
+
For collection reads, `data` should look like:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
type LivequeryCollectionResponse<T> = {
|
|
223
|
+
summary?: Record<string, any>
|
|
224
|
+
items: T[]
|
|
225
|
+
subscription_token?: string
|
|
226
|
+
count?: {
|
|
227
|
+
prev: number
|
|
228
|
+
next: number
|
|
229
|
+
total: number
|
|
230
|
+
current: number
|
|
231
|
+
}
|
|
232
|
+
has?: {
|
|
233
|
+
prev: boolean
|
|
234
|
+
next: boolean
|
|
235
|
+
}
|
|
236
|
+
cursor?: {
|
|
237
|
+
first: string
|
|
238
|
+
last: string
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
68
242
|
|
|
69
|
-
|
|
243
|
+
Example:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"data": {
|
|
248
|
+
"items": [
|
|
249
|
+
{ "id": "u1", "name": "Ada" },
|
|
250
|
+
{ "id": "u2", "name": "Linus" }
|
|
251
|
+
],
|
|
252
|
+
"summary": {
|
|
253
|
+
"active": 2
|
|
254
|
+
},
|
|
255
|
+
"count": {
|
|
256
|
+
"prev": 0,
|
|
257
|
+
"next": 20,
|
|
258
|
+
"current": 2,
|
|
259
|
+
"total": 22
|
|
260
|
+
},
|
|
261
|
+
"has": {
|
|
262
|
+
"prev": false,
|
|
263
|
+
"next": true
|
|
264
|
+
},
|
|
265
|
+
"cursor": {
|
|
266
|
+
"first": "cursor-1",
|
|
267
|
+
"last": "cursor-2"
|
|
268
|
+
},
|
|
269
|
+
"subscription_token": "rt_abc123"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
70
273
|
|
|
71
|
-
|
|
72
|
-
|---|---|---|
|
|
73
|
-
| `api` | `string` | Base URL of your REST API |
|
|
74
|
-
| `ws` | `string` (optional) | WebSocket endpoint for real-time updates |
|
|
75
|
-
| `onRequest` | function (optional) | Interceptor called before each request. Return partial request overrides or a fake response. |
|
|
76
|
-
| `onResponse` | function (optional) | Called after each response. |
|
|
274
|
+
### Single document response
|
|
77
275
|
|
|
78
|
-
|
|
276
|
+
For document reads, `data` should contain `item`:
|
|
79
277
|
|
|
80
|
-
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"data": {
|
|
281
|
+
"item": {
|
|
282
|
+
"id": "u1",
|
|
283
|
+
"name": "Ada"
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
81
288
|
|
|
82
|
-
|
|
83
|
-
|---|---|
|
|
84
|
-
| `query(ref, filters)` | Query a collection or document. Returns an Observable. |
|
|
85
|
-
| `add(ref, data)` | Create a new document (`POST`) |
|
|
86
|
-
| `update(ref, id, data)` | Update a document (`PATCH`) |
|
|
87
|
-
| `delete(ref, id)` | Delete a document (`DELETE`) |
|
|
88
|
-
| `trigger({ ref, action, payload })` | Trigger a custom action (`POST /ref/~action`) |
|
|
289
|
+
### Create response
|
|
89
290
|
|
|
90
|
-
|
|
291
|
+
`add()` accepts either of these backend shapes:
|
|
91
292
|
|
|
92
|
-
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"data": {
|
|
296
|
+
"item": {
|
|
297
|
+
"id": "u1",
|
|
298
|
+
"name": "Ada"
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
```json
|
|
305
|
+
{
|
|
306
|
+
"data": {
|
|
307
|
+
"id": "u1",
|
|
308
|
+
"name": "Ada"
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Query Output Shape
|
|
314
|
+
|
|
315
|
+
`query()` converts server data into the shape expected by `@livequery/core`:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
type LivequeryQueryResult = {
|
|
319
|
+
changes: DataChangeEvent[]
|
|
320
|
+
summary: Record<string, any>
|
|
321
|
+
paging: {
|
|
322
|
+
total: number
|
|
323
|
+
current: number
|
|
324
|
+
next?: { count: number; cursor: string }
|
|
325
|
+
prev?: { count: number; cursor: string }
|
|
326
|
+
}
|
|
327
|
+
source: 'query' | 'realtime' | 'action'
|
|
328
|
+
error: { code: string; message: string }
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Collection responses are converted to `added` events for each returned item. Document responses are converted to a single `added` event for the returned document.
|
|
333
|
+
|
|
334
|
+
## Realtime
|
|
335
|
+
|
|
336
|
+
If `ws` is provided, the transporter creates a `Socket` instance and adds these headers to REST calls:
|
|
337
|
+
|
|
338
|
+
- `socket_id`
|
|
339
|
+
- `x-lcid`
|
|
340
|
+
- `x-lgid`
|
|
341
|
+
|
|
342
|
+
This lets your backend bind the HTTP query to the active realtime session.
|
|
343
|
+
|
|
344
|
+
### Realtime flow
|
|
345
|
+
|
|
346
|
+
1. A collection query returns `subscription_token`.
|
|
347
|
+
2. The transporter forwards that token to the socket with a `subscribe` event.
|
|
348
|
+
3. The socket listens for server `sync` messages.
|
|
349
|
+
4. Incoming changes are emitted as `DataChangeEvent`s with `source: 'realtime'`.
|
|
350
|
+
|
|
351
|
+
Realtime listening is only attached for standard collection queries. It is skipped when:
|
|
352
|
+
|
|
353
|
+
- no `ws` endpoint is configured
|
|
354
|
+
- the request is a cursor query using `:after`
|
|
355
|
+
- the request is a cursor query using `:before`
|
|
356
|
+
- the request is an around query using `:around`
|
|
357
|
+
|
|
358
|
+
### WebSocket protocol expected by `Socket`
|
|
359
|
+
|
|
360
|
+
When the socket opens, it sends:
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{ "event": "start", "data": { "id": "<client_id>" } }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
It also sends a heartbeat every 60 seconds:
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{ "event": "ping" }
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
To subscribe a collection query, it sends:
|
|
373
|
+
|
|
374
|
+
```json
|
|
375
|
+
{ "event": "subscribe", "data": { "realtime_token": "<token>" } }
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The server should respond with a hello message containing the gateway id:
|
|
379
|
+
|
|
380
|
+
```json
|
|
381
|
+
{ "event": "hello", "gid": "gateway-1" }
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Realtime sync messages should look like:
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
{
|
|
388
|
+
"event": "sync",
|
|
389
|
+
"data": {
|
|
390
|
+
"changes": [
|
|
391
|
+
{
|
|
392
|
+
"ref": "users",
|
|
393
|
+
"id": "u1",
|
|
394
|
+
"type": "modified",
|
|
395
|
+
"data": { "name": "Ada Lovelace" }
|
|
396
|
+
}
|
|
397
|
+
]
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Each sync change is routed to `listen(ref)` subscribers and normalized to:
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
type DataChangeEvent = {
|
|
406
|
+
collection_ref: string
|
|
407
|
+
id: string
|
|
408
|
+
type: 'added' | 'removed' | 'modified'
|
|
409
|
+
data?: Record<string, any>
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Public API
|
|
414
|
+
|
|
415
|
+
### `RestTransporter`
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
import { RestTransporter } from '@livequery/rest'
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Methods:
|
|
422
|
+
|
|
423
|
+
- `query({ ref, filters })`
|
|
424
|
+
- `add(ref, data)`
|
|
425
|
+
- `update(collectionRef, id, data)`
|
|
426
|
+
- `delete(collectionRef, id)`
|
|
427
|
+
- `trigger({ ref, action, payload })`
|
|
428
|
+
|
|
429
|
+
### `Socket`
|
|
93
430
|
|
|
94
431
|
```ts
|
|
95
432
|
import { Socket } from '@livequery/rest'
|
|
433
|
+
```
|
|
96
434
|
|
|
435
|
+
Subpath import is also available:
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
import { Socket } from '@livequery/rest/Socket'
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
The socket class is exported for low-level integrations and debugging.
|
|
442
|
+
|
|
443
|
+
Example:
|
|
444
|
+
|
|
445
|
+
```ts
|
|
97
446
|
const socket = new Socket('wss://api.example.com/ws')
|
|
98
|
-
|
|
99
|
-
socket.
|
|
447
|
+
|
|
448
|
+
socket.listen('users').subscribe(change => {
|
|
449
|
+
console.log(change)
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
socket.stop()
|
|
100
453
|
```
|
|
101
454
|
|
|
102
|
-
##
|
|
455
|
+
## Error Handling
|
|
103
456
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
457
|
+
If `fetch()` throws or the backend returns an error envelope, the transporter surfaces it as:
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
{
|
|
461
|
+
error: {
|
|
462
|
+
code: string,
|
|
463
|
+
message: string
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
For `query()`, errors are emitted as an observable result with `source: 'query'`.
|
|
469
|
+
|
|
470
|
+
For `add()`, `update()`, `delete()`, and `trigger()`, errors are thrown as rejected promises.
|
|
107
471
|
|
|
108
472
|
## Build
|
|
109
473
|
|
|
@@ -111,7 +475,18 @@ socket.stop() // close connection
|
|
|
111
475
|
bun run build
|
|
112
476
|
```
|
|
113
477
|
|
|
114
|
-
|
|
478
|
+
Build steps:
|
|
479
|
+
|
|
480
|
+
- clean `dist/`
|
|
481
|
+
- emit Node.js ESM files with TypeScript (`module: NodeNext`)
|
|
482
|
+
- generate `.js`, `.d.ts`, `.js.map`, and `.d.ts.map`
|
|
483
|
+
|
|
484
|
+
The published package is strict ESM (`"type": "module"`) and exposes these entrypoints:
|
|
485
|
+
|
|
486
|
+
- `@livequery/rest`
|
|
487
|
+
- `@livequery/rest/RestTransporter`
|
|
488
|
+
- `@livequery/rest/Socket`
|
|
489
|
+
- `@livequery/rest/helpers/parseJson`
|
|
115
490
|
|
|
116
491
|
## License
|
|
117
492
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { of, firstValueFrom, EMPTY, from } from 'rxjs';
|
|
2
|
+
import { catchError, filter, first, map, mergeMap, take } from 'rxjs/operators';
|
|
3
|
+
import { merge } from 'rxjs';
|
|
4
|
+
import { Socket } from './Socket.js';
|
|
5
|
+
import { parseJson } from './helpers/parseJson.js';
|
|
6
|
+
export class RestTransporter {
|
|
7
|
+
config;
|
|
8
|
+
socket;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
if (config.ws) {
|
|
12
|
+
this.socket = new Socket(config.ws);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async #call(req) {
|
|
16
|
+
const url = `${await this.config.api}/${req.ref}${req.action ? `/~${req.action}` : ''}${Object.keys(req.query || {}).length > 0 ? `?${new URLSearchParams(req.query).toString()}` : ''}`;
|
|
17
|
+
const base_headers = {
|
|
18
|
+
...req.body ? {
|
|
19
|
+
'Content-Type': 'application/json'
|
|
20
|
+
} : {},
|
|
21
|
+
...this.socket ? {
|
|
22
|
+
socket_id: this.socket.client_id,
|
|
23
|
+
'x-lcid': this.socket.client_id,
|
|
24
|
+
'x-lgid': await firstValueFrom(this.socket.$gateway)
|
|
25
|
+
} : {}
|
|
26
|
+
};
|
|
27
|
+
const original_request = {
|
|
28
|
+
url,
|
|
29
|
+
method: req.method,
|
|
30
|
+
body: req.body,
|
|
31
|
+
headers: base_headers,
|
|
32
|
+
query: req.query,
|
|
33
|
+
ref: req.ref
|
|
34
|
+
};
|
|
35
|
+
const { response: fake_response, headers, ...modified } = await this.config.onRequest?.(original_request) || {};
|
|
36
|
+
if (fake_response) {
|
|
37
|
+
this.config.onResponse && await this.config.onResponse(original_request, fake_response);
|
|
38
|
+
if (fake_response.error)
|
|
39
|
+
throw fake_response.error;
|
|
40
|
+
return fake_response.data;
|
|
41
|
+
}
|
|
42
|
+
const request = {
|
|
43
|
+
ref: req.ref,
|
|
44
|
+
url,
|
|
45
|
+
method: req.method,
|
|
46
|
+
...req.body ? { body: typeof req.body === 'string' ? req.body : JSON.stringify(req.body) } : {},
|
|
47
|
+
...modified,
|
|
48
|
+
headers: {
|
|
49
|
+
...req.body && typeof req.body != 'string' ? { 'Content-Type': 'application/json' } : {},
|
|
50
|
+
...base_headers,
|
|
51
|
+
...headers
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const response = fake_response ? fake_response : await (async () => {
|
|
55
|
+
try {
|
|
56
|
+
const result = await fetch(request.url, request);
|
|
57
|
+
return parseJson(await result.text()) || {};
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
return {
|
|
61
|
+
error: {
|
|
62
|
+
code: e instanceof Error ? e.name : 'UnknownError',
|
|
63
|
+
message: e instanceof Error ? e.message : 'An unknown error occurred'
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
})();
|
|
68
|
+
this.config.onResponse && await this.config.onResponse(request, response);
|
|
69
|
+
if (response.error)
|
|
70
|
+
throw response.error;
|
|
71
|
+
return response.data;
|
|
72
|
+
}
|
|
73
|
+
query({ ref, filters }) {
|
|
74
|
+
const ready$ = from(this.socket ? (this.socket.pipe(filter(s => !!s.connected), map(() => Date.now()))) : of(1)).pipe(first());
|
|
75
|
+
const watch$ = (!this.socket || !filters || filters[':after'] || filters[':before'] || filters[':around']) ? EMPTY : this.socket.listen(ref);
|
|
76
|
+
const refs = ref.split('/');
|
|
77
|
+
const collection_ref = refs.length % 2 == 0 ? refs.slice(0, -1).join('/') : ref;
|
|
78
|
+
return merge(ready$.pipe(take(1), mergeMap(() => (from(this.#call({
|
|
79
|
+
ref,
|
|
80
|
+
method: 'GET',
|
|
81
|
+
query: filters
|
|
82
|
+
})).pipe(map(collection => {
|
|
83
|
+
collection.subscription_token && this.socket?.subscribeWith(collection.subscription_token);
|
|
84
|
+
// If collection
|
|
85
|
+
if (collection.items) {
|
|
86
|
+
const items = Array.isArray(collection.items) ? collection.items : [];
|
|
87
|
+
const length = items.length;
|
|
88
|
+
return {
|
|
89
|
+
summary: collection.summary,
|
|
90
|
+
paging: {
|
|
91
|
+
current: collection?.count?.current ?? length,
|
|
92
|
+
total: collection?.count?.total ?? length,
|
|
93
|
+
next: collection?.has?.next ? {
|
|
94
|
+
count: collection?.count?.next || 0,
|
|
95
|
+
cursor: collection?.cursor?.last
|
|
96
|
+
} : undefined,
|
|
97
|
+
prev: collection?.has?.prev ? {
|
|
98
|
+
count: collection?.count?.prev || 0,
|
|
99
|
+
cursor: collection?.cursor?.first
|
|
100
|
+
} : undefined
|
|
101
|
+
},
|
|
102
|
+
changes: items.map(data => ({
|
|
103
|
+
data,
|
|
104
|
+
type: 'added',
|
|
105
|
+
id: data.id,
|
|
106
|
+
collection_ref
|
|
107
|
+
})),
|
|
108
|
+
source: "query"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// If document
|
|
112
|
+
return {
|
|
113
|
+
summary: collection.summary,
|
|
114
|
+
changes: [{
|
|
115
|
+
data: collection.item,
|
|
116
|
+
type: 'added',
|
|
117
|
+
id: collection.item.id,
|
|
118
|
+
collection_ref
|
|
119
|
+
}],
|
|
120
|
+
source: "query"
|
|
121
|
+
};
|
|
122
|
+
}), catchError(e => {
|
|
123
|
+
const error = e instanceof Error ? { code: e.name, message: e.message } : { code: e.code || 'UnknownError', message: e.message || 'An unknown error occurred' };
|
|
124
|
+
return of({ error, source: "query" });
|
|
125
|
+
}))))), watch$.pipe(map((change) => {
|
|
126
|
+
const id = change.data?.id;
|
|
127
|
+
if (id) {
|
|
128
|
+
const e = {
|
|
129
|
+
changes: [
|
|
130
|
+
{
|
|
131
|
+
...change,
|
|
132
|
+
collection_ref,
|
|
133
|
+
id
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
source: "realtime"
|
|
137
|
+
};
|
|
138
|
+
return e;
|
|
139
|
+
}
|
|
140
|
+
}), filter(Boolean)));
|
|
141
|
+
}
|
|
142
|
+
async add(ref, data) {
|
|
143
|
+
const r = await this.#call({ method: 'POST', ref, body: data, query: {} });
|
|
144
|
+
if (r.id)
|
|
145
|
+
return r;
|
|
146
|
+
return r.item;
|
|
147
|
+
}
|
|
148
|
+
update(collection_ref, id, data) {
|
|
149
|
+
return this.#call({ method: 'PATCH', ref: collection_ref + '/' + id, body: data, query: {} });
|
|
150
|
+
}
|
|
151
|
+
delete(collection_ref, id) {
|
|
152
|
+
return this.#call({ method: 'DELETE', ref: collection_ref + '/' + id, body: undefined, query: {} });
|
|
153
|
+
}
|
|
154
|
+
trigger({ ref, action, payload }) {
|
|
155
|
+
return this.#call({ method: 'POST', ref, action, body: payload, query: {} });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=RestTransporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RestTransporter.js","sourceRoot":"","sources":["../src/RestTransporter.ts"],"names":[],"mappings":"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"}
|