@livequery/core 2.0.104 → 2.0.106

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.
Files changed (96) hide show
  1. package/LIVEQUERY_SPEC.md +451 -0
  2. package/README.md +571 -314
  3. package/build/src/ApiGatewayHandler.d.ts +56 -0
  4. package/build/src/ApiGatewayHandler.js +210 -0
  5. package/build/src/ApiGatewayHandler.js.map +1 -0
  6. package/build/src/ApiGatewayLinker.d.ts +61 -0
  7. package/build/src/ApiGatewayLinker.js +229 -0
  8. package/build/src/ApiGatewayLinker.js.map +1 -0
  9. package/build/src/ApiServiceLinker.d.ts +18 -0
  10. package/build/src/ApiServiceLinker.js +61 -0
  11. package/build/src/ApiServiceLinker.js.map +1 -0
  12. package/build/src/LivequeryContext.d.ts +43 -0
  13. package/build/src/LivequeryContext.js +2 -0
  14. package/build/src/LivequeryContext.js.map +1 -0
  15. package/build/src/LivequeryDatasource.d.ts +8 -0
  16. package/build/src/LivequeryDatasource.js +2 -0
  17. package/build/src/LivequeryDatasource.js.map +1 -0
  18. package/build/src/LivequeryRequestParser.d.ts +13 -0
  19. package/build/src/LivequeryRequestParser.js +34 -0
  20. package/build/src/LivequeryRequestParser.js.map +1 -0
  21. package/build/src/UdpDiscovery.d.ts +26 -0
  22. package/build/src/UdpDiscovery.js +238 -0
  23. package/build/src/UdpDiscovery.js.map +1 -0
  24. package/build/src/WebsocketGateway.d.ts +36 -0
  25. package/build/src/WebsocketGateway.js +353 -0
  26. package/build/src/WebsocketGateway.js.map +1 -0
  27. package/build/src/const.d.ts +8 -0
  28. package/build/src/const.js +10 -0
  29. package/build/src/const.js.map +1 -0
  30. package/build/src/headers.d.ts +9 -0
  31. package/build/src/headers.js +32 -0
  32. package/build/src/headers.js.map +1 -0
  33. package/build/src/helpers/PathHelper.d.ts +11 -0
  34. package/build/src/helpers/PathHelper.js +41 -0
  35. package/build/src/helpers/PathHelper.js.map +1 -0
  36. package/build/src/helpers/hidePrivateFields.d.ts +3 -0
  37. package/build/src/helpers/hidePrivateFields.js +29 -0
  38. package/build/src/helpers/hidePrivateFields.js.map +1 -0
  39. package/build/src/helpers/nodeRequestToWebRequest.d.ts +7 -0
  40. package/build/src/helpers/nodeRequestToWebRequest.js +17 -0
  41. package/build/src/helpers/nodeRequestToWebRequest.js.map +1 -0
  42. package/build/src/helpers/writeWebResponse.d.ts +2 -0
  43. package/build/src/helpers/writeWebResponse.js +9 -0
  44. package/build/src/helpers/writeWebResponse.js.map +1 -0
  45. package/build/src/index.d.ts +9 -0
  46. package/build/src/index.js +10 -0
  47. package/build/src/index.js.map +1 -0
  48. package/build/src/parseLivequeryRequest.d.ts +10 -0
  49. package/build/src/parseLivequeryRequest.js +17 -0
  50. package/build/src/parseLivequeryRequest.js.map +1 -0
  51. package/build/tsconfig.tsbuildinfo +1 -0
  52. package/package.json +40 -103
  53. package/dist/LivequeryCollection.d.ts +0 -92
  54. package/dist/LivequeryCollection.d.ts.map +0 -1
  55. package/dist/LivequeryCollection.js +0 -231
  56. package/dist/LivequeryCollection.js.map +0 -1
  57. package/dist/LivequeryCore.d.ts +0 -67
  58. package/dist/LivequeryCore.d.ts.map +0 -1
  59. package/dist/LivequeryCore.js +0 -343
  60. package/dist/LivequeryCore.js.map +0 -1
  61. package/dist/LivequeryDocument.d.ts +0 -23
  62. package/dist/LivequeryDocument.d.ts.map +0 -1
  63. package/dist/LivequeryDocument.js +0 -22
  64. package/dist/LivequeryDocument.js.map +0 -1
  65. package/dist/LivequeryMemoryStorage.d.ts +0 -14
  66. package/dist/LivequeryMemoryStorage.d.ts.map +0 -1
  67. package/dist/LivequeryMemoryStorage.js +0 -89
  68. package/dist/LivequeryMemoryStorage.js.map +0 -1
  69. package/dist/LivequeryStorge.d.ts +0 -12
  70. package/dist/LivequeryStorge.d.ts.map +0 -1
  71. package/dist/LivequeryStorge.js +0 -2
  72. package/dist/LivequeryStorge.js.map +0 -1
  73. package/dist/LivequeryTransporter.d.ts +0 -22
  74. package/dist/LivequeryTransporter.d.ts.map +0 -1
  75. package/dist/LivequeryTransporter.js +0 -2
  76. package/dist/LivequeryTransporter.js.map +0 -1
  77. package/dist/helpers/filterDocs.d.ts +0 -5
  78. package/dist/helpers/filterDocs.d.ts.map +0 -1
  79. package/dist/helpers/filterDocs.js +0 -80
  80. package/dist/helpers/filterDocs.js.map +0 -1
  81. package/dist/helpers/tryCatch.d.ts +0 -2
  82. package/dist/helpers/tryCatch.d.ts.map +0 -1
  83. package/dist/helpers/tryCatch.js +0 -10
  84. package/dist/helpers/tryCatch.js.map +0 -1
  85. package/dist/helpers/whenCompleted.d.ts +0 -3
  86. package/dist/helpers/whenCompleted.d.ts.map +0 -1
  87. package/dist/helpers/whenCompleted.js +0 -5
  88. package/dist/helpers/whenCompleted.js.map +0 -1
  89. package/dist/index.d.ts +0 -9
  90. package/dist/index.d.ts.map +0 -1
  91. package/dist/index.js +0 -9
  92. package/dist/index.js.map +0 -1
  93. package/dist/types.d.ts +0 -70
  94. package/dist/types.d.ts.map +0 -1
  95. package/dist/types.js +0 -2
  96. package/dist/types.js.map +0 -1
@@ -0,0 +1,451 @@
1
+ # Livequery Specification
2
+
3
+ This document defines the framework-agnostic Livequery model used by `@livequery/core`.
4
+
5
+ Livequery is not a database engine and does not require any specific HTTP framework. A Livequery service can be backed by a database, an external API, an in-memory map, generated data, or any custom handler. The standard is the request model, response envelope, path grammar, action model, and realtime update shape.
6
+
7
+ ## Core Idea
8
+
9
+ Livequery maps an HTTP path to a normalized data reference called a `ref`.
10
+
11
+ Examples:
12
+
13
+ | Request path | Meaning | Ref |
14
+ | --- | --- | --- |
15
+ | `/livequery/posts` | `posts` collection | `posts` |
16
+ | `/livequery/posts/p1` | `posts/p1` document | `posts/p1` |
17
+ | `/livequery/users/u1/posts` | nested `posts` collection for user `u1` | `users/u1/posts` |
18
+ | `/livequery/users/u1/posts/p1` | nested post document `p1` for user `u1` | `users/u1/posts/p1` |
19
+
20
+ The `livequery` prefix is a route prefix. It is not part of the normalized data ref.
21
+
22
+ ## Request Model
23
+
24
+ A framework adapter provides a `RawRequest`, then `LivequeryRequestParser` creates a normalized `LivequeryRequest`.
25
+
26
+ Important fields:
27
+
28
+ ```ts
29
+ type LivequeryRequest<I = any> = {
30
+ keys: Record<string, any>
31
+ path: string
32
+ document_id?: string
33
+ collection_ref: string
34
+ schema_collection_ref: string
35
+ ref: string
36
+ method: string
37
+ body: I
38
+ query: Record<string, any>
39
+ }
40
+ ```
41
+
42
+ Field meanings:
43
+
44
+ | Field | Meaning |
45
+ | --- | --- |
46
+ | `path` | Original request path, including any query string or custom action suffix supplied by the adapter. |
47
+ | `ref` | Concrete data reference for this request, such as `posts` or `posts/p1`. |
48
+ | `collection_ref` | Concrete collection containing the item, such as `posts` or `users/u1/posts`. |
49
+ | `schema_collection_ref` | Route-pattern collection ref using parameter names instead of values, such as `users/uid/posts`. |
50
+ | `document_id` | Document id when the request targets a document. Undefined for collection requests. |
51
+ | `keys` | Route parameters supplied by the framework adapter. |
52
+ | `method` | Uppercased HTTP method. |
53
+ | `body` | Request body. The format is application-defined. |
54
+ | `query` | Parsed query parameters. Query values are not part of the path grammar. |
55
+
56
+ ## Collection And Document Paths
57
+
58
+ Livequery distinguishes collections and documents by the route pattern.
59
+
60
+ A collection route does not end with a document parameter:
61
+
62
+ ```txt
63
+ GET /livequery/posts
64
+ ref /livequery/posts
65
+ ```
66
+
67
+ Normalized request:
68
+
69
+ ```ts
70
+ {
71
+ ref: 'posts',
72
+ collection_ref: 'posts',
73
+ schema_collection_ref: 'posts',
74
+ document_id: undefined
75
+ }
76
+ ```
77
+
78
+ A document route ends with a parameter segment:
79
+
80
+ ```txt
81
+ GET /livequery/posts/p1
82
+ ref /livequery/posts/:id
83
+ ```
84
+
85
+ Normalized request:
86
+
87
+ ```ts
88
+ {
89
+ ref: 'posts/p1',
90
+ collection_ref: 'posts',
91
+ schema_collection_ref: 'posts',
92
+ document_id: 'p1',
93
+ keys: { id: 'p1' }
94
+ }
95
+ ```
96
+
97
+ Nested collections and documents follow the same rule:
98
+
99
+ ```txt
100
+ GET /livequery/users/u1/posts/p1
101
+ ref /livequery/users/:uid/posts/:pid
102
+ ```
103
+
104
+ Normalized request:
105
+
106
+ ```ts
107
+ {
108
+ ref: 'users/u1/posts/p1',
109
+ collection_ref: 'users/u1/posts',
110
+ schema_collection_ref: 'users/uid/posts',
111
+ document_id: 'p1',
112
+ keys: { uid: 'u1', pid: 'p1' }
113
+ }
114
+ ```
115
+
116
+ ## Actions
117
+
118
+ The default action is derived from the HTTP method and whether the target is a collection or document.
119
+
120
+ Common conventions:
121
+
122
+ | Request | Meaning |
123
+ | --- | --- |
124
+ | `GET /livequery/posts` | Read a collection. |
125
+ | `GET /livequery/posts/p1` | Read a document. |
126
+ | `POST /livequery/posts` | Create in a collection. |
127
+ | `PATCH /livequery/posts/p1` | Update a document. |
128
+ | `DELETE /livequery/posts/p1` | Delete a document. |
129
+
130
+ Livequery core does not execute these actions by itself. A datasource or custom handler decides how to handle the normalized request.
131
+
132
+ ## Custom Actions
133
+
134
+ A custom action is an application-defined verb appended to the pathname with `~`.
135
+
136
+ Syntax:
137
+
138
+ ```txt
139
+ <livequery-path>~<verb>
140
+ ```
141
+
142
+ Examples:
143
+
144
+ ```txt
145
+ POST /livequery/posts/p1~publish
146
+ POST /livequery/posts/p1~archive
147
+ POST /livequery/posts~export
148
+ POST /livequery/users/u1/posts~reorder
149
+ ```
150
+
151
+ Rules:
152
+
153
+ - Custom actions must use HTTP `POST`.
154
+ - The part before `~` is parsed as the normal Livequery data ref.
155
+ - The part after `~` is the custom action verb.
156
+ - `~` is only meaningful in the pathname.
157
+ - `~` inside a query string is part of the query value and is not a custom action.
158
+
159
+ Example:
160
+
161
+ ```txt
162
+ POST /livequery/posts/p1~publish
163
+ ref /livequery/posts/:id~publish
164
+ ```
165
+
166
+ Conceptual normalized request:
167
+
168
+ ```ts
169
+ {
170
+ method: 'POST',
171
+ action: 'publish',
172
+ ref: 'posts/p1',
173
+ collection_ref: 'posts',
174
+ document_id: 'p1'
175
+ }
176
+ ```
177
+
178
+ Current type definitions may not expose every conceptual field yet. When adding custom action support to implementation code, preserve the rules above and add tests for collection actions, document actions, non-POST rejection, and query strings containing `~`.
179
+
180
+ ## Response Envelope
181
+
182
+ Livequery response payloads are application-defined, but the top-level response envelope is standardized.
183
+
184
+ Successful responses must wrap all payloads in `data`:
185
+
186
+ ```ts
187
+ type LivequerySuccessResponse<T = any> = {
188
+ data: T
189
+ }
190
+ ```
191
+
192
+ Error responses must use `error` with `message` and `code`:
193
+
194
+ ```ts
195
+ type LivequeryErrorResponse = {
196
+ error: {
197
+ message: string
198
+ code: string
199
+ }
200
+ }
201
+ ```
202
+
203
+ Combined shape:
204
+
205
+ ```ts
206
+ type LivequeryResponse<T = any> =
207
+ | { data: T }
208
+ | { error: { message: string; code: string } }
209
+ ```
210
+
211
+ Rules:
212
+
213
+ - A successful response must have `data`.
214
+ - A failed response must have `error.message` and `error.code`.
215
+ - Do not return successful payloads as bare top-level objects.
216
+ - HTTP status belongs to the HTTP response status, not the Livequery error body.
217
+ - A response body should not contain both `data` and `error`.
218
+
219
+ Valid success response:
220
+
221
+ ```json
222
+ {
223
+ "data": {
224
+ "item": {
225
+ "id": "p1",
226
+ "title": "Hello"
227
+ }
228
+ }
229
+ }
230
+ ```
231
+
232
+ Valid error response:
233
+
234
+ ```json
235
+ {
236
+ "error": {
237
+ "message": "Post not found",
238
+ "code": "DOCUMENT_NOT_FOUND"
239
+ }
240
+ }
241
+ ```
242
+
243
+ ## Collection And Document Payloads
244
+
245
+ Livequery payloads are free-form. The following shapes are common helpers, not the only allowed payloads.
246
+
247
+ Collection payload:
248
+
249
+ ```ts
250
+ type CollectionResponse<T> = {
251
+ items: T[]
252
+ paging: {
253
+ current: number
254
+ total: number
255
+ }
256
+ cursor: {
257
+ current: string
258
+ next: string
259
+ prev: string
260
+ }
261
+ }
262
+ ```
263
+
264
+ Document payload:
265
+
266
+ ```ts
267
+ type DocumentResponse<T> = {
268
+ item: T
269
+ }
270
+ ```
271
+
272
+ Wrapped collection response:
273
+
274
+ ```json
275
+ {
276
+ "data": {
277
+ "items": [
278
+ { "id": "p1", "title": "Hello" }
279
+ ],
280
+ "paging": {
281
+ "current": 1,
282
+ "total": 1
283
+ },
284
+ "cursor": {
285
+ "current": "",
286
+ "next": "",
287
+ "prev": ""
288
+ }
289
+ }
290
+ }
291
+ ```
292
+
293
+ Wrapped document response:
294
+
295
+ ```json
296
+ {
297
+ "data": {
298
+ "item": {
299
+ "id": "p1",
300
+ "title": "Hello"
301
+ }
302
+ }
303
+ }
304
+ ```
305
+
306
+ Wrapped custom action response:
307
+
308
+ ```json
309
+ {
310
+ "data": {
311
+ "published": true,
312
+ "id": "p1"
313
+ }
314
+ }
315
+ ```
316
+
317
+ ## Fake Or Non-Database Responses
318
+
319
+ A Livequery handler does not need a database. It only needs to read `ctx.livequery` and return the standard envelope.
320
+
321
+ Example:
322
+
323
+ ```ts
324
+ import type { LivequeryContext, LivequeryHandler } from '@livequery/core'
325
+
326
+ type Post = { id: string; title: string }
327
+
328
+ const posts: Post[] = [
329
+ { id: 'p1', title: 'Hello' },
330
+ { id: 'p2', title: 'World' },
331
+ ]
332
+
333
+ class FakePostsHandler implements LivequeryHandler {
334
+ handle(ctx: LivequeryContext) {
335
+ const lq = ctx.livequery
336
+ if (!lq) return
337
+
338
+ if (lq.method === 'GET' && lq.collection_ref === 'posts' && !lq.document_id) {
339
+ ctx.response = {
340
+ data: {
341
+ items: posts,
342
+ paging: { current: 1, total: 1 },
343
+ cursor: { current: '', next: '', prev: '' },
344
+ },
345
+ }
346
+ return ctx.response
347
+ }
348
+
349
+ if (lq.method === 'GET' && lq.collection_ref === 'posts' && lq.document_id) {
350
+ const item = posts.find(post => post.id === lq.document_id)
351
+ ctx.response = item
352
+ ? { data: { item } }
353
+ : { error: { message: 'Post not found', code: 'DOCUMENT_NOT_FOUND' } }
354
+ return ctx.response
355
+ }
356
+
357
+ ctx.response = {
358
+ error: {
359
+ message: 'Unsupported request',
360
+ code: 'UNSUPPORTED_REQUEST',
361
+ },
362
+ }
363
+ return ctx.response
364
+ }
365
+ }
366
+ ```
367
+
368
+ ## Realtime Updates
369
+
370
+ Realtime updates are emitted to a specific collection ref by calling `WebsocketGateway.next(update)`.
371
+
372
+ Shape:
373
+
374
+ ```ts
375
+ ws.next({
376
+ ref: '<collection_ref>',
377
+ type: '<created|updated|deleted>',
378
+ data: {
379
+ id: '<document_id>',
380
+ ...fields,
381
+ },
382
+ })
383
+ ```
384
+
385
+ Create example:
386
+
387
+ ```ts
388
+ ws.next({
389
+ ref: 'posts',
390
+ type: 'created',
391
+ data: {
392
+ id: 'p1',
393
+ title: 'Hello',
394
+ },
395
+ })
396
+ ```
397
+
398
+ Update example:
399
+
400
+ ```ts
401
+ ws.next({
402
+ ref: 'posts',
403
+ type: 'updated',
404
+ data: {
405
+ id: 'p1',
406
+ title: 'New title',
407
+ },
408
+ })
409
+ ```
410
+
411
+ Delete example:
412
+
413
+ ```ts
414
+ ws.next({
415
+ ref: 'posts',
416
+ type: 'deleted',
417
+ data: {
418
+ id: 'p1',
419
+ },
420
+ })
421
+ ```
422
+
423
+ Nested collection example:
424
+
425
+ ```ts
426
+ ws.next({
427
+ ref: 'users/u1/posts',
428
+ type: 'created',
429
+ data: {
430
+ id: 'p9',
431
+ title: 'Nested post',
432
+ },
433
+ })
434
+ ```
435
+
436
+ Rules:
437
+
438
+ - `ref` should be the collection ref containing the changed document.
439
+ - `data.id` identifies the changed document.
440
+ - A collection update for `posts` with `data.id = 'p1'` notifies subscribers of `posts` and `posts/p1`.
441
+ - A nested collection update for `users/u1/posts` with `data.id = 'p9'` notifies subscribers of `users/u1/posts` and `users/u1/posts/p9`.
442
+
443
+ ## Private Field Sanitization
444
+
445
+ The helper `hidePrivateFields` can sanitize response payloads before wrapping or returning them:
446
+
447
+ - Fields beginning with `_` are removed.
448
+ - `_id` is mapped to `id` when `id` is missing.
449
+ - Plain item, collection payload, and document payload shapes are supported.
450
+
451
+ When using the standard response envelope, sanitize the payload inside `data`.