@omegup/msync 0.1.55 → 0.1.57
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 +388 -0
- package/package.json +11 -2
- package/lib/accumulators/index.d.ts +0 -28
- package/lib/aggregate/$merge.d.ts +0 -37
- package/lib/aggregate/$upsert.d.ts +0 -19
- package/lib/aggregate/group/$group-merge.d.ts +0 -18
- package/lib/aggregate/group/index.d.ts +0 -3
- package/lib/aggregate/group/utils/sub-group.d.ts +0 -6
- package/lib/aggregate/group/utils/sub-merge.d.ts +0 -20
- package/lib/aggregate/index.d.ts +0 -10
- package/lib/aggregate/lookup/$lookup-delta.d.ts +0 -11
- package/lib/aggregate/lookup/$lookup-raw.d.ts +0 -10
- package/lib/aggregate/lookup/index.d.ts +0 -20
- package/lib/aggregate/match/$match-delta.d.ts +0 -4
- package/lib/aggregate/match/$match.d.ts +0 -3
- package/lib/aggregate/match/index.d.ts +0 -2
- package/lib/aggregate/mongo-stages.d.ts +0 -13
- package/lib/aggregate/out.d.ts +0 -37
- package/lib/aggregate/prefix.d.ts +0 -33
- package/lib/aggregate/raws.d.ts +0 -17
- package/lib/aggregate/set/$replace-with-each.d.ts +0 -13
- package/lib/aggregate/set/$set-delta.d.ts +0 -5
- package/lib/aggregate/set/$set.d.ts +0 -9
- package/lib/aggregate/set/index.d.ts +0 -1
- package/lib/aggregate/unwind/$unwind-delta.d.ts +0 -7
- package/lib/aggregate/unwind/index.d.ts +0 -6
- package/lib/boot/boot-simpl.d.ts +0 -14
- package/lib/boot/boot-utils.d.ts +0 -27
- package/lib/boot/boot.d.ts +0 -16
- package/lib/boot/first-stages.d.ts +0 -13
- package/lib/boot/index.d.ts +0 -3
- package/lib/boot/single.d.ts +0 -11
- package/lib/expression/arith.d.ts +0 -33
- package/lib/expression/array.d.ts +0 -30
- package/lib/expression/concat.d.ts +0 -35
- package/lib/expression/date.d.ts +0 -22
- package/lib/expression/expr-base.d.ts +0 -5
- package/lib/expression/index.d.ts +0 -7
- package/lib/expression/logic.d.ts +0 -26
- package/lib/expression/range.d.ts +0 -11
- package/lib/expression/val.d.ts +0 -18
- package/lib/field/field.d.ts +0 -25
- package/lib/field/index.d.ts +0 -1
- package/lib/machine.d.ts +0 -9
- package/lib/predicate/$eq.d.ts +0 -24
- package/lib/predicate/$expr.d.ts +0 -3
- package/lib/predicate/$in.d.ts +0 -2
- package/lib/predicate/index.d.ts +0 -3
- package/lib/predicate/utils.d.ts +0 -5
- package/lib/query/index.d.ts +0 -1
- package/lib/query/logic.d.ts +0 -11
- package/lib/stream/aggregate.d.ts +0 -7
- package/lib/types/accumulator.d.ts +0 -32
- package/lib/types/aggregate.d.ts +0 -9
- package/lib/types/expr.d.ts +0 -27
- package/lib/types/extern.d.ts +0 -4
- package/lib/types/index.d.ts +0 -8
- package/lib/types/lookup.d.ts +0 -19
- package/lib/types/machine.d.ts +0 -17
- package/lib/types/predicate.d.ts +0 -11
- package/lib/types/query.d.ts +0 -12
- package/lib/types/stream.d.ts +0 -82
- package/lib/update/index.d.ts +0 -1
- package/lib/update/updater.d.ts +0 -23
- package/lib/utils/before.d.ts +0 -3
- package/lib/utils/coll.d.ts +0 -5
- package/lib/utils/db-indexes.d.ts +0 -7
- package/lib/utils/guard.d.ts +0 -21
- package/lib/utils/index.d.ts +0 -5
- package/lib/utils/json.d.ts +0 -9
- package/lib/utils/log.d.ts +0 -1
- package/lib/utils/map-object.d.ts +0 -41
- package/lib/utils/merge/combiners.d.ts +0 -6
- package/lib/utils/merge/index.d.ts +0 -2
- package/lib/utils/merge/merge.d.ts +0 -10
- package/lib/utils/merge/next-winner.d.ts +0 -3
- package/lib/utils/merge/types.d.ts +0 -19
- package/lib/utils/merge/utils.d.ts +0 -4
- package/lib/utils/tear-down.d.ts +0 -2
- package/lib/watch.d.ts +0 -30
- package/test/mongodb.d.ts +0 -9
- package/test/uri.d.ts +0 -1
- package/types/class.d.ts +0 -1
- package/types/extern.d.ts +0 -5
- package/types/global-aug.d.ts +0 -48
- package/types/global.d.ts +0 -2
- package/types/hkt.d.ts +0 -56
- package/types/index.d.ts +0 -6
- package/types/json.d.ts +0 -54
- package/types/mongo.d.ts +0 -24
- package/types/view.d.ts +0 -35
package/README.md
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
# @omegup/msync
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@omegup/msync)
|
|
4
|
+
|
|
5
|
+
`msync` keeps derived MongoDB data in sync with the documents it comes from.
|
|
6
|
+
|
|
7
|
+
You describe the derivation the way you would write an aggregation: project the fields you read, join, reshape, group, and write the result back. `msync` runs that description once over the existing data, then again on every change. Joins, sums, and counts update from the delta. A removed source undoes the write that depended on it. A changed source rewrites only the documents that depend on it.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @omegup/msync
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Peer runtime: MongoDB with change streams (a replica set), and TypeScript. `msync` enables change-stream pre- and post-images on every collection it watches.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { Machine, from, staging } from '@omegup/msync'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## The principle
|
|
22
|
+
|
|
23
|
+
A stream is a named, typed function from source documents to stored documents.
|
|
24
|
+
|
|
25
|
+
1. **Sources are ordinary collections.** Every document `msync` reads or writes is a `Model`: it has `_id`, `touchedAt`, and `deletedAt`. `touchedAt` is the cluster time of the last write. `deletedAt: null` means the document is alive; a timestamp means it was removed. Deletes are soft, so a stream can see that a document went away and undo what it had written.
|
|
26
|
+
|
|
27
|
+
2. **You declare the slice you read.** A view is a collection plus a projection. The projection is the only data the rest of the pipeline is allowed to see, and the types follow it.
|
|
28
|
+
|
|
29
|
+
3. **Stages are valid as a full pass and as a delta.** `$lookup`, `$set`, `$replaceWith`, `$match`, and `$unwind` each know how to run over a snapshot and how to run over a before/after pair. You write them once.
|
|
30
|
+
|
|
31
|
+
4. **The last stage writes.** `$simpleMerge` and `$merge` patch fields onto an existing document. `$group` and `$groupId` fold many documents into one and keep the fold incremental. `$insert` creates documents in another collection and soft-deletes them when the source disappears.
|
|
32
|
+
|
|
33
|
+
5. **The name is the identity of the job.** Progress lives in a `__last` document keyed by that name. If the pipeline you registered under the name changes, `msync` tears the previous output down (unsets the fields it wrote, or soft-deletes the documents it inserted) and rebuilds.
|
|
34
|
+
|
|
35
|
+
6. **Streams compose by reading each other's fields.** A field whose name starts with `_` is treated as derived. A later stream that projects `_accountName` waits until that field exists, so you can add every stream to one machine and they still line up.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
source collection
|
|
39
|
+
│ change stream (pre-image + post-image)
|
|
40
|
+
▼
|
|
41
|
+
view ── projection, optional match
|
|
42
|
+
│
|
|
43
|
+
stages ── lookup, set, replace, match, unwind
|
|
44
|
+
│
|
|
45
|
+
sink ── merge | group | insert
|
|
46
|
+
│
|
|
47
|
+
▼
|
|
48
|
+
same collection, or another one
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Two ways in:
|
|
52
|
+
|
|
53
|
+
| | `from` | `staging` |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| Use it when | The current document is enough, and you patch it in place | You join, group, insert, or need the previous value |
|
|
56
|
+
| What it stores | The resume token in `__last` | A `{collection}_{name}_snapshot` of before/after, plus `__last` |
|
|
57
|
+
| Sink | `$simpleMerge`, `$simpleInsert` | `$merge`, `$group`, `$groupId`, `$insert` |
|
|
58
|
+
| Can be the right side of `$lookup` | | Yes, after `.get()` |
|
|
59
|
+
|
|
60
|
+
`single` builds the same snapshot pipeline without the watch loop, for a one-shot read.
|
|
61
|
+
|
|
62
|
+
## A document
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import type { Model } from '@omegup/msync'
|
|
66
|
+
|
|
67
|
+
// Model is { _id, touchedAt, deletedAt? } intersected with your fields.
|
|
68
|
+
type Account = {
|
|
69
|
+
_id: string
|
|
70
|
+
name: string
|
|
71
|
+
createdAt: Date
|
|
72
|
+
_cohort?: string | null // written by a stream, e.g. "2026-03"
|
|
73
|
+
} & Model
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Your application sets `touchedAt` on every write (including its own updates) and sets `deletedAt` when it removes a document. `msync` does the same on the documents it creates. Bump `touchedAt` with the cluster time; streams resume from it.
|
|
77
|
+
|
|
78
|
+
Derived fields use a leading `_`. That is how a downstream stream knows the field is produced by an upstream one and should be waited for. Pass `needs: { plainField: 1 }` to wait on a field that does not use the prefix, or `needs: { _alreadyThere: 0 }` to read a `_` field without waiting.
|
|
79
|
+
|
|
80
|
+
## The shape of every stream
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
staging(view, 'unique-stream-name')
|
|
84
|
+
.with($lookup({ /* join, needs the left-hand stream */ }))
|
|
85
|
+
.then($replaceWith(/* reshape the current document */))
|
|
86
|
+
.then($match(/* keep or drop */))
|
|
87
|
+
.get() // freeze the pipeline; also what $lookup expects as `from`
|
|
88
|
+
.out($merge(/* ... */)(/* write */))
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- `.with(fn)` wraps the stream so far. `$lookup` and `$outerLookup` are written this way, because a join has to see the left side.
|
|
92
|
+
- `.then(stage)` appends a stage that only sees the current document: `$set`, `$replaceWith`, `$match`, `$unwind`.
|
|
93
|
+
- `.get()` closes the pipeline.
|
|
94
|
+
- `.out(sink)` attaches the writer and returns a runner.
|
|
95
|
+
|
|
96
|
+
`from` has the same chain, and its stages are linear (`$set`, `$replaceWith`, `$match`). It has no snapshot, so it has no `$lookup`.
|
|
97
|
+
|
|
98
|
+
A projection entry is a pair, `[fieldName, 1]`, under the same name:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
projection: {
|
|
102
|
+
createdAt: ['createdAt', 1],
|
|
103
|
+
regionId: ['regionId', 1],
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
A field expression is a pair too, `[fieldName, expr]`, inside `field({ ... })` and inside merge and group maps.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
root<Account>().of('createdAt').expr() // this document's field
|
|
111
|
+
val(' ') // a literal
|
|
112
|
+
concat(first, val(' '), last) // an expression
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`root<T>()` is the current document, typed as `T`. `.of('key')` steps into a field. `.expr()` turns the path into a value you can pass to `concat`, `$sum`, `$merge`, and the rest.
|
|
116
|
+
|
|
117
|
+
## Tutorial
|
|
118
|
+
|
|
119
|
+
One small domain runs through every example: accounts place orders, and plans are sold per region. Each function takes the collections it touches and returns a runner. The runners are what you hand to a `Machine`.
|
|
120
|
+
|
|
121
|
+
### 1. Derive a field from the document itself
|
|
122
|
+
|
|
123
|
+
An account has `createdAt`. List pages want the cohort (`YYYY-MM`) without parsing dates on every read. Keep `_cohort` on the account, and refresh it when `createdAt` changes.
|
|
124
|
+
|
|
125
|
+
`from` is enough: there is no join and no previous value to subtract.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import type { Collection, Model, N, O } from '@omegup/msync'
|
|
129
|
+
import { $simpleMerge, from, monthPart, root } from '@omegup/msync'
|
|
130
|
+
|
|
131
|
+
type Account = O<{
|
|
132
|
+
_id: string
|
|
133
|
+
name: string
|
|
134
|
+
createdAt: Date
|
|
135
|
+
_cohort: string | N
|
|
136
|
+
}>
|
|
137
|
+
|
|
138
|
+
export const makeAccountCohortStream = (accounts: Collection<Account & Model>) =>
|
|
139
|
+
from(
|
|
140
|
+
{
|
|
141
|
+
collection: accounts,
|
|
142
|
+
projection: { createdAt: ['createdAt', 1] },
|
|
143
|
+
},
|
|
144
|
+
'account-cohort',
|
|
145
|
+
)
|
|
146
|
+
.get()
|
|
147
|
+
.out(
|
|
148
|
+
$simpleMerge<O<{ _cohort: string }>>()(accounts, {
|
|
149
|
+
_cohort: ['_cohort', monthPart(root<Account>().of('createdAt').expr())],
|
|
150
|
+
}),
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`$simpleMerge` writes `_cohort` onto the same `_id`. The document is already there, so a missing target fails the merge. The next change of `createdAt` rewrites that account only.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
{ _id: "a1", createdAt: 2026-03-02 } → { _cohort: "2026-03" }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 2. Copy a field across a reference
|
|
161
|
+
|
|
162
|
+
An order stores `accountId`. The account stores `name`. Reading an order should not look the account up again, and renaming an account should fix every order that points at it.
|
|
163
|
+
|
|
164
|
+
This needs `staging`. `$lookup` watches both sides: the order's `accountId`, and the account's `name`.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import type { Collection, Model, O, OPick, StrKey } from '@omegup/msync'
|
|
168
|
+
import { $lookup, $merge, root, staging } from '@omegup/msync'
|
|
169
|
+
|
|
170
|
+
type Account = O<{ _id: string; name: string }>
|
|
171
|
+
type Order = O<{ _id: string; accountId: string; total: number; _accountName?: string }>
|
|
172
|
+
|
|
173
|
+
type OrderView = OPick<Order & Model, '_id' | 'accountId'>
|
|
174
|
+
type AccountView = OPick<Account & Model, '_id' | 'name'>
|
|
175
|
+
type Joined = OrderView & { readonly account: AccountView }
|
|
176
|
+
|
|
177
|
+
export const makeOrderAccountNameStream = (
|
|
178
|
+
orders: Collection<Order & Model>,
|
|
179
|
+
accounts: Collection<Account & Model>,
|
|
180
|
+
) =>
|
|
181
|
+
staging<Order & Model, StrKey<OrderView>>(
|
|
182
|
+
{
|
|
183
|
+
collection: orders,
|
|
184
|
+
projection: { accountId: ['accountId', 1] },
|
|
185
|
+
},
|
|
186
|
+
'order-account-name',
|
|
187
|
+
)
|
|
188
|
+
.with(
|
|
189
|
+
$lookup({
|
|
190
|
+
as: 'account',
|
|
191
|
+
to: 'one',
|
|
192
|
+
from: staging(
|
|
193
|
+
{
|
|
194
|
+
collection: accounts,
|
|
195
|
+
projection: { name: ['name', 1] },
|
|
196
|
+
},
|
|
197
|
+
'accounts-for-orders',
|
|
198
|
+
).get(),
|
|
199
|
+
localField: root<OrderView>().of('accountId'),
|
|
200
|
+
}),
|
|
201
|
+
)
|
|
202
|
+
.get()
|
|
203
|
+
.out(
|
|
204
|
+
$merge<O<{ _accountName: string }>>()(orders, {
|
|
205
|
+
_accountName: ['_accountName', root<Joined>().of('account').of('name').expr()],
|
|
206
|
+
}),
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The inner `staging(...).get()` is a stream of its own, with its own snapshot. `$merge` patches `_accountName` back onto the order. A later stream that projects `_accountName` waits until this one has written it.
|
|
211
|
+
|
|
212
|
+
`$lookup` keeps documents that match. `$outerLookup` keeps the left document when the right side is missing, and types the joined field as `T | null`.
|
|
213
|
+
|
|
214
|
+
A lookup says where it points with `to`. `to: 'one'` means `localField` points at the right document's `_id`, so there is no `foreignField`. Each left row matches one right row, and the result keeps the left `_id`. `to: 'many'` is several matches per left row, or a join on some other field: pass `middle` and both fields. The lookup writes each row's `_id` as the left id, that string, and the right id. You do not build that id again afterwards.
|
|
215
|
+
|
|
216
|
+
### 3. Keep a total on the parent
|
|
217
|
+
|
|
218
|
+
Each order has an `accountId` and a `total`. The account should store `_lifetimeSpend`, the sum of its orders, including orders that change or disappear.
|
|
219
|
+
|
|
220
|
+
`$groupId` folds into a document that already exists. The group key is that document's `_id`, here the account id. `$sum` is delta-aware: a new order adds its total, an edited order subtracts the old total and adds the new one, a deleted order subtracts. The account is never recomputed from scratch.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import type { Collection, Model, O } from '@omegup/msync'
|
|
224
|
+
import { $groupId, $sum, notNull, root, staging } from '@omegup/msync'
|
|
225
|
+
|
|
226
|
+
type Account = O<{ _id: string; _lifetimeSpend?: number }>
|
|
227
|
+
type Order = O<{ _id: string; accountId: string; total: number }>
|
|
228
|
+
|
|
229
|
+
export const makeAccountSpendStream = (
|
|
230
|
+
orders: Collection<Order & Model>,
|
|
231
|
+
accounts: Collection<Account & Model>,
|
|
232
|
+
) =>
|
|
233
|
+
staging<Order & Model, '_id' | 'accountId' | 'total'>(
|
|
234
|
+
{
|
|
235
|
+
collection: orders,
|
|
236
|
+
projection: {
|
|
237
|
+
accountId: ['accountId', 1],
|
|
238
|
+
total: ['total', 1],
|
|
239
|
+
},
|
|
240
|
+
match: notNull(root<Order>().of('accountId').expr()),
|
|
241
|
+
},
|
|
242
|
+
'account-lifetime-spend',
|
|
243
|
+
)
|
|
244
|
+
.get()
|
|
245
|
+
.out(
|
|
246
|
+
$groupId<Order, O<{ _lifetimeSpend: number }>, {}, Account & Model>(
|
|
247
|
+
root<Order>().of('accountId').expr(),
|
|
248
|
+
{
|
|
249
|
+
_lifetimeSpend: ['_lifetimeSpend', $sum(root<Order>().of('total').expr())],
|
|
250
|
+
},
|
|
251
|
+
accounts,
|
|
252
|
+
{},
|
|
253
|
+
),
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Read `$groupId` as four arguments:
|
|
258
|
+
|
|
259
|
+
1. The group key, a string expression. It must equal `_id` on the target.
|
|
260
|
+
2. Accumulators. Each value is `['field', accumulator]`. These fields are maintained incrementally on the target.
|
|
261
|
+
3. The target collection.
|
|
262
|
+
4. Extra fields, recomputed from the accumulator output on every update. `{}` when the accumulator value is already what you want to store.
|
|
263
|
+
|
|
264
|
+
`$group` is the same fold when the target document does not exist yet. It stores the key in `_grp` and inserts. Use it for a summary collection, such as revenue per month. Use `$groupId` to hang a summary on a document you already have, such as spend on an account.
|
|
265
|
+
|
|
266
|
+
When the thing you are counting is a set of strings rather than a number, use `$countDict`. It stores a map of string to count, drops keys whose count falls to zero, and pairs with `$keys` in the extra-fields argument to publish the surviving strings as an array. A product that references tag ids, joined to the tag's name and folded with `$countDict`, keeps `_tags` on the product this way. The [guide](docs/guide.md) shows that accumulator next to `$sum`.
|
|
267
|
+
|
|
268
|
+
### 4. Materialize a collection of pairs
|
|
269
|
+
|
|
270
|
+
Plans are sold per region. Every account in a region is entitled to every plan in that region. Each pair is its own document in `entitlements`, with `_id` of `accountId-planId`, so other streams can aggregate entitlements directly. When the plan's price changes, the entitlement updates. When the account or the plan goes away, the entitlement is soft-deleted.
|
|
271
|
+
|
|
272
|
+
Several plans can share a region, so this lookup is `to: 'many'`. `middle: '-'` is required, and the lookup sets each row's `_id` to the account id, that hyphen, and the plan id. Copy `accountId` off `_id` before the join, because the composite id replaces it. `$replaceWith` keeps the id the lookup already assigned. `$insert` upserts by that `_id`, and on a deleted input it sets `deletedAt`.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import type { Collection, ID, Model, O, OPick } from '@omegup/msync'
|
|
276
|
+
import { $insert, $lookup, $replaceWith, $set, field, root, staging, to } from '@omegup/msync'
|
|
277
|
+
|
|
278
|
+
type Account = O<{ _id: string; regionId: string }>
|
|
279
|
+
type Plan = O<{ _id: string; regionId: string; price: number }>
|
|
280
|
+
type Entitlement = O<{
|
|
281
|
+
_id: string
|
|
282
|
+
accountId: string
|
|
283
|
+
planId: string
|
|
284
|
+
price: number
|
|
285
|
+
}>
|
|
286
|
+
|
|
287
|
+
export const makeEntitlementStream = (
|
|
288
|
+
accounts: Collection<Account & Model>,
|
|
289
|
+
plans: Collection<Plan & Model>,
|
|
290
|
+
entitlements: Collection<Entitlement & Model>,
|
|
291
|
+
) => {
|
|
292
|
+
type AccountView = OPick<Account & Model, keyof ID | 'regionId'>
|
|
293
|
+
type PlanView = OPick<Plan & Model, keyof ID | 'regionId' | 'price'>
|
|
294
|
+
type WithAccountId = AccountView & O<{ accountId: string }>
|
|
295
|
+
type Joined = WithAccountId & { readonly plan: PlanView }
|
|
296
|
+
|
|
297
|
+
return staging<Account & Model, keyof AccountView>(
|
|
298
|
+
{
|
|
299
|
+
collection: accounts,
|
|
300
|
+
projection: { regionId: ['regionId', 1] },
|
|
301
|
+
},
|
|
302
|
+
'entitlements-from-accounts',
|
|
303
|
+
)
|
|
304
|
+
.then(
|
|
305
|
+
$set<O<{ accountId: string }>>()({
|
|
306
|
+
accountId: ['accountId', to(root<AccountView>().of('_id').expr())],
|
|
307
|
+
}),
|
|
308
|
+
)
|
|
309
|
+
.with(
|
|
310
|
+
$lookup({
|
|
311
|
+
as: 'plan',
|
|
312
|
+
to: 'many',
|
|
313
|
+
middle: '-',
|
|
314
|
+
from: staging(
|
|
315
|
+
{
|
|
316
|
+
collection: plans,
|
|
317
|
+
projection: {
|
|
318
|
+
regionId: ['regionId', 1],
|
|
319
|
+
price: ['price', 1],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
'plans-for-entitlements',
|
|
323
|
+
).get(),
|
|
324
|
+
localField: root<WithAccountId>().of('regionId'),
|
|
325
|
+
foreignField: root<PlanView>().of('regionId'),
|
|
326
|
+
}),
|
|
327
|
+
)
|
|
328
|
+
.then(
|
|
329
|
+
$replaceWith(
|
|
330
|
+
field<Entitlement, Joined>({
|
|
331
|
+
_id: ['_id', root<Joined>().of('_id').expr()],
|
|
332
|
+
accountId: ['accountId', root<Joined>().of('accountId').expr()],
|
|
333
|
+
planId: ['planId', root<Joined>().of('plan').of('_id').expr()],
|
|
334
|
+
price: ['price', root<Joined>().of('plan').of('price').expr()],
|
|
335
|
+
}),
|
|
336
|
+
),
|
|
337
|
+
)
|
|
338
|
+
.get()
|
|
339
|
+
.out($insert(entitlements))
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The same account and the same plan always produce the same `_id`, because `middle` is part of the lookup. That is how the next run finds the row to update or retire.
|
|
344
|
+
|
|
345
|
+
## Run them together
|
|
346
|
+
|
|
347
|
+
A `Machine` merges runners and loops. Each tick reports which stream did the work.
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import { Machine, prepare } from '@omegup/msync'
|
|
351
|
+
|
|
352
|
+
const client = await prepare() // connects with MONGO_URL, or use your own MongoClient
|
|
353
|
+
const db = client.db(process.env.MONGO_NAME)
|
|
354
|
+
|
|
355
|
+
const accounts = db.collection('accounts')
|
|
356
|
+
const orders = db.collection('orders')
|
|
357
|
+
const plans = db.collection('plans')
|
|
358
|
+
const entitlements = db.collection('entitlements')
|
|
359
|
+
|
|
360
|
+
const machine = new Machine()
|
|
361
|
+
machine.add(makeAccountCohortStream(accounts))
|
|
362
|
+
machine.add(makeOrderAccountNameStream(orders, accounts))
|
|
363
|
+
machine.add(makeAccountSpendStream(orders, accounts))
|
|
364
|
+
machine.add(makeEntitlementStream(accounts, plans, entitlements))
|
|
365
|
+
|
|
366
|
+
await machine.start(info => {
|
|
367
|
+
console.log(new Date(), info.debug)
|
|
368
|
+
})
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`.start` resolves only when it stops. Return `true` from the callback to stop after the current tick. Add streams in an order you can read — producers of `_` fields before the streams that project them — then let the machine run them side by side. The `_` prefix (and `needs`) is what makes a consumer skip a document until the producer has filled it.
|
|
372
|
+
|
|
373
|
+
Stream names are unique inside the process. Reusing a name from two different call sites throws. Pick a name that says what the stream maintains: `'account-cohort'`, `'order-account-name'`.
|
|
374
|
+
|
|
375
|
+
Split a large sync by area. Each area builds a `Machine` and returns it. The process adds every area and starts once:
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
const machine = new Machine()
|
|
379
|
+
machine.add(billing(client).runner())
|
|
380
|
+
machine.add(catalog(client).runner())
|
|
381
|
+
await machine.start(info => {
|
|
382
|
+
console.log(new Date(), info.debug)
|
|
383
|
+
})
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Next
|
|
387
|
+
|
|
388
|
+
[docs/guide.md](docs/guide.md) is the rest of the language: expressions, predicates, accumulators, the write modes side by side, and what the process stores while it runs.
|
package/package.json
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
+
"name": "@omegup/msync",
|
|
3
|
+
"version": "0.1.57",
|
|
4
|
+
"description": "Keeps derived MongoDB data in sync with the documents it comes from.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Omegup/msync.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://omegup.github.io/msync/",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/Omegup/msync/issues"
|
|
12
|
+
},
|
|
2
13
|
"main": "index.js",
|
|
3
14
|
"module": "index.esm.js",
|
|
4
15
|
"typings": "index.d.ts",
|
|
5
|
-
"name": "@omegup/msync",
|
|
6
|
-
"version": "0.1.55",
|
|
7
16
|
"dependencies": {
|
|
8
17
|
"dayjs": "^1.11.9",
|
|
9
18
|
"dotenv": "^16.3.1",
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { AppMap, N, O, PRRec } from '../../types';
|
|
2
|
-
import type { ExprHKT } from '../expression/concat';
|
|
3
|
-
import { type AddO } from '../expression/val';
|
|
4
|
-
import type { DeltaAccumulator, Expr, Part } from '../types';
|
|
5
|
-
type AddOs<A extends readonly unknown[]> = {
|
|
6
|
-
[K in keyof A]: AddO<A[K]>;
|
|
7
|
-
};
|
|
8
|
-
type PushSides<V> = {
|
|
9
|
-
0: V[];
|
|
10
|
-
1: V[];
|
|
11
|
-
};
|
|
12
|
-
type PushDict<V> = PRRec<string, PushSides<V>>;
|
|
13
|
-
export declare const $sum: <D extends O, C = unknown>(expr: Expr<number | N, D, C>) => DeltaAccumulator<D, number, C>;
|
|
14
|
-
export declare const $accumulator: <D, T, Ctx, A extends readonly unknown[]>(init: () => T, accumulateArgs: AppMap<ExprHKT<Part<D>, Ctx>, AddOs<A>>, accumulate: (a: T, ...args: A) => T, merge: (...args: [T | N, T]) => T) => DeltaAccumulator<D, AddO<T>, Ctx>;
|
|
15
|
-
export declare const $countDict: <D extends O, K extends string = string, C = unknown>(expr: Expr<AddO<K>, D, C>) => DeltaAccumulator<D, AddO<PRRec<K, number>>, C>;
|
|
16
|
-
export declare const $countDictArray: <D extends O, K extends string = string, C = unknown>(expr: Expr<AddO<K[]>, D, C>) => DeltaAccumulator<D, AddO<PRRec<K, number>>, C>;
|
|
17
|
-
export declare const $pushDict: <D extends O, V, C = unknown>(key: Expr<string, D, C>, value: Expr<AddO<V>, D, C>) => DeltaAccumulator<D, O<{
|
|
18
|
-
[x: string]: O<{
|
|
19
|
-
0: import("../..").Arr<AddO<V>>;
|
|
20
|
-
1: import("../..").Arr<AddO<V>>;
|
|
21
|
-
}> | undefined;
|
|
22
|
-
}>, C>;
|
|
23
|
-
export declare const $keys: <D extends O, K extends string = string, C = unknown>(expr: Expr<AddO<PRRec<K, number>>, D, C>) => Expr<AddO<K[]>, D, C>;
|
|
24
|
-
export declare const $entries: <D extends O, V, C = unknown>(expr: Expr<AddO<PushDict<V>>, D, C>) => Expr<AddO<{
|
|
25
|
-
k: string;
|
|
26
|
-
v: V;
|
|
27
|
-
}[]>, D, C>;
|
|
28
|
-
export {};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { IdHKT, OPick, RWCollection, Timestamp } from '../../types';
|
|
2
|
-
import type { doc, ID, N, O, rawItem, Rec, Replace, RORec, StrKey } from '../../types/json';
|
|
3
|
-
import { type ExprsExact } from '../expression/concat';
|
|
4
|
-
import type { Delta, Expr, IsDeleted, Model, RawStages, StreamRunnerParam, TS } from '../types';
|
|
5
|
-
import { type Exact } from '../utils/map-object';
|
|
6
|
-
type OutInputE<T, E, A = T | null> = ID & Rec<'after', A> & E;
|
|
7
|
-
type Allowed<K extends string> = Exclude<K, keyof (TS & ID)>;
|
|
8
|
-
export type Patch<V, KK extends StrKey<V> = StrKey<V>> = ((OPick<V, Allowed<KK>> & ID) | (Rec<Allowed<KK>, N> & ID)) & TS;
|
|
9
|
-
type TakeDoc<V, E = ID, KK extends StrKey<V> = StrKey<V>> = OPick<V, Allowed<KK>> & E;
|
|
10
|
-
type ND = {
|
|
11
|
-
readonly deletedAt?: null;
|
|
12
|
-
};
|
|
13
|
-
type SafeE<E> = Omit<E, `$${string}` | keyof ID>;
|
|
14
|
-
type Args<P, Out> = [added: Expr<P, unknown, {
|
|
15
|
-
new: P;
|
|
16
|
-
}>, old: Expr<Out, Out>];
|
|
17
|
-
type Result<P> = Expr<P, unknown, {
|
|
18
|
-
new: P;
|
|
19
|
-
}>;
|
|
20
|
-
type Update<P, Out> = (...args: Args<P, Out>) => Result<P>;
|
|
21
|
-
type UpdateF<P, Out> = <K extends keyof IsDeleted>(...args: Args<Replace<P, RORec<K, Timestamp>>, Out>) => Result<Replace<P, RORec<K, Timestamp>>>;
|
|
22
|
-
export declare const getWhenMatchedForMerge: <Out extends Model, P extends Model, K extends keyof IsDeleted>(whenNotMatched: "discard" | "fail" | "insert", update: Update<Replace<P, RORec<K, Timestamp>>, Out>) => RawStages<O, Out, Out | Replace<Out, P>, RORec<"new", Replace<P, RORec<K, Timestamp>>>>;
|
|
23
|
-
export declare const getWhenMatched: <Out extends Model, P extends Model, K extends keyof IsDeleted>(whenNotMatched: "discard" | "fail" | "insert") => RawStages<O, Rec<"old" | "merged", Out | Replace<Out, P>>, Out | Replace<Out, P>>;
|
|
24
|
-
type MergeCollection<V extends O, Out extends Model> = {
|
|
25
|
-
coll: RWCollection<Out | Replace<Out, Patch<V>> | Replace<Patch<V>, IsDeleted>, Out>;
|
|
26
|
-
whenNotMatched: 'discard';
|
|
27
|
-
} | {
|
|
28
|
-
coll: RWCollection<Out | Replace<Out, Patch<V>>, Out>;
|
|
29
|
-
whenNotMatched: 'fail';
|
|
30
|
-
};
|
|
31
|
-
type MergeUpdate<V, Out> = UpdateF<((OPick<V, Allowed<StrKey<V>>> & ID) | (Rec<Allowed<StrKey<V>>, N> & ID)) & TS, Out>;
|
|
32
|
-
export declare const $mergeId: <V extends O>() => <SourcePart extends doc, Out extends Model, E = unknown, EEE extends RORec<string, rawItem> = {}>(out: MergeCollection<V, Out>, keys: ExprsExact<TakeDoc<V, unknown>, SourcePart>, id: Expr<string, OutInputE<TakeDoc<V>, E, null>>, ext: Exact<Omit<SafeE<EEE>, keyof (ND & TS)>, IdHKT>, update?: MergeUpdate<V, Out>) => StreamRunnerParam<OutInputE<SourcePart, E>, "out">;
|
|
33
|
-
export declare const $simpleMergePart: <V extends O>() => <Source extends doc, Out extends Model, EEE extends RORec<string, rawItem>>(out: MergeCollection<V, Out>, keys: ExprsExact<TakeDoc<V, unknown>, Source>, ext: Exact<Omit<SafeE<EEE>, keyof (ND & TS)>, IdHKT>, update?: MergeUpdate<V, Out>) => StreamRunnerParam<Source, "out">;
|
|
34
|
-
export declare const $simpleMerge: <V extends O>() => <Source extends doc, Out extends Model>(out: RWCollection<Out | Replace<Out, Patch<V>> | Replace<Patch<V>, IsDeleted>, Out>, keys: ExprsExact<TakeDoc<V, unknown>, Source>, whenNotMatched?: "fail" | "discard", update?: MergeUpdate<V, Out>) => StreamRunnerParam<Source, "out">;
|
|
35
|
-
export declare const $mergePart: <V extends O>() => <Out extends Model, SourcePart extends doc, EEE extends RORec<string, rawItem>>(out: RWCollection<Out | Replace<Out, Patch<V>>, Out>, keys: ExprsExact<TakeDoc<V, unknown>, SourcePart>, ext: Exact<Omit<SafeE<EEE>, keyof (ND & TS)>, IdHKT>, update?: MergeUpdate<V, Out>) => StreamRunnerParam<Delta<SourcePart>, "out">;
|
|
36
|
-
export declare const $merge: <V extends O>() => <Out extends Model, SourcePart extends doc>(out: RWCollection<Out | Replace<Out, Patch<V>>, Out>, keys: ExprsExact<TakeDoc<V, unknown>, SourcePart>, update?: MergeUpdate<V, Out>) => StreamRunnerParam<Delta<SourcePart>, "out">;
|
|
37
|
-
export {};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { IdHKT, Replace, RWCollection } from '../../types';
|
|
2
|
-
import type { ID, O, RORec, doc, rawItem } from '../../types/json';
|
|
3
|
-
import { type ExprsExact } from '../expression/concat';
|
|
4
|
-
import type { Del, Delta, Expr, StreamRunnerParam, TS } from '../types';
|
|
5
|
-
import { type Exact } from '../utils/map-object';
|
|
6
|
-
type ND = {
|
|
7
|
-
readonly deletedAt?: null;
|
|
8
|
-
};
|
|
9
|
-
type SafeE<E> = Omit<E, `$${string}` | keyof ID>;
|
|
10
|
-
export type Merge<T extends doc, E> = Omit<SafeE<E>, keyof (ND & TS)> & ((T & ND & TS) | Del);
|
|
11
|
-
type ExprP<P> = Expr<P, unknown, {
|
|
12
|
-
new: P;
|
|
13
|
-
}>;
|
|
14
|
-
type Update<P, Out> = (added: ExprP<P>, old: Expr<Out, Out>) => ExprP<P>;
|
|
15
|
-
export declare const $insertX: <T extends doc, D extends O, EEE extends RORec<string, rawItem>>(out: RWCollection<Merge<T, EEE>>, expr: Expr<T, D>, map: (x: Expr<T & ND & TS & Omit<SafeE<EEE>, keyof (ND & TS)>, D>) => Expr<Merge<T, EEE>, D>, ext: Exact<Omit<SafeE<EEE>, keyof (ND & TS)>, IdHKT>, extExpr: ExprsExact<Omit<SafeE<EEE>, keyof (ND & TS)>, unknown>, update?: Update<Replace<Merge<T, EEE>, {}>, Merge<T, EEE>>) => StreamRunnerParam<D, "out">;
|
|
16
|
-
export declare const $simpleInsert: <T extends doc>(out: RWCollection<Merge<T, {}>>) => StreamRunnerParam<T, "out">;
|
|
17
|
-
export declare const $insertPart: <T extends doc, EEE extends RORec<string, rawItem>>(out: RWCollection<Merge<T, EEE>>, ext: Exact<Omit<SafeE<EEE>, keyof (ND & TS)>, IdHKT>) => StreamRunnerParam<Delta<T>, "out">;
|
|
18
|
-
export declare const $insert: <T extends doc>(out: RWCollection<Merge<T, {}>>) => StreamRunnerParam<Delta<T>, "out">;
|
|
19
|
-
export {};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { RWCollection, WriteonlyCollection } from '../../../types';
|
|
2
|
-
import type { AsLiteral, ID, O, Rec, doc, notArr } from '../../../types/json';
|
|
3
|
-
import { type ExprsExact } from '../../expression/concat';
|
|
4
|
-
import type { Delta, DeltaAccumulators, Expr, StreamRunnerParam, TS } from '../../types';
|
|
5
|
-
import type { MergeInto } from '../out';
|
|
6
|
-
import { type Extra, type IdAndTsKeys, type Loose, type MergedInput, type Strict, type V_Grp } from './utils/sub-merge';
|
|
7
|
-
type Denied<GID = never> = keyof (TS & ID) | GID;
|
|
8
|
-
type GI<GG> = Exclude<GG, keyof TS>;
|
|
9
|
-
export type GroupMergeOut<Grp extends notArr, V extends O, GG extends string, EE = {}, Out extends Loose<Grp, V, GG> = Loose<Grp, V, GG>> = MergeInto<Strict<Grp, V, GG, EE>, Out, WriteonlyCollection<MergedInput<Out, V, Grp, GG, EE>>>;
|
|
10
|
-
export type GroupMergeCollection<WhenNotMatched extends MergeInto<never, never>['whenNotMatched'], Grp extends notArr, V extends O, GG extends string, EE = {}, Out extends Loose<Grp, V, GG> = Loose<Grp, V, GG>> = (GroupMergeOut<Grp, V, GG, EE, Out> & {
|
|
11
|
-
whenNotMatched: WhenNotMatched;
|
|
12
|
-
})['into'];
|
|
13
|
-
export declare const $groupMerge: <T extends O, Grp extends notArr, V extends O, GG extends string, EE = {}, Out extends Loose<Grp, V, GG> = Loose<Grp, V, GG>>(id: Expr<Grp, T>, args: DeltaAccumulators<T, O & Omit<V, Denied<GI<GG>>>>, out: GroupMergeOut<Grp, V, GG, EE, Out>, gid: AsLiteral<GI<GG>>, extra: ExprsExact<Extra<EE, V, GG>, V_Grp<V, GG, Grp>>, idPrefix?: string) => StreamRunnerParam<Delta<T>, "out">;
|
|
14
|
-
export type GroupIdCollection<V extends O, EE = {}, Out extends Loose<string, V, '_id'> = Loose<string, V, '_id'>> = RWCollection<MergedInput<Out, V, string, '_id', EE>, Out>;
|
|
15
|
-
export declare const $groupId: <T extends O, V extends O, EE = {}, Out extends Loose<string, V, "_id"> = Loose<string, V, "_id">>(id: Expr<string, T>, args: DeltaAccumulators<T, O & Omit<V, Denied>>, out: GroupIdCollection<V, EE, Out>, extra: ExprsExact<Omit<EE, IdAndTsKeys | keyof Omit<V, IdAndTsKeys>>, doc & Omit<V, IdAndTsKeys>>) => StreamRunnerParam<Delta<T>, "out">;
|
|
16
|
-
export type GroupCollection<Grp extends notArr, V extends O, EE = {}, Out extends Loose<Grp, V, '_grp'> = Loose<Grp, V, '_grp'>> = RWCollection<MergedInput<Out, V, Grp, '_grp', EE> | Strict<Grp, V, '_grp', EE>, Out>;
|
|
17
|
-
export declare const $group: <T extends O, Grp extends notArr, V extends O, EE = {}, Out extends Loose<Grp, V, "_grp"> = Loose<Grp, V, "_grp">>(id: Expr<Grp, T>, args: DeltaAccumulators<T, O & Omit<V, Denied<"_grp">>>, out: GroupCollection<Grp, V, EE, Out>, extra: ExprsExact<Omit<EE, IdAndTsKeys | "_grp" | Exclude<keyof V, IdAndTsKeys | "_grp">>, Rec<"_grp", Grp> & Omit<V, IdAndTsKeys | "_grp">>, idPrefix?: string) => StreamRunnerParam<Delta<T>, "out">;
|
|
18
|
-
export {};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { O, RORec, Rec } from '../../../../types';
|
|
2
|
-
import { type ExprsExact } from '../../../expression/concat';
|
|
3
|
-
import type { Delta, Expr, RawStages } from '../../../types';
|
|
4
|
-
import type { DeltaAccumulators } from '../../../types/accumulator';
|
|
5
|
-
export type WithItem<V, Grp> = Rec<'_id', Grp> & Rec<'item', O<V>>;
|
|
6
|
-
export declare const subGroup: <T extends O, Grp, V extends O, GID extends string>(id: Expr<Grp, T>, args: DeltaAccumulators<T, V>, addGrp: <D extends Rec<"_id", Grp>>(src: ExprsExact<V, D>) => ExprsExact<RORec<GID, Grp> & V, D>) => RawStages<unknown, Delta<T>, Rec<GID, Grp> & V>;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { AsLiteral, ID, O, Rec, Replace, WriteonlyCollection, notArr } from '../../../../types';
|
|
2
|
-
import { type ExprsExact } from '../../../expression/concat';
|
|
3
|
-
import type { Model, RawStages, TS } from '../../../types';
|
|
4
|
-
import type { DeltaAccumulators } from '../../../types/accumulator';
|
|
5
|
-
import { type MergeInto } from '../../out';
|
|
6
|
-
type GI<GG> = Exclude<GG, keyof TS>;
|
|
7
|
-
export type IdAndTsKeys = keyof (TS & ID);
|
|
8
|
-
type V<VV, GG extends string> = Omit<VV, IdAndTsKeys | GI<GG>>;
|
|
9
|
-
export type Prepare<Grp, GG extends string> = TS & ID & Rec<GI<GG>, Grp>;
|
|
10
|
-
type Par<T> = {
|
|
11
|
-
[P in keyof T]?: T[P] | null;
|
|
12
|
-
};
|
|
13
|
-
export type Loose<Grp, VV, GG extends string> = Prepare<Grp, GG> & Par<V<VV, GG>>;
|
|
14
|
-
export type Strict<Grp, VV, GG extends string, EE> = Prepare<Grp, GG> & V<VV, GG> & Omit<EE, IdAndTsKeys | GI<GG> | keyof V<VV, GG>>;
|
|
15
|
-
export type V_Grp<VV, GG extends string, Grp> = Rec<GI<GG>, Grp> & V<VV, GG>;
|
|
16
|
-
export type Extra<EE, VV, GG extends string> = Omit<EE, IdAndTsKeys | GI<GG> | keyof V<VV, GG>>;
|
|
17
|
-
type OrReplace<T, V> = T | Replace<T, V>;
|
|
18
|
-
export type MergedInput<Out, VV, Grp, GG extends string, EE> = OrReplace<Replace<Replace<Out, V<VV, GG>>, Extra<EE, VV, GG>> & Model, TS & ID & V_Grp<VV, GG, Grp> & Extra<EE, VV, GG>>;
|
|
19
|
-
export declare const subMerge: <T extends O, Grp extends notArr, VV extends O, GG extends string, EE = {}, Out extends Loose<Grp, VV, GG> = Loose<Grp, VV, GG>>(args: DeltaAccumulators<T, V<VV, GG>>, out: MergeInto<Strict<Grp, VV, GG, EE>, Out, WriteonlyCollection<MergedInput<Out, VV, Grp, GG, EE>>>, gid: AsLiteral<GI<GG>>, extra: ExprsExact<Extra<EE, VV, GG>, V_Grp<VV, GG, Grp>>, idPrefix: string, first: boolean) => RawStages<unknown, V_Grp<VV, GG, Grp>, "out">;
|
|
20
|
-
export {};
|
package/lib/aggregate/index.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export { emptyDelta } from './prefix';
|
|
2
|
-
export * from './group';
|
|
3
|
-
export * from './lookup';
|
|
4
|
-
export * from './match';
|
|
5
|
-
export * from './set';
|
|
6
|
-
export * from './unwind';
|
|
7
|
-
export * from './$merge';
|
|
8
|
-
export * from './$upsert';
|
|
9
|
-
export * from './out';
|
|
10
|
-
export { $group_ } from './mongo-stages';
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Arr, AsLiteral, ID, N, O, Rec, doc, rawItem } from '../../../types';
|
|
2
|
-
import { Field } from '../../field';
|
|
3
|
-
import type { BA, Before, Delta, RawStages, TStages, UBefore } from '../../types';
|
|
4
|
-
import { type JoinId } from '../unwind';
|
|
5
|
-
type s = string;
|
|
6
|
-
type Both<K1 extends s, LE, KK2 extends s, RE, N1 extends null = never, N2 extends null = never> = Delta<Rec<K1, LE | N1> & Rec<Exclude<KK2, BA | K1>, RE | N2> & ID>;
|
|
7
|
-
export declare const $lookupDelta: <LQ extends O, LE extends LQ & doc, RQ extends O, RE extends RQ & doc, BRB extends UBefore<RQ>, RS extends UBefore<RQ>, S extends rawItem, K1 extends s, KK2 extends s, N1 extends null = never, N2 extends null = never>({ field1, field2 }: {
|
|
8
|
-
field1: Field<LQ, S | N | Arr<S>>;
|
|
9
|
-
field2: Field<RQ, S | N | Arr<S>>;
|
|
10
|
-
}, { coll, exec, input }: TStages<RS, UBefore<RQ>, BRB, Before<RE>>, k1: AsLiteral<K1>, k2: AsLiteral<Exclude<KK2, BA | K1>>, k: JoinId<K1, Exclude<KK2, BA | K1>>, includeNull1?: N1, includeNull2?: N2) => RawStages<unknown, Delta<LE>, Both<K1, LE, KK2, RE, N1, N2>>;
|
|
11
|
-
export {};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { App, Arr, AsLiteral, HKT, ID, O, Rec, rawItem } from '../../../types';
|
|
2
|
-
import { type Field } from '../../field';
|
|
3
|
-
import type { Before, RawStages, TStages, UBefore } from '../../types';
|
|
4
|
-
import { type JoinId } from '../unwind';
|
|
5
|
-
type s = string;
|
|
6
|
-
export declare const $lookupRaw: <LQ extends O, LE extends LQ & ID, RQ extends O, RE extends RQ & ID, BRB extends Before<RQ>, RS extends UBefore<RQ>, S extends rawItem, As extends s, Null extends null = never>({ field1, field2 }: {
|
|
7
|
-
field1: Field<LQ, S | Arr<S>>;
|
|
8
|
-
field2: Field<RQ, S | Arr<S>>;
|
|
9
|
-
}, { coll, exec, input }: TStages<RS, Before<RQ>, BRB, Before<RE>>, k2: AsLiteral<As>, k: JoinId<"left", "right">, includeNull?: Null) => <F extends HKT<O, O>>(f: <T extends O>() => Field<App<F, T>, T>) => RawStages<App<F, LQ>, App<F, LE>, App<F, LE & Rec<As, RE | Null> & ID>>;
|
|
10
|
-
export {};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type AsLiteral, type O, type RORec, type doc, type notArr, type Arr, type N } from '../../../types';
|
|
2
|
-
import { type Field } from '../../field';
|
|
3
|
-
import type { SnapshotStream, SnapshotStreamExecutionResult } from '../../types/stream';
|
|
4
|
-
type Params<As extends string, LQ extends O, RQ extends O, RE extends RQ, S extends notArr> = {
|
|
5
|
-
from: SnapshotStreamExecutionResult<RQ, RE>;
|
|
6
|
-
as: AsLiteral<As>;
|
|
7
|
-
} & ({
|
|
8
|
-
to: 'one';
|
|
9
|
-
localField: Field<LQ, string | N>;
|
|
10
|
-
foreignField?: never;
|
|
11
|
-
middle?: never;
|
|
12
|
-
} | {
|
|
13
|
-
to: 'many';
|
|
14
|
-
middle: string;
|
|
15
|
-
localField: Field<LQ, S | Arr<S>>;
|
|
16
|
-
foreignField: Field<RQ, S | Arr<S>>;
|
|
17
|
-
});
|
|
18
|
-
export declare const $lookup: <As extends string, LQ extends doc, RQ extends doc, RE extends RQ & doc, S extends notArr>(p: Params<As, LQ, RQ, RE, S>) => <LE extends LQ>(l: SnapshotStream<LQ, LE>) => SnapshotStream<LQ, LE & RORec<As, RE>>;
|
|
19
|
-
export declare const $outerLookup: <As extends string, LQ extends doc, RQ extends doc, RE extends RQ & doc, S extends notArr>(p: Params<As, LQ, RQ, RE, S>) => <LE extends LQ>(l: SnapshotStream<LQ, LE>) => SnapshotStream<LQ, LE & RORec<As, RE | null>>;
|
|
20
|
-
export {};
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { doc } from '../../../types';
|
|
2
|
-
import type { Delta, Expr, RawStages } from '../../types';
|
|
3
|
-
export declare const $matchDelta: <T extends doc>(query: Expr<boolean, T>) => RawStages<unknown, Delta<T>, Delta<T>, unknown, number>;
|
|
4
|
-
export declare const matchDelta: <D>() => RawStages<unknown, Delta<D | null>, Delta<D>>;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Arr, O, Rec } from '../../types';
|
|
2
|
-
import type { Accumulators, Expr, LookupArgs, Query, RawStages } from '../types';
|
|
3
|
-
import type { Updater } from '../update/updater';
|
|
4
|
-
export * from './raws';
|
|
5
|
-
type s = string;
|
|
6
|
-
export declare const $match_: <T extends O, C = unknown>(query?: Query<T, C>) => RawStages<O<T>, T, T, C>;
|
|
7
|
-
export declare const $set_: <Q, T extends Q & O, V extends Q & O, C = unknown>(updater: Updater<T, T, V, C>) => RawStages<O<Q>, T, V, C, 1>;
|
|
8
|
-
export declare const $replaceWith_: <T extends O, V extends O, C = unknown>(expr: Expr<V, T, C>) => RawStages<O, T, V, C, 1>;
|
|
9
|
-
export declare const $unwind_: <T extends O, K extends s, U, Null extends null = never>(k: K, includeNull?: Null) => RawStages<O<T>, T & Rec<K, Arr<U>>, T & Rec<K, Null | U>>;
|
|
10
|
-
export declare const $group_: <V extends O>() => <ID, T extends O, C = unknown>(id: Expr<ID, T, C>, args: Accumulators<T, V, C>) => RawStages<O, T, Rec<"_id", ID> & V, C, 1>;
|
|
11
|
-
export declare const $documents_: <T extends O, C>(docs: Expr<Arr<T>, unknown, C>) => RawStages<unknown, null, T, C, 1>;
|
|
12
|
-
export declare const $project_: <T extends O, K extends import("../..").StrKey<T>>(projection: import("../utils").ExactKeys<K>) => RawStages<T, T, T, unknown, 1>;
|
|
13
|
-
export declare const $simpleLookup_: <T extends O, U extends O, R, K extends s, Ctx, C = unknown, S = string>(args: LookupArgs<T, U, R, K, Ctx, C, S>) => RawStages<O<T>, T, T & Rec<K, Arr<U>>, C, 1>;
|