@kairos-es/feed-algolia 0.0.0-stage → 0.0.0
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/LICENSE +28 -0
- package/README.md +66 -2
- package/dist/cjs/classify.js +157 -0
- package/dist/cjs/classify.js.map +1 -0
- package/dist/cjs/identifiers.js +38 -0
- package/dist/cjs/identifiers.js.map +1 -0
- package/dist/cjs/index.js +39 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/records.js +105 -0
- package/dist/cjs/records.js.map +1 -0
- package/dist/cjs/rest.js +293 -0
- package/dist/cjs/rest.js.map +1 -0
- package/dist/cjs/sink.js +463 -0
- package/dist/cjs/sink.js.map +1 -0
- package/dist/dts-cjs/classify.d.ts +101 -0
- package/dist/dts-cjs/classify.d.ts.map +1 -0
- package/dist/dts-cjs/identifiers.d.ts +34 -0
- package/dist/dts-cjs/identifiers.d.ts.map +1 -0
- package/dist/dts-cjs/index.d.ts +39 -0
- package/dist/dts-cjs/index.d.ts.map +1 -0
- package/dist/dts-cjs/package.json +3 -0
- package/dist/dts-cjs/records.d.ts +56 -0
- package/dist/dts-cjs/records.d.ts.map +1 -0
- package/dist/dts-cjs/rest.d.ts +215 -0
- package/dist/dts-cjs/rest.d.ts.map +1 -0
- package/dist/dts-cjs/sink.d.ts +220 -0
- package/dist/dts-cjs/sink.d.ts.map +1 -0
- package/dist/dts-esm/classify.d.ts +101 -0
- package/dist/dts-esm/classify.d.ts.map +1 -0
- package/dist/dts-esm/identifiers.d.ts +34 -0
- package/dist/dts-esm/identifiers.d.ts.map +1 -0
- package/dist/dts-esm/index.d.ts +39 -0
- package/dist/dts-esm/index.d.ts.map +1 -0
- package/dist/dts-esm/package.json +3 -0
- package/dist/dts-esm/records.d.ts +56 -0
- package/dist/dts-esm/records.d.ts.map +1 -0
- package/dist/dts-esm/rest.d.ts +215 -0
- package/dist/dts-esm/rest.d.ts.map +1 -0
- package/dist/dts-esm/sink.d.ts +220 -0
- package/dist/dts-esm/sink.d.ts.map +1 -0
- package/dist/esm/classify.js +145 -0
- package/dist/esm/classify.js.map +1 -0
- package/dist/esm/identifiers.js +31 -0
- package/dist/esm/identifiers.js.map +1 -0
- package/dist/esm/index.js +38 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/records.js +96 -0
- package/dist/esm/records.js.map +1 -0
- package/dist/esm/rest.js +285 -0
- package/dist/esm/rest.js.map +1 -0
- package/dist/esm/sink.js +454 -0
- package/dist/esm/sink.js.map +1 -0
- package/package.json +33 -3
- package/src/classify.ts +201 -0
- package/src/identifiers.ts +45 -0
- package/src/index.ts +48 -0
- package/src/records.ts +109 -0
- package/src/rest.ts +773 -0
- package/src/sink.ts +733 -0
package/dist/esm/sink.js
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `makeAlgoliaSink` — a `FeedSink` onto one served Algolia index, with both optional
|
|
3
|
+
* parts: a `FeedResync` built on Algolia's index copy and move, and a
|
|
4
|
+
* `FeedReconcile` built on `browse`.
|
|
5
|
+
*
|
|
6
|
+
* ## The steady state
|
|
7
|
+
*
|
|
8
|
+
* `apply` is ONE `batch` to the served index (`records.ts` owns what goes in it and
|
|
9
|
+
* which records are refused before it is sent), and its receipt is the index and
|
|
10
|
+
* the task Algolia returned. `confirm` polls `getTask` on that index until the task
|
|
11
|
+
* is `published`, which is the moment Algolia serves the batch — the moment the
|
|
12
|
+
* port's `confirm` promises. The poll runs on an Effect `Schedule`: it backs off
|
|
13
|
+
* from `taskPolling.initialDelay`, doubling to `taskPolling.maxDelay`, and no poll
|
|
14
|
+
* STARTS after `taskPolling.timeout` has passed since the wait began — the last
|
|
15
|
+
* wait is cut short to land on that deadline rather than overrun it by up to
|
|
16
|
+
* `maxDelay`. A task still unpublished then is a `SinkRetryable`: the driver
|
|
17
|
+
* resends the batch, which is safe, rather than waiting on a task without bound.
|
|
18
|
+
* A poll that has started can still outlast the deadline by its own request
|
|
19
|
+
* timeout (`rest.ts`). A `Schedule` rather than a hand-written loop because the
|
|
20
|
+
* curve is then one declared value, run under the `Clock`, so a test's
|
|
21
|
+
* `TestClock` drives it like any other wait.
|
|
22
|
+
*
|
|
23
|
+
* ## Resync: a fresh index, swapped in whole
|
|
24
|
+
*
|
|
25
|
+
* Algolia's `operationIndex` move replaces the destination index atomically, which
|
|
26
|
+
* is exactly the swap `FeedResync` asks for. So:
|
|
27
|
+
*
|
|
28
|
+
* - `begin(generation)` names the target `<served>__kairos_g<generation>_<attempt>`,
|
|
29
|
+
* where `<attempt>` is drawn from Effect's `Random` afresh on every call, and
|
|
30
|
+
* then COPIES the served index into it with `scope` limited to settings, synonyms
|
|
31
|
+
* and rules. The fresh index then ranks and matches exactly as the served one does,
|
|
32
|
+
* and holds none of its records. A served index that has never been written has no
|
|
33
|
+
* scope to copy, and that is not an error: `begin` reads the served index's
|
|
34
|
+
* settings first and makes no copy when it does not exist. Algolia would copy a
|
|
35
|
+
* missing source as an empty index, but the copy's task is polled on the served
|
|
36
|
+
* index, and `getTask` on an index that does not exist answers 404, so skipping
|
|
37
|
+
* the copy is what keeps that wait off a missing index. The name is the ATTEMPT's, not the
|
|
38
|
+
* generation's, because two workers can be resyncing one generation at once —
|
|
39
|
+
* one of them having lost its lease without knowing it yet — and a target they
|
|
40
|
+
* shared is one either could publish half-built; `runFeedSync.ts` owns the
|
|
41
|
+
* argument. Random rather than the lease holder's id, because the port hands a
|
|
42
|
+
* sink no lease, and a draw needs none.
|
|
43
|
+
* - `begin` then MARKS the target as its attempt's own: it writes the target's
|
|
44
|
+
* `userData` setting as whatever the copy brought from the served index, plus one
|
|
45
|
+
* key, `kairosResyncAttempt`, holding the attempt. Algolia creates an index on a
|
|
46
|
+
* settings write, so this is also what makes the target EXIST whatever the copy
|
|
47
|
+
* did — over a served index never written, and for a rebuild that loads nothing.
|
|
48
|
+
* The mark is what `publish` checks, below. It goes in `userData` because that
|
|
49
|
+
* is the one setting Algolia keeps for the application's own data and reads
|
|
50
|
+
* nothing from, so the mark changes nothing about how the target ranks or
|
|
51
|
+
* matches.
|
|
52
|
+
* - `begin` first drops every OTHER resync index of this served index it can list,
|
|
53
|
+
* whatever its generation, settings snapshots included. Each was left by an attempt that will never publish
|
|
54
|
+
* it now — one that crashed, one abandoned because the generation moved, or one
|
|
55
|
+
* a worker that lost its lease is still loading — and every such index holds
|
|
56
|
+
* records against the application's quota; an ephemeral feed, whose generation
|
|
57
|
+
* is new on every boot, would otherwise leave one behind per interrupted boot.
|
|
58
|
+
* Dropping the one a stale worker is loading is also what makes that worker's
|
|
59
|
+
* `publish` fail rather than land. Listing needs the key to carry Algolia's
|
|
60
|
+
* `listIndexes` ACL, and the sink REQUIRES it rather than skipping the drop when
|
|
61
|
+
* it is missing: without the drop, a stale worker's `publish` could land and
|
|
62
|
+
* abandoned targets would pile up. A key without it is refused like any other
|
|
63
|
+
* 4xx, a `SinkHalted` at the start of the resync, before the copy, so nothing is
|
|
64
|
+
* created.
|
|
65
|
+
* - `load` is `apply`, against the target. It does not check the mark: that would
|
|
66
|
+
* cost a settings read per page and still race the drop, and `publish` is where
|
|
67
|
+
* a target is served, so `publish` is where the check has to hold.
|
|
68
|
+
* - `publish` reads the target's settings and refuses, `SinkRetryable`, a target
|
|
69
|
+
* that is missing or does not carry this attempt's mark; only then does it move
|
|
70
|
+
* the target over the served index and wait on the move's task. Then it reads the
|
|
71
|
+
* SERVED index's settings and reports success only if the served index now
|
|
72
|
+
* carries this attempt's mark, which only this move could have put there;
|
|
73
|
+
* otherwise it fails `SinkRetryable`. That read is the authority, and the
|
|
74
|
+
* move's own answer is not: Algolia ignores a move of a missing index "without
|
|
75
|
+
* returning an error", so a target dropped after the check gets a task back, the
|
|
76
|
+
* task publishes, and the served index still holds the old copy. Taking that for
|
|
77
|
+
* success would let the driver save its cursor at the new generation over an
|
|
78
|
+
* index that never got it. For the same
|
|
79
|
+
* reason a 404 on the move's task poll — `getTask` on an index that no longer
|
|
80
|
+
* exists — ends the wait rather than halting, and the read decides. An empty rebuild
|
|
81
|
+
* is a move like any other, of a target that holds no records, and it leaves the
|
|
82
|
+
* served index empty. A target can go missing before its publish in two ways: a
|
|
83
|
+
* newer `begin` dropped it, or something moved it over the served index first.
|
|
84
|
+
* Only the second could ever land a half-built target, and the per-attempt name
|
|
85
|
+
* is what rules it out: the one `publish` that names this target is this
|
|
86
|
+
* attempt's own, so no other worker's can move it. What is left is the first,
|
|
87
|
+
* and it has a second form, which is why the mark exists: a worker that lost its
|
|
88
|
+
* lease without knowing it goes on LOADING into its dropped target, and Algolia
|
|
89
|
+
* creates an index on a batch write, so the target comes back holding the stale
|
|
90
|
+
* worker's later pages alone. A name alone cannot tell that index from the one
|
|
91
|
+
* `begin` made; the missing mark can, since only `begin` writes it. Either way
|
|
92
|
+
* `publish` fails `SinkRetryable` and the resync starts again. It never touches
|
|
93
|
+
* the served index on that account, since the worker that finds its target gone
|
|
94
|
+
* is the one most likely to have lost the lease. An index a stale load brought
|
|
95
|
+
* back is left where it is, and the next `begin` drops it with the rest.
|
|
96
|
+
* - Before it marks the target, `begin` takes a SNAPSHOT of the served index's
|
|
97
|
+
* settings into `<target>__settings`, a settings-scoped copy, or an empty
|
|
98
|
+
* settings write when the served index does not exist. After the move has landed,
|
|
99
|
+
* `publish` takes the mark back off the served index by copying the snapshot's
|
|
100
|
+
* settings over it, scoped to settings, then drops the snapshot. The drop is a
|
|
101
|
+
* best effort of its own, and runs whether or not the restore landed. A copy is
|
|
102
|
+
* the one write that REMOVES a setting: Algolia stores a `null` written to `userData` as
|
|
103
|
+
* the value `null`, and `{}` or `resetToDefault` leave an empty object, so a
|
|
104
|
+
* rebuild of an index that had no `userData` would otherwise leave one behind.
|
|
105
|
+
* The snapshot is a second copy of the same served index's settings, made just
|
|
106
|
+
* after the target's, so the restore changes nothing else unless those settings
|
|
107
|
+
* changed between the two copies, when it applies the later ones. Its scope is
|
|
108
|
+
* SETTINGS ONLY, and must stay so: the snapshot holds no synonyms or rules, so a
|
|
109
|
+
* wider restore would wipe the served index's. Records are untouched, and so are the
|
|
110
|
+
* served index's replicas (see below). A mark an earlier failed restore left on the
|
|
111
|
+
* served index is taken off the snapshot as it is made, so it cannot come
|
|
112
|
+
* back; when it was all the `userData` there was, a settings write is the only
|
|
113
|
+
* way to take it off, and the `null` Algolia stores for it is what remains.
|
|
114
|
+
* The move and the restore are two steps, so a search index briefly carries the
|
|
115
|
+
* key, which nothing reads. The restore is BEST-EFFORT: the swap has
|
|
116
|
+
* already landed, and failing `publish` now would tell the driver it had not. A
|
|
117
|
+
* snapshot that is gone (a newer `begin` dropped it) is not copied, since a copy
|
|
118
|
+
* from a missing index changes nothing and its task never publishes; the mark
|
|
119
|
+
* then stays until the next rebuild's restore, and a warning says so. One that
|
|
120
|
+
* goes between that check and the copy is reported the same way, once the wait
|
|
121
|
+
* on the copy's task ends: at the first poll, if `getTask` on the missing index
|
|
122
|
+
* answers 404 as above, and at `pollTimeout` if it does not. The swap has landed
|
|
123
|
+
* by then, so the worst cost is a `publish` that returns late, not a wrong one.
|
|
124
|
+
*
|
|
125
|
+
* Check-then-move leaves no gap a stale load can use: the only worker that loads
|
|
126
|
+
* into this target is the one publishing it, and its loads have finished. A drop
|
|
127
|
+
* between the check and the move makes the move a no-op, which the read of the
|
|
128
|
+
* served index after it turns into a `SinkRetryable`, as above. Taking the mark
|
|
129
|
+
* off the TARGET before the move would open one: a drop between that write and
|
|
130
|
+
* the move, and the write itself would bring back an empty target for the move to
|
|
131
|
+
* land.
|
|
132
|
+
*
|
|
133
|
+
* Replicas are the one thing this does not settle by construction: a copy with
|
|
134
|
+
* `scope` is not documented to carry a primary's replicas to the target, nor a
|
|
135
|
+
* move over a primary, or the restore's settings-scoped copy over it, to keep
|
|
136
|
+
* them. Measured against a real application, all three leave them where they
|
|
137
|
+
* were, and the package's live test checks it after `publish` when credentials
|
|
138
|
+
* are present.
|
|
139
|
+
*
|
|
140
|
+
* ## Reconcile
|
|
141
|
+
*
|
|
142
|
+
* `listKeys` browses the served index page by page, retrieving `objectID` and nothing
|
|
143
|
+
* else, so listing a large index costs the ids and not the records.
|
|
144
|
+
*
|
|
145
|
+
* ## The key's ACLs
|
|
146
|
+
*
|
|
147
|
+
* A scoped key needs `addObject` (record writes, and the copy and the move),
|
|
148
|
+
* `deleteObject`, `deleteIndex` (the drop), `settings` (the mark check),
|
|
149
|
+
* `editSettings` (writing and removing the mark), `browse` (reconcile) and
|
|
150
|
+
* `listIndexes` (the drop's listing), on the served index and its resync indices.
|
|
151
|
+
* The README states the same set, ACL by ACL, for a consumer.
|
|
152
|
+
*
|
|
153
|
+
* ## What the sink holds
|
|
154
|
+
*
|
|
155
|
+
* Nothing between calls but its options and the captured `HttpClient`: no queue, no
|
|
156
|
+
* cursor, no buffered batch. Every piece of progress is the driver's, which is what
|
|
157
|
+
* lets a resent batch and a restarted resync be safe.
|
|
158
|
+
*/
|
|
159
|
+
import { FeedKey, SinkHalted, SinkRetryable } from '@kairos-es/feed';
|
|
160
|
+
import { Clock, Duration, Effect, Option, Random, Schedule, Stream } from 'effect';
|
|
161
|
+
import { AlgoliaIndexName } from "./identifiers.js";
|
|
162
|
+
import { DEFAULT_MAX_RECORD_BYTES, encodeBatch } from "./records.js";
|
|
163
|
+
import { makeAlgoliaRest } from "./rest.js";
|
|
164
|
+
/**
|
|
165
|
+
* The resync target's name for one attempt at `generation`. PACKAGE-INTERNAL, and
|
|
166
|
+
* kept off the entry point deliberately: `attempt` is what `begin` draws for each
|
|
167
|
+
* call, so a real target's name is the one `begin` returns, and a consumer has no
|
|
168
|
+
* use for composing one.
|
|
169
|
+
*
|
|
170
|
+
* It does not check `attempt`. Every caller here passes lower-case base-36
|
|
171
|
+
* digits, which `begin` draws and the tests write by hand, and that shape is all
|
|
172
|
+
* `resyncGenerationOf` below recognises: a suffix of any other shape would name an
|
|
173
|
+
* index that `begin` never drops and `publish` never accepts.
|
|
174
|
+
*/
|
|
175
|
+
export const resyncIndexName = (served, generation, attempt) => AlgoliaIndexName.make(`${served}__kairos_g${generation}_${attempt}`);
|
|
176
|
+
/**
|
|
177
|
+
* The generation `name` rebuilds, when it is a resync target of `served` for SOME
|
|
178
|
+
* generation and attempt, and `undefined` otherwise: the prefix, the decimal digits
|
|
179
|
+
* a `Generation` prints as, an underscore, then base-36 digits and nothing else,
|
|
180
|
+
* so a served index that merely starts with this one's name plus `__kairos_g` is not
|
|
181
|
+
* mistaken for one of its targets.
|
|
182
|
+
*/
|
|
183
|
+
const resyncGenerationOf = (served, name) => {
|
|
184
|
+
const prefix = `${served}__kairos_g`;
|
|
185
|
+
if (!name.startsWith(prefix)) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
return /^(\d+)_[0-9a-z]+$/.exec(name.slice(prefix.length))?.[1];
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* The settings snapshot `begin` takes for `target`: the served index's settings as
|
|
192
|
+
* the target got them, before the mark. Named after its target, so it is a resync
|
|
193
|
+
* index of the same served index and `begin`'s drop of leftovers takes a snapshot an
|
|
194
|
+
* interrupted attempt left, as it takes the target.
|
|
195
|
+
*/
|
|
196
|
+
const settingsSnapshotName = target => AlgoliaIndexName.make(`${target}__settings`);
|
|
197
|
+
/**
|
|
198
|
+
* Whether `name` is any resync index of `served`: a target, or a target's settings
|
|
199
|
+
* snapshot. What `begin` drops; `resyncGenerationOf` alone is what a target must be.
|
|
200
|
+
*/
|
|
201
|
+
const isResyncIndexOf = (served, name) => resyncGenerationOf(served, name.replace(/__settings$/, '')) !== undefined;
|
|
202
|
+
/**
|
|
203
|
+
* The attempt a resync target's name ends in: the base-36 digits after its last
|
|
204
|
+
* underscore. Called only on a name `targetIndex` has already checked.
|
|
205
|
+
*/
|
|
206
|
+
const attemptOf = name => /_([0-9a-z]+)$/.exec(name)?.[1] ?? '';
|
|
207
|
+
/** The `userData` key `begin` marks its target with; see the module doc. */
|
|
208
|
+
const ATTEMPT_MARK = 'kairosResyncAttempt';
|
|
209
|
+
/** Digits in a resync target's attempt suffix: 12 base-36 digits, about 62 bits. */
|
|
210
|
+
const ATTEMPT_DIGITS = 12;
|
|
211
|
+
/** One base-36 digit's worth of `Random`, `ATTEMPT_DIGITS` times. */
|
|
212
|
+
const drawAttempt = /*#__PURE__*/Effect.map(/*#__PURE__*/Effect.all(/*#__PURE__*/Array.from({
|
|
213
|
+
length: ATTEMPT_DIGITS
|
|
214
|
+
}, () => Random.nextIntBetween(0, 35))), digits => digits.map(d => d.toString(36)).join(''));
|
|
215
|
+
const RESYNC_SCOPE = ['settings', 'synonyms', 'rules'];
|
|
216
|
+
/** Index names per `listIndices` page; Algolia's own default. */
|
|
217
|
+
const LIST_PAGE_SIZE = 100;
|
|
218
|
+
const DEFAULT_POLL_INITIAL = /*#__PURE__*/Duration.millis(100);
|
|
219
|
+
const DEFAULT_POLL_MAX = /*#__PURE__*/Duration.seconds(2);
|
|
220
|
+
const DEFAULT_POLL_TIMEOUT = /*#__PURE__*/Duration.seconds(60);
|
|
221
|
+
const DEFAULT_BROWSE_PAGE = 1_000;
|
|
222
|
+
/** A positive duration from an option, or the default; dies on nonsense. */
|
|
223
|
+
const durationOption = (label, input, fallback) => {
|
|
224
|
+
if (input === undefined) {
|
|
225
|
+
return fallback;
|
|
226
|
+
}
|
|
227
|
+
const decoded = Duration.fromInputUnsafe(input);
|
|
228
|
+
if (!Duration.isFinite(decoded) || Duration.toMillis(decoded) <= 0) {
|
|
229
|
+
throw new Error(`feed-algolia: ${label} must be a positive, finite duration`);
|
|
230
|
+
}
|
|
231
|
+
return decoded;
|
|
232
|
+
};
|
|
233
|
+
const positiveInteger = (label, value, fallback) => {
|
|
234
|
+
if (value === undefined) {
|
|
235
|
+
return fallback;
|
|
236
|
+
}
|
|
237
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
238
|
+
throw new Error(`feed-algolia: ${label} must be a positive integer (got ${value})`);
|
|
239
|
+
}
|
|
240
|
+
return value;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* An index's `userData` setting as an object to add a key to or take one from:
|
|
244
|
+
* empty when the index has none. A `userData` that is not a JSON object leaves no
|
|
245
|
+
* room for the mark beside it, and is a halt rather than something to overwrite.
|
|
246
|
+
*/
|
|
247
|
+
const userDataOf = (index, settings) => {
|
|
248
|
+
const userData = Option.isSome(settings) ? settings.value.userData : undefined;
|
|
249
|
+
if (userData === undefined || userData === null) {
|
|
250
|
+
return Effect.succeed({});
|
|
251
|
+
}
|
|
252
|
+
return typeof userData === 'object' && !Array.isArray(userData) ? Effect.succeed(userData) : Effect.fail(new SinkHalted({
|
|
253
|
+
reason: `feed-algolia: the userData setting of '${index}' is not a JSON object, so a resync cannot mark its target beside it`
|
|
254
|
+
}));
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* The resync mark an index's settings carry, if any. Unlike {@link userDataOf} it
|
|
258
|
+
* never halts: after a move, a `userData` that is not an object only means the
|
|
259
|
+
* move did not land, which is the retryable case.
|
|
260
|
+
*/
|
|
261
|
+
const attemptMarkOf = settings => {
|
|
262
|
+
const userData = Option.isSome(settings) ? settings.value.userData : undefined;
|
|
263
|
+
return typeof userData === 'object' && userData !== null && !Array.isArray(userData) ? userData[ATTEMPT_MARK] : undefined;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Narrow a call that sends no records. Only a write can be refused per record, so
|
|
267
|
+
* a `SinkRejected` from anything else is a bug in this package's classification;
|
|
268
|
+
* it is reported as a halt rather than silently dropped.
|
|
269
|
+
*/
|
|
270
|
+
const noRejection = effect => Effect.mapError(effect, failure => failure._tag === 'SinkRejected' ? new SinkHalted({
|
|
271
|
+
reason: `feed-algolia: a call that sends no records was refused per record: ${failure.message}`
|
|
272
|
+
}) : failure);
|
|
273
|
+
/**
|
|
274
|
+
* Build an Algolia sink over the `HttpClient` in context; see the module doc.
|
|
275
|
+
* Dies at once on options out of range.
|
|
276
|
+
*/
|
|
277
|
+
export const makeAlgoliaSink = options => Effect.gen(function* () {
|
|
278
|
+
const settings = yield* Effect.try({
|
|
279
|
+
try: () => ({
|
|
280
|
+
maxRecordBytes: positiveInteger('maxRecordBytes', options.maxRecordBytes, DEFAULT_MAX_RECORD_BYTES),
|
|
281
|
+
browsePageSize: positiveInteger('browsePageSize', options.browsePageSize, DEFAULT_BROWSE_PAGE),
|
|
282
|
+
pollInitial: durationOption('taskPolling.initialDelay', options.taskPolling?.initialDelay, DEFAULT_POLL_INITIAL),
|
|
283
|
+
pollMax: durationOption('taskPolling.maxDelay', options.taskPolling?.maxDelay, DEFAULT_POLL_MAX),
|
|
284
|
+
pollTimeout: durationOption('taskPolling.timeout', options.taskPolling?.timeout, DEFAULT_POLL_TIMEOUT)
|
|
285
|
+
}),
|
|
286
|
+
catch: error => error
|
|
287
|
+
}).pipe(Effect.orDie);
|
|
288
|
+
const rest = yield* makeAlgoliaRest(options);
|
|
289
|
+
const served = options.index;
|
|
290
|
+
const pollTimeoutMillis = Duration.toMillis(settings.pollTimeout);
|
|
291
|
+
/**
|
|
292
|
+
* The curve a task poll waits on between polls: doubling from the first delay,
|
|
293
|
+
* capped, and never past `deadline` — the last delay is cut to land on it, and
|
|
294
|
+
* nothing recurs once it is reached. Built per wait, since the deadline is
|
|
295
|
+
* the wait's own; the curve is the same for every one.
|
|
296
|
+
*/
|
|
297
|
+
const pollSchedule = deadline => Schedule.min([Schedule.exponential(settings.pollInitial, 2), Schedule.spaced(settings.pollMax)]).pipe(Schedule.while(({
|
|
298
|
+
now
|
|
299
|
+
}) => now < deadline), Schedule.modifyDelay(({
|
|
300
|
+
now,
|
|
301
|
+
duration
|
|
302
|
+
}) => Effect.succeed(Duration.millis(Math.min(Duration.toMillis(duration), deadline - now)))));
|
|
303
|
+
/**
|
|
304
|
+
* Poll until `poll` is done; see the module doc for the curve. `poll` answers
|
|
305
|
+
* whether to stop, which for every wait but one is "the task is published".
|
|
306
|
+
*/
|
|
307
|
+
const pollUntil = (index, taskID, poll) => Effect.gen(function* () {
|
|
308
|
+
const deadline = (yield* Clock.currentTimeMillis) + pollTimeoutMillis;
|
|
309
|
+
const done = yield* poll.pipe(Effect.repeat({
|
|
310
|
+
schedule: pollSchedule(deadline),
|
|
311
|
+
until: d => d
|
|
312
|
+
}));
|
|
313
|
+
if (!done) {
|
|
314
|
+
return yield* Effect.fail(new SinkRetryable({
|
|
315
|
+
reason: `Algolia task ${taskID} on index '${index}' was not published within ${Duration.format(settings.pollTimeout)}`
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
/** Poll a task until it is published; see the module doc for the curve. */
|
|
320
|
+
const awaitTask = (index, taskID) => pollUntil(index, taskID, noRejection(rest.taskPublished(index, taskID)));
|
|
321
|
+
/** Wait on a task an index operation may or may not have returned. */
|
|
322
|
+
const awaitIfAny = (index, task) => Option.match(task, {
|
|
323
|
+
onNone: () => Effect.void,
|
|
324
|
+
onSome: taskID => awaitTask(index, taskID)
|
|
325
|
+
});
|
|
326
|
+
const send = (index, batch) => Effect.gen(function* () {
|
|
327
|
+
const encoded = yield* encodeBatch(options.doc, settings.maxRecordBytes, batch);
|
|
328
|
+
const taskID = yield* rest.batch(index, encoded.operations, encoded.keys);
|
|
329
|
+
return {
|
|
330
|
+
index,
|
|
331
|
+
taskID
|
|
332
|
+
};
|
|
333
|
+
});
|
|
334
|
+
/**
|
|
335
|
+
* The target's index name, refusing a target this sink did not begin: a
|
|
336
|
+
* target is named after the served index, its generation and its attempt, and a
|
|
337
|
+
* load into any other index would write somewhere no publish of this sink will
|
|
338
|
+
* move.
|
|
339
|
+
*/
|
|
340
|
+
const targetIndex = target => resyncGenerationOf(served, target.name) === `${target.generation}` ? Effect.succeed(AlgoliaIndexName.make(target.name)) : Effect.die(new Error(`feed-algolia: the resync target '${target.name}' is not one of this sink's targets for generation ${target.generation} ('${served}__kairos_g${target.generation}_<attempt>')`));
|
|
341
|
+
/** Every name `listIndices` returns, page by page. */
|
|
342
|
+
const allIndexNames = Stream.paginate(0, page => Effect.map(noRejection(rest.listIndexNames(page, LIST_PAGE_SIZE)), answer => [answer.names, answer.more ? Option.some(page + 1) : Option.none()]));
|
|
343
|
+
/** Drop every resync target of `served` but `keep`; see the module doc. */
|
|
344
|
+
const dropAbandonedTargets = keep => Effect.gen(function* () {
|
|
345
|
+
const names = yield* Stream.runCollect(allIndexNames);
|
|
346
|
+
for (const name of names) {
|
|
347
|
+
if (name === keep || !isResyncIndexOf(served, name)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const index = AlgoliaIndexName.make(name);
|
|
351
|
+
yield* awaitIfAny(index, yield* noRejection(rest.deleteIndex(index)));
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
const resync = {
|
|
355
|
+
begin: generation => Effect.gen(function* () {
|
|
356
|
+
const attempt = yield* drawAttempt;
|
|
357
|
+
const target = resyncIndexName(served, generation, attempt);
|
|
358
|
+
yield* dropAbandonedTargets(target);
|
|
359
|
+
const snapshot = settingsSnapshotName(target);
|
|
360
|
+
// No copy of a served index that does not exist; see the module doc.
|
|
361
|
+
if (Option.isSome(yield* noRejection(rest.readSettings(served)))) {
|
|
362
|
+
yield* awaitTask(served, yield* noRejection(rest.operation(served, {
|
|
363
|
+
operation: 'copy',
|
|
364
|
+
destination: target,
|
|
365
|
+
scope: RESYNC_SCOPE
|
|
366
|
+
})));
|
|
367
|
+
yield* awaitTask(served, yield* noRejection(rest.operation(served, {
|
|
368
|
+
operation: 'copy',
|
|
369
|
+
destination: snapshot,
|
|
370
|
+
scope: ['settings']
|
|
371
|
+
})));
|
|
372
|
+
// A mark a failed restore left on the served index must not ride into
|
|
373
|
+
// the snapshot, or every restore would put it back.
|
|
374
|
+
const {
|
|
375
|
+
[ATTEMPT_MARK]: stale,
|
|
376
|
+
...kept
|
|
377
|
+
} = yield* userDataOf(snapshot, yield* noRejection(rest.readSettings(snapshot)));
|
|
378
|
+
if (stale !== undefined) {
|
|
379
|
+
yield* awaitTask(snapshot, yield* noRejection(rest.writeSettings(snapshot, {
|
|
380
|
+
userData: Object.keys(kept).length === 0 ? null : kept
|
|
381
|
+
})));
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
// An index with every setting at its default, which is what the
|
|
385
|
+
// served index has; an empty settings write creates it.
|
|
386
|
+
yield* awaitTask(snapshot, yield* noRejection(rest.writeSettings(snapshot, {})));
|
|
387
|
+
}
|
|
388
|
+
const userData = yield* userDataOf(target, yield* noRejection(rest.readSettings(target)));
|
|
389
|
+
yield* awaitTask(target, yield* noRejection(rest.writeSettings(target, {
|
|
390
|
+
userData: {
|
|
391
|
+
...userData,
|
|
392
|
+
[ATTEMPT_MARK]: attempt
|
|
393
|
+
}
|
|
394
|
+
})));
|
|
395
|
+
return {
|
|
396
|
+
generation,
|
|
397
|
+
name: target
|
|
398
|
+
};
|
|
399
|
+
}),
|
|
400
|
+
load: (target, batch) => Effect.flatMap(targetIndex(target), index => send(index, batch)),
|
|
401
|
+
publish: target => Effect.gen(function* () {
|
|
402
|
+
const index = yield* targetIndex(target);
|
|
403
|
+
const found = yield* noRejection(rest.readSettings(index));
|
|
404
|
+
const userData = yield* userDataOf(index, found);
|
|
405
|
+
if (userData[ATTEMPT_MARK] !== attemptOf(index)) {
|
|
406
|
+
return yield* Effect.fail(new SinkRetryable({
|
|
407
|
+
reason: Option.isNone(found) ? `the resync index '${index}' vanished before it could be moved over '${served}'` : `the resync index '${index}' is not the one its begin made (a load after a newer begin dropped it brought it back), so it is not moved over '${served}'`
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
const moved = yield* noRejection(rest.operation(index, {
|
|
411
|
+
operation: 'move',
|
|
412
|
+
destination: served
|
|
413
|
+
}));
|
|
414
|
+
// A 404 on the poll ends the wait; the read below decides.
|
|
415
|
+
yield* pollUntil(index, moved, Effect.map(noRejection(rest.taskStateUnlessIndexMissing(index, moved)), state => state !== 'pending'));
|
|
416
|
+
const landed = yield* noRejection(rest.readSettings(served));
|
|
417
|
+
if (attemptMarkOf(landed) !== attemptOf(index)) {
|
|
418
|
+
return yield* Effect.fail(new SinkRetryable({
|
|
419
|
+
reason: `the move of the resync index '${index}' over '${served}' did not land: '${served}' does not carry its mark, so the index was dropped after publish checked it, or another attempt's move landed over it`
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
// Best-effort, after the swap has landed; see the module doc.
|
|
423
|
+
const snapshot = settingsSnapshotName(index);
|
|
424
|
+
yield* Effect.gen(function* () {
|
|
425
|
+
// A copy from a missing snapshot changes nothing, and its task never
|
|
426
|
+
// publishes, so a snapshot a newer `begin` dropped is not waited on,
|
|
427
|
+
// unless it goes between this read and the copy; see the module doc.
|
|
428
|
+
if (Option.isNone(yield* noRejection(rest.readSettings(snapshot)))) {
|
|
429
|
+
return yield* Effect.fail(new SinkRetryable({
|
|
430
|
+
reason: `its settings snapshot '${snapshot}' is gone`
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
yield* awaitTask(snapshot, yield* noRejection(rest.operation(snapshot, {
|
|
434
|
+
operation: 'copy',
|
|
435
|
+
destination: served,
|
|
436
|
+
scope: ['settings']
|
|
437
|
+
})));
|
|
438
|
+
}).pipe(Effect.catch(failure => Effect.logWarning(`feed-algolia: the resync mark was left in the userData of '${served}', and the next rebuild will take it off: ${failure.reason}`)));
|
|
439
|
+
// Separately, so a failed drop is not reported as a failed restore.
|
|
440
|
+
yield* Effect.flatMap(noRejection(rest.deleteIndex(snapshot)), task => awaitIfAny(snapshot, task)).pipe(Effect.catch(failure => Effect.logWarning(`feed-algolia: the settings snapshot '${snapshot}' was left behind, and the next rebuild will drop it: ${failure.reason}`)));
|
|
441
|
+
})
|
|
442
|
+
};
|
|
443
|
+
const reconcile = {
|
|
444
|
+
listKeys: Stream.paginate(Option.none(), cursor => Effect.map(noRejection(rest.browseObjectIds(served, cursor, settings.browsePageSize)), page => [page.objectIDs.filter(id => id.length > 0).map(id => FeedKey.make(id)), Option.map(page.cursor, Option.some)]))
|
|
445
|
+
};
|
|
446
|
+
return {
|
|
447
|
+
name: options.name,
|
|
448
|
+
apply: batch => send(served, batch),
|
|
449
|
+
confirm: receipt => awaitTask(receipt.index, receipt.taskID),
|
|
450
|
+
resync,
|
|
451
|
+
reconcile
|
|
452
|
+
};
|
|
453
|
+
});
|
|
454
|
+
//# sourceMappingURL=sink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sink.js","names":["FeedKey","SinkHalted","SinkRetryable","Clock","Duration","Effect","Option","Random","Schedule","Stream","AlgoliaIndexName","DEFAULT_MAX_RECORD_BYTES","encodeBatch","makeAlgoliaRest","resyncIndexName","served","generation","attempt","make","resyncGenerationOf","name","prefix","startsWith","undefined","exec","slice","length","settingsSnapshotName","target","isResyncIndexOf","replace","attemptOf","ATTEMPT_MARK","ATTEMPT_DIGITS","drawAttempt","map","all","Array","from","nextIntBetween","digits","d","toString","join","RESYNC_SCOPE","LIST_PAGE_SIZE","DEFAULT_POLL_INITIAL","millis","DEFAULT_POLL_MAX","seconds","DEFAULT_POLL_TIMEOUT","DEFAULT_BROWSE_PAGE","durationOption","label","input","fallback","decoded","fromInputUnsafe","isFinite","toMillis","Error","positiveInteger","value","Number","isInteger","userDataOf","index","settings","userData","isSome","succeed","isArray","fail","reason","attemptMarkOf","noRejection","effect","mapError","failure","_tag","message","makeAlgoliaSink","options","gen","try","maxRecordBytes","browsePageSize","pollInitial","taskPolling","initialDelay","pollMax","maxDelay","pollTimeout","timeout","catch","error","pipe","orDie","rest","pollTimeoutMillis","pollSchedule","deadline","min","exponential","spaced","while","now","modifyDelay","duration","Math","pollUntil","taskID","poll","currentTimeMillis","done","repeat","schedule","until","format","awaitTask","taskPublished","awaitIfAny","task","match","onNone","void","onSome","send","batch","encoded","doc","operations","keys","targetIndex","die","allIndexNames","paginate","page","listIndexNames","answer","names","more","some","none","dropAbandonedTargets","keep","runCollect","deleteIndex","resync","begin","snapshot","readSettings","operation","destination","scope","stale","kept","writeSettings","Object","load","flatMap","publish","found","isNone","moved","taskStateUnlessIndexMissing","state","landed","logWarning","reconcile","listKeys","cursor","browseObjectIds","objectIDs","filter","id","apply","confirm","receipt"],"sources":["../../src/sink.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kairos-es/feed-algolia",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "An Algolia sink for the kairos-es change feed, over effect's own HttpClient",
|
|
5
|
+
"license": "BSD-3-Clause",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/neverbland/kairos-es.git",
|
|
9
|
+
"directory": "packages/feed-algolia"
|
|
10
|
+
},
|
|
11
|
+
"sideEffects": [],
|
|
12
|
+
"homepage": "https://github.com/neverbland/kairos-es",
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"effect": "^4.0.5",
|
|
15
|
+
"@kairos-es/feed": "^0.0.0"
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/cjs/index.js",
|
|
18
|
+
"module": "./dist/esm/index.js",
|
|
19
|
+
"types": "./dist/dts-cjs/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/dts-esm/index.d.ts",
|
|
24
|
+
"default": "./dist/esm/index.js"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/dts-cjs/index.d.ts",
|
|
28
|
+
"default": "./dist/cjs/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"typesVersions": {
|
|
34
|
+
"*": {}
|
|
35
|
+
}
|
|
6
36
|
}
|