@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.
- package/LIVEQUERY_SPEC.md +451 -0
- package/README.md +571 -314
- package/build/src/ApiGatewayHandler.d.ts +56 -0
- package/build/src/ApiGatewayHandler.js +210 -0
- package/build/src/ApiGatewayHandler.js.map +1 -0
- package/build/src/ApiGatewayLinker.d.ts +61 -0
- package/build/src/ApiGatewayLinker.js +229 -0
- package/build/src/ApiGatewayLinker.js.map +1 -0
- package/build/src/ApiServiceLinker.d.ts +18 -0
- package/build/src/ApiServiceLinker.js +61 -0
- package/build/src/ApiServiceLinker.js.map +1 -0
- package/build/src/LivequeryContext.d.ts +43 -0
- package/build/src/LivequeryContext.js +2 -0
- package/build/src/LivequeryContext.js.map +1 -0
- package/build/src/LivequeryDatasource.d.ts +8 -0
- package/build/src/LivequeryDatasource.js +2 -0
- package/build/src/LivequeryDatasource.js.map +1 -0
- package/build/src/LivequeryRequestParser.d.ts +13 -0
- package/build/src/LivequeryRequestParser.js +34 -0
- package/build/src/LivequeryRequestParser.js.map +1 -0
- package/build/src/UdpDiscovery.d.ts +26 -0
- package/build/src/UdpDiscovery.js +238 -0
- package/build/src/UdpDiscovery.js.map +1 -0
- package/build/src/WebsocketGateway.d.ts +36 -0
- package/build/src/WebsocketGateway.js +353 -0
- package/build/src/WebsocketGateway.js.map +1 -0
- package/build/src/const.d.ts +8 -0
- package/build/src/const.js +10 -0
- package/build/src/const.js.map +1 -0
- package/build/src/headers.d.ts +9 -0
- package/build/src/headers.js +32 -0
- package/build/src/headers.js.map +1 -0
- package/build/src/helpers/PathHelper.d.ts +11 -0
- package/build/src/helpers/PathHelper.js +41 -0
- package/build/src/helpers/PathHelper.js.map +1 -0
- package/build/src/helpers/hidePrivateFields.d.ts +3 -0
- package/build/src/helpers/hidePrivateFields.js +29 -0
- package/build/src/helpers/hidePrivateFields.js.map +1 -0
- package/build/src/helpers/nodeRequestToWebRequest.d.ts +7 -0
- package/build/src/helpers/nodeRequestToWebRequest.js +17 -0
- package/build/src/helpers/nodeRequestToWebRequest.js.map +1 -0
- package/build/src/helpers/writeWebResponse.d.ts +2 -0
- package/build/src/helpers/writeWebResponse.js +9 -0
- package/build/src/helpers/writeWebResponse.js.map +1 -0
- package/build/src/index.d.ts +9 -0
- package/build/src/index.js +10 -0
- package/build/src/index.js.map +1 -0
- package/build/src/parseLivequeryRequest.d.ts +10 -0
- package/build/src/parseLivequeryRequest.js +17 -0
- package/build/src/parseLivequeryRequest.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +40 -103
- package/dist/LivequeryCollection.d.ts +0 -92
- package/dist/LivequeryCollection.d.ts.map +0 -1
- package/dist/LivequeryCollection.js +0 -231
- package/dist/LivequeryCollection.js.map +0 -1
- package/dist/LivequeryCore.d.ts +0 -67
- package/dist/LivequeryCore.d.ts.map +0 -1
- package/dist/LivequeryCore.js +0 -343
- package/dist/LivequeryCore.js.map +0 -1
- package/dist/LivequeryDocument.d.ts +0 -23
- package/dist/LivequeryDocument.d.ts.map +0 -1
- package/dist/LivequeryDocument.js +0 -22
- package/dist/LivequeryDocument.js.map +0 -1
- package/dist/LivequeryMemoryStorage.d.ts +0 -14
- package/dist/LivequeryMemoryStorage.d.ts.map +0 -1
- package/dist/LivequeryMemoryStorage.js +0 -89
- package/dist/LivequeryMemoryStorage.js.map +0 -1
- package/dist/LivequeryStorge.d.ts +0 -12
- package/dist/LivequeryStorge.d.ts.map +0 -1
- package/dist/LivequeryStorge.js +0 -2
- package/dist/LivequeryStorge.js.map +0 -1
- package/dist/LivequeryTransporter.d.ts +0 -22
- package/dist/LivequeryTransporter.d.ts.map +0 -1
- package/dist/LivequeryTransporter.js +0 -2
- package/dist/LivequeryTransporter.js.map +0 -1
- package/dist/helpers/filterDocs.d.ts +0 -5
- package/dist/helpers/filterDocs.d.ts.map +0 -1
- package/dist/helpers/filterDocs.js +0 -80
- package/dist/helpers/filterDocs.js.map +0 -1
- package/dist/helpers/tryCatch.d.ts +0 -2
- package/dist/helpers/tryCatch.d.ts.map +0 -1
- package/dist/helpers/tryCatch.js +0 -10
- package/dist/helpers/tryCatch.js.map +0 -1
- package/dist/helpers/whenCompleted.d.ts +0 -3
- package/dist/helpers/whenCompleted.d.ts.map +0 -1
- package/dist/helpers/whenCompleted.js +0 -5
- package/dist/helpers/whenCompleted.js.map +0 -1
- package/dist/index.d.ts +0 -9
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -9
- package/dist/index.js.map +0 -1
- package/dist/types.d.ts +0 -70
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- 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`.
|