@ahrowe/mongo 0.1.3 → 0.2.1

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/docs/CLAUDE.md CHANGED
@@ -49,13 +49,14 @@ Every `createService<T>(...)` call returns:
49
49
  is rejected**; use `upsertOne` for create-or-update, `insert` for explicit creates. Adds `updatedOn: new Date()` to the `$set`
50
50
  unless `addUpdatedOnField: false` was passed to `createService`. Enqueues an
51
51
  `'updated'` outbox row with `{ prevDoc, doc, meta }` for each document that
52
- actually changed. `updateMany` always uses the reliable per-document path (see
53
- "Events are delivered via a transactional outbox" below).
52
+ actually changed. For `updateMany`, see "How updateMany/removeMany work" below.
54
53
  - `removeOne(query, options?)` and `removeMany(query, options?)` — `removeMany`
55
54
  returns removed docs under `results` unless `{ returnRemoved: false }`. Enqueues
56
55
  a `'removed'` outbox row with `{ doc, meta }` for each removed document (no row
57
- if nothing matched). `removeMany` always uses the reliable per-document path,
58
- same as `updateMany`.
56
+ if nothing matched). For `removeMany`, see "How updateMany/removeMany work" below.
57
+ - Write methods reject `projection` and `includeResultMetadata` (types omit them,
58
+ and they throw at runtime): validation and outbox events need the full
59
+ document. Read the fields you need with `find`/`findOne` afterwards.
59
60
  - `createIndex(fields, options?)` — `fields` is `IndexSpec<T>`: any top-level key of
60
61
  `T` or a dotted path into it (`'users._id'`), following arrays into their element
61
62
  type, mapped to `IndexDirection` (`1 | -1 | 'text' | 'hashed' | '2dsphere' | '2d'`).
@@ -65,12 +66,13 @@ Every `createService<T>(...)` call returns:
65
66
  having to enumerate that far.
66
67
  - `count`, `exists`, `aggregate`, `distinct`, `dropIndex`, `bulkWrite`
67
68
  (requires `{ allowBulkwrite: true }` — guards against accidental unvalidated writes).
68
- `bulkWrite` does not enqueue outbox rows. `dropIndex(indexName, options?)` drops
69
+ `bulkWrite` writes no outbox rows, so it throws on a service that emits them.
70
+ `dropIndex(indexName, options?)` drops
69
71
  a single index by name (not by key spec) and does not enqueue outbox rows.
70
- - `on(event, cb)` / `onPropertiesUpdated(properties, cb)` — subscribe to the
71
- service's own `eventBus` (a plain `node:events` `EventEmitter`). Listeners
72
- registered here are dispatched to by `startOutboxRelay`, not by the write call
73
- itself.
72
+ - `on(event, cb)` / `onPropertiesUpdated(properties, cb)`: register a listener
73
+ for `'created' | 'updated' | 'removed'` on this collection. Returns a function
74
+ that removes it. Listeners are called by `startOutboxRelay` (or
75
+ `dispatchEvent`), not by the write call itself.
74
76
  - `withSession(cb)` / `startSession(options?)` — exported from the module (not
75
77
  per-service) for manual multi-document transactions across several services.
76
78
  - `disconnect()` — exported from the module; closes both MongoDB clients.
@@ -94,8 +96,9 @@ The contract is validator-agnostic — plug in Zod (`schema.safeParse(entity)`),
94
96
  directly — writing to the data collection can't be suppressed per-call, so for
95
97
  each document actually affected, one row is written to an internal `outbox`
96
98
  collection **in the same transaction** as the data write. A separate process,
97
- `startOutboxRelay({ instanceId, ... })`, claims pending rows (atomic leased
98
- `findOneAndUpdate`, safe across multiple pods) and dispatches each to whatever's
99
+ `startOutboxRelay({ instanceId, ... })`, claims pending rows (an atomic
100
+ `findOneAndUpdate` that pushes `nextAttemptAt` past a lease, safe across
101
+ multiple pods) and dispatches each to whatever's
99
102
  registered via `.on(event, cb)` on that collection's service, retrying with
100
103
  backoff and dead-lettering after `maxAttempts`. This means delivery is durable —
101
104
  it survives a crash between the write committing and a listener running — and
@@ -108,10 +111,10 @@ const relay = startOutboxRelay({
108
111
  instanceId: process.env.HOSTNAME ?? 'local', // required — identifies this lease holder
109
112
  pollIntervalMs: 500, // idle poll interval when the outbox is empty
110
113
  batchSize: 20, // rows claimed+dispatched per tick
111
- leaseMs: 30_000, // how long a claim is held before another instance may reclaim it
114
+ leaseMs: 30_000, // how long a claimed row stays hidden from other claims
112
115
  maxAttempts: 10, // attempts before a row is dead-lettered to 'failed'
113
116
  keepDoneMs: 7 * 24 * 60 * 60 * 1000, // how long 'done' rows are kept; Infinity = forever
114
- keepFailedMs: Infinity, // how long 'failed' rows are kept; default forever, for inspection/replay
117
+ keepFailedMs: 90 * 24 * 60 * 60 * 1000, // how long 'failed' rows are kept for inspection/replay; Infinity = forever
115
118
  // backoffMs: (attempt) => ms, // override the default capped-exponential backoff
116
119
  // autoStart: false, // don't start the poll loop — drive tick()/drain() manually (useful in tests)
117
120
  });
@@ -142,8 +145,19 @@ listeners on that event (e.g. a flag a specific handler checks to skip its own
142
145
  side effect, without other listeners missing the event).
143
146
 
144
147
  Set `{ emitOutboxEvents: false }` on `createService` for infra collections with
145
- no listeners (sequences, the outbox itself, etc.) — writes then take a leaner
146
- fast path with no pre-reads and no transaction-wrapped outbox insert.
148
+ no listeners (sequences, counters, the outbox itself, etc.) and for one-off
149
+ scripts such as migrations — writes then take a leaner fast path with no
150
+ pre-reads and no transaction-wrapped outbox insert (see "Transactions").
151
+
152
+ Set `{ toOutboxDoc: (doc) => ... }` on `createService` to decide what a
153
+ collection's outbox rows hold of a document, `doc` and `prevDoc` alike. Rows
154
+ reach every listener, and whatever those store (an audit log, a broker), so a
155
+ secret in a row leaks there. `({ secret, ...hook }) => hook` leaves it out (a
156
+ misspelled field is a type error), and an update that changes only that field
157
+ records no row. To have the change show without the value, return a hash of it
158
+ instead. The function must be pure and must not modify the document it gets. It
159
+ also gets stored documents that were never validated against `T` (older ones,
160
+ `skipValidation` writes), so handle a missing field: a throw fails the write.
147
161
 
148
162
  If a listener needs request/actor context (e.g. an audit log), register
149
163
  `setOutboxContextProvider(fn)` once at startup. It's called **synchronously at
@@ -169,11 +183,10 @@ await getOutboxCollection().updateOne(
169
183
  );
170
184
  ```
171
185
 
172
- `getServiceBus(collectionName)` and `getRegisteredCollections()` are the same
173
- internals the relay itself uses to route a row to its listeners — reach for
174
- them only if you're building custom tooling around the outbox (a dashboard, a
175
- manual replay script); normal listener registration is just `.on(...)` on the
176
- service.
186
+ `getRegisteredCollections()` lists the collections with a service in this
187
+ process (the ones the relay claims rows for). Reach for it only if you're
188
+ building custom tooling around the outbox; normal listener registration is just
189
+ `.on(...)` on the service.
177
190
 
178
191
  ### Multiple consumers
179
192
 
@@ -190,7 +203,10 @@ move is to bridge: run one relay whose only job is to republish each row to a
190
203
  message broker (Kafka, SNS, SQS, etc.), and let downstream services subscribe
191
204
  there. The outbox guarantees the write reaches the broker exactly once; the
192
205
  broker handles fan-out from there. That bridging code lives in your app — this
193
- package has no broker dependency by design.
206
+ package has no broker dependency by design. On the consuming side, hand each
207
+ received event to `dispatchEvent(collectionName, event)`: it calls the
208
+ listeners registered via `.on(...)` exactly like the relay does (awaits all of
209
+ them, rejects with the first failure so your consumer can retry).
194
210
 
195
211
  Until a second independent consumer appears, don't add the broker. Bundle the
196
212
  relay and all listeners in the same process.
@@ -198,41 +214,35 @@ relay and all listeners in the same process.
198
214
  ### Outbox row retention
199
215
 
200
216
  When a row is marked `done` or `failed`, the relay sets its `expireAt` from
201
- `keepDoneMs` (default **7 days**) or `keepFailedMs` (default: never), and a TTL
202
- index deletes it after that. MongoDB's TTL task runs every 60 seconds, so
203
- deletion isn't instant. Retention is stored per row, so changing it needs no
204
- index migration; it applies to rows settled from then on.
205
-
206
- By default `failed` rows are **never** auto-deleted. They accumulate until
207
- manually requeued or removed, so a steady stream of permanently-failing events
208
- will grow the outbox collection unbounded until someone looks at it. Set
209
- `keepFailedMs` to cap that. When requeuing a failed row by hand, clear
217
+ `keepDoneMs` (default **7 days**) or `keepFailedMs` (default **90 days**), and a
218
+ TTL index deletes it after that; `Infinity` keeps rows forever. MongoDB's TTL
219
+ task runs every 60 seconds, so deletion isn't instant. Retention is stored per
220
+ row, so changing it needs no index migration; it applies to rows settled from
221
+ then on. A row no relay processes stays until one does. When requeuing a failed row by hand, clear
210
222
  `expireAt` (as in the snippet above), or the TTL index may delete it while it
211
223
  waits.
212
224
 
213
- Upgrading from v0.1.2 or earlier: the relay drops the old fixed 7-day TTL
214
- index on `processedOn` on startup.
225
+ ## How updateMany/removeMany work
215
226
 
216
- ## updateMany/removeMany always use the reliable per-document path
227
+ Both collect the matched `_id`s, then work through them in chunks of 500 inside a
228
+ single transaction: per chunk, one read of the current documents, one native
229
+ `updateMany`/`deleteMany` by `_id`, and (for `updateMany`) one read of the
230
+ results, plus one outbox insert. A failure in any chunk rolls back every chunk,
231
+ data **and** outbox rows. Every updated document is validated, and each outbox
232
+ row's `{ prevDoc, doc }` pair is exact: once the transaction has read a document,
233
+ any concurrent write to it makes the transaction fail with a transient write
234
+ conflict, and the whole call is retried.
217
235
 
218
- Unlike a single native bulk `updateMany`/`deleteMany` call, both methods process
219
- every matched document one at a time, sequentially (MongoDB doesn't allow
220
- concurrent operations on one session), inside a single transaction so the whole
221
- batch still rolls back together (data **and** outbox rows) on any failure. Every
222
- document is individually validated, and each outbox row's `{ prevDoc, doc }` pair
223
- comes from its own atomic `findOneAndUpdate`/`findOneAndDelete` — not a diff
224
- between two independent before/after scans, which could otherwise miss or
225
- misattribute documents if the matched set changed mid-operation.
236
+ With `emitOutboxEvents: false`, `updateMany` still validates every document
237
+ (skipping the `prevDoc` reads and outbox rows); only without a validator, or
238
+ with `{ skipValidation: true }`, does it become one native `updateMany`.
226
239
 
227
- This path has **no size cap by default** — pass `{ maxBatchSize }` (per-call, or
228
- as a `createService` option) to enforce one and fail fast; a single transaction
229
- processing a very large matched set risks hitting MongoDB's transaction lifetime
230
- limit (default 60s). Past that limit it throws — use `bulkWrite` for larger jobs
231
- (no transaction/validation/event guarantees, but no cap either).
232
-
233
- Internally this is powered by `src/batchify.ts` (sequential `concurrency: 1`,
234
- `throwOnError: true` so a per-document failure aborts the whole transaction) — an
235
- internal implementation detail, not exported from the package.
240
+ There is **no size cap by default**. Pass `{ maxBatchSize }` (per-call, or as a
241
+ `createService` option) to fail fast: a single transaction processing a very
242
+ large matched set risks MongoDB's transaction lifetime limit (default 60s).
243
+ Past the cap it throws before writing anything; it never processes a partial
244
+ batch, so it can't be used to loop. For more than fits one transaction, split
245
+ the work into several calls (see the README for a rerunnable migration loop).
236
246
 
237
247
  ## Transactions
238
248
 
@@ -244,8 +254,13 @@ atomic transaction, pass a session **inside** `session.withTransaction(...)`, as
244
254
  the README. A bare session from `startSession()`/`withSession()` gives each call
245
255
  its own transaction, not a shared one.
246
256
 
247
- Exceptions: `bulkWrite`, and `removeOne`/`removeMany` on a service with
248
- `emitOutboxEvents: false`, don't use a transaction.
257
+ Exceptions: `bulkWrite`, and on a service with `emitOutboxEvents: false` the
258
+ writes a failure couldn't leave half done: `removeOne`/`removeMany`, and
259
+ `upsertOne`/`updateOne`/a single `insert` that validate nothing after the write
260
+ (no validator, or `skipValidation`). One `findOneAndUpdate` or `insertOne` is
261
+ atomic on its own, and a transaction of its own would cost a commit and, on a
262
+ contended document such as a counter, WriteConflict retries. They still join the
263
+ caller's transaction.
249
264
 
250
265
  ## Gotchas (read before integrating)
251
266
 
@@ -265,42 +280,29 @@ Exceptions: `bulkWrite`, and `removeOne`/`removeMany` on a service with
265
280
  somewhere in the deployment.
266
281
  - **Two MongoDB clients per `connect()` call** (primary + secondary-preferred reader).
267
282
  `preferReadFromSecondary: true` only affects `find`/`findCursor`/`count`/`aggregate`.
268
- - **`bulkWrite` requires `{ allowBulkwrite: true }`** — there is no other safety net
269
- (no schema validation runs on bulk writes).
270
- - **`updateOne`'s `prevDoc` can be stale** — and so can `updateMany`/`removeMany`'s
271
- reliable path, since it reuses the exact same single-document logic
272
- (`performSingleUpdate` in `db.ts`). To populate `{ prevDoc, doc }` on the outbox
273
- row, the document is read with a separate `findOne` *before* running the atomic
274
- `findOneAndUpdate` — a concurrent write landing in that gap means `prevDoc` may
275
- not reflect the actual immediately-prior state. The atomic update itself is
276
- unaffected (Mongo still applies it correctly); only the `prevDoc` value in the
277
- outbox row can be stale. Treat `prevDoc` as best-effort, not authoritative, for
278
- anything safety-critical.
279
- - **`emitOutboxEvents: false` (no-outbox) writes only validate one sampled
280
- document on `updateMany`, not every updated document.** For `$set`-with-literal-values
281
- updates this is representative (every matched doc lands on the same value for
282
- the touched fields), but for operators whose result depends on a document's
283
- prior value (`$inc`, `$mul`, `$min`, `$max`, `$push`/`$addToSet`, `$rename`,
284
- positional array filters), the same update can be valid for the sampled
285
- document and invalid for another — undetected on that fast path. The default
286
- reliable path (`emitOutboxEvents: true`, the default) validates every document
287
- individually and doesn't have this gap.
283
+ - **`bulkWrite` requires `{ allowBulkwrite: true }`** and a service with
284
+ `emitOutboxEvents: false` — no schema validation runs on bulk writes, and no
285
+ outbox rows are written.
286
+ - **Rows wait for a relay with a service for their collection.** A relay only
287
+ claims rows of collections with a service in its own process, and a pending
288
+ row never expires. A script that writes with events on, to a collection no
289
+ relay process has a service for, leaves rows behind forever; a relay warns
290
+ about such rows on start once they are an hour overdue. Write from scripts
291
+ through a service with `emitOutboxEvents: false`.
288
292
  - **Nothing dispatches outbox events unless a relay is running.** Outbox rows
289
293
  accumulate (durably, harmlessly) until some process calls `startOutboxRelay`.
290
294
  Make sure at least one instance in your deployment runs it, or `.on(...)`
291
295
  listeners will never fire.
292
- - **`createService` called twice for the same collection name reuses the first
293
- call's `eventBus` (and logs a warning).** The registry mapping collection name
294
- → `eventBus` is process-wide, so without this, a second call's fresh
295
- `EventEmitter` would silently replace the first's, orphaning any listeners
296
- already registered on it. Reuse means `.on(...)`/`onPropertiesUpdated(...)`
296
+ - **`createService` called twice for the same collection name shares the first
297
+ call's listeners (and logs a warning).** Listeners are registered per
298
+ collection name, process-wide, so `.on(...)`/`onPropertiesUpdated(...)`
297
299
  registered on *either* instance still receive dispatched events — but other
298
300
  per-instance config is **not** shared, and a mismatch there is easy to miss
299
- since the shared bus makes everything else look consistent:
301
+ since the shared listeners make everything else look consistent:
300
302
  - **`emitOutboxEvents`** — if one instance has it `false`, writes through
301
- that instance never enqueue an outbox row, so listeners on the shared bus
302
- silently never fire for those writes specifically (not a bus problem —
303
- no event is generated at all). This can be an intentional choice (e.g. a
303
+ that instance never enqueue an outbox row, so the shared listeners
304
+ silently never fire for those writes specifically (no event is generated
305
+ at all). This can be an intentional choice (e.g. a
304
306
  backfill/migration instance skipping outbox writes on purpose), so it's
305
307
  not something `createService` should reject — just be deliberate about it.
306
308
  - **`validate`** — two instances can enforce different schemas on the same
@@ -310,10 +312,34 @@ Exceptions: `bulkWrite`, and `removeOne`/`removeMany` on a service with
310
312
  - **`addCreatedOnField`/`addUpdatedOnField`** — documents written through
311
313
  one instance may end up with `createdOn`/`updatedOn` and documents
312
314
  written through the other may not, for the same collection.
315
+ - **`toOutboxDoc`** — a field one instance keeps out of the outbox reaches
316
+ the shared listeners through the other. Give every instance the same one.
313
317
  Prefer creating each service once (e.g. in a shared module) and importing
314
318
  that instance everywhere it's used, unless one of the above divergences is
315
- actually what you want. Because the bus is shared, `.eventBus
316
- .removeAllListeners()` (or `.off(...)`/`.removeListener(...)`) called on
317
- *either* instance also removes listeners registered via the *other*
318
- instance — teardown code needs to account for that instead of assuming it
319
- only touches its own listeners.
319
+ actually what you want.
320
+
321
+ ## Upgrading
322
+
323
+ `CHANGELOG.md` (shipped with the package) lists what changed in each version.
324
+ The steps that have no place there run once per database, after no relay of the
325
+ older version runs any more. Each says which versions need it, and `dropIndex`
326
+ throws for an index that isn't there, so skip the ones you don't have.
327
+
328
+ ```js
329
+ // From 0.1: rows a 0.1 relay left in status 'processing', which no newer relay claims.
330
+ db.outbox.updateMany({ status: 'processing' }, [
331
+ { $set: { status: 'pending', leasedBy: null, nextAttemptAt: { $ifNull: ['$leasedUntil', '$$NOW'] } } },
332
+ { $unset: 'leasedUntil' },
333
+ ]);
334
+ // From 0.1.2 or older: done rows only the processedOn index expired. 7 days is keepDoneMs's
335
+ // default; use yours.
336
+ db.outbox.updateMany({ status: 'done', expireAt: { $exists: false } }, [
337
+ { $set: { expireAt: { $add: ['$processedOn', 7 * 24 * 60 * 60 * 1000] } } },
338
+ ]);
339
+ db.outbox.dropIndex('processedOn_1'); // 0.1.2 or older
340
+ db.outbox.dropIndex('status_1_nextAttemptAt_1_leasedUntil_1'); // 0.1
341
+ db.outbox.dropIndex('status_1_nextAttemptAt_1'); // 0.2.0
342
+ ```
343
+
344
+ Rows a 0.1 relay dead-lettered keep no expiry, as they did in 0.1: delete them
345
+ once inspected.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahrowe/mongo",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Typed MongoDB CRUD service factory with built-in transactions, event hooks, and optional document validation.",
@@ -23,7 +23,8 @@
23
23
  "types": "./dist/index.d.ts",
24
24
  "files": [
25
25
  "dist",
26
- "docs/CLAUDE.md"
26
+ "docs/CLAUDE.md",
27
+ "CHANGELOG.md"
27
28
  ],
28
29
  "sideEffects": false,
29
30
  "exports": {
@@ -40,6 +41,7 @@
40
41
  "mongodb": "^7.0.0"
41
42
  },
42
43
  "devDependencies": {
44
+ "@changesets/cli": "^3.0.3",
43
45
  "@eslint/js": "^10.0.1",
44
46
  "@faker-js/faker": "^10.1.0",
45
47
  "@types/lodash": "^4.17.21",
@@ -56,6 +58,7 @@
56
58
  "build": "tsc",
57
59
  "lint": "eslint .",
58
60
  "test": "vitest",
61
+ "changeset": "changeset",
59
62
  "release": "bash scripts/release.sh"
60
63
  }
61
64
  }
@@ -1,36 +0,0 @@
1
- interface BatchifyOptions<T> {
2
- /**
3
- * Returns the next batch of data, or an array containing all data (sliced into
4
- * batches automatically). When a function: receives the requested batch size
5
- * and the number of items already processed.
6
- */
7
- dataDelivery?: T[] | ((requestedBatchSize: number, alreadyProcessed: number) => T[] | Promise<T[]>);
8
- /** Number of items fetched per `dataDelivery` call. Default 1000. */
9
- batchSize?: number;
10
- /**
11
- * Number of items from the current batch processed concurrently. Defaults to
12
- * `batchSize` (the whole batch at once). Set to `1` for strictly sequential
13
- * processing — required when items share a MongoDB session/transaction, which
14
- * doesn't allow concurrent operations.
15
- */
16
- concurrency?: number;
17
- /** Function to run for each item. Receives the item and its global index. */
18
- functionToRun?: (data: T, index: number) => void | Promise<void>;
19
- /** Invoked after each batch completes, with the total number of items processed so far. */
20
- onProgress?: (processedAmount: number) => void | Promise<void>;
21
- /** Called when processing an item fails. Receives the failed item and the error. */
22
- onError?: (entry: T, err: unknown) => void | Promise<void>;
23
- /** If true, stop fetching further batches once any error has occurred. */
24
- returnOnError?: boolean;
25
- /**
26
- * If true, rethrow the first error out of `batchify()` itself once the batch
27
- * it occurred in has settled, instead of only invoking `onError`. Use this when
28
- * the caller needs the failure to actually propagate (e.g. to abort a transaction).
29
- */
30
- throwOnError?: boolean;
31
- /** Milliseconds to wait after completing each batch before starting the next. */
32
- pauseAfterBatch?: number;
33
- }
34
- export declare function batchify<T>({ dataDelivery, batchSize, concurrency, functionToRun, onProgress, onError, returnOnError, throwOnError, pauseAfterBatch, }?: BatchifyOptions<T>): Promise<void>;
35
- export {};
36
- //# sourceMappingURL=batchify.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"batchify.d.ts","sourceRoot":"","sources":["../src/batchify.ts"],"names":[],"mappings":"AAAA,UAAU,eAAe,CAAC,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,kBAAkB,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpG,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,2FAA2F;IAC3F,UAAU,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,oFAAoF;IACpF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,0EAA0E;IAC1E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,EAChC,YAAuB,EACvB,SAAgB,EAChB,WAAW,EACX,aAA+B,EAC/B,UAA4B,EAC5B,OAAyB,EACzB,aAAqB,EACrB,YAAoB,EACpB,eAAmB,GACpB,GAAE,eAAe,CAAC,CAAC,CAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDzC"}
package/dist/batchify.js DELETED
@@ -1,57 +0,0 @@
1
- export async function batchify({ dataDelivery = () => [], batchSize = 1000, concurrency, functionToRun = () => undefined, onProgress = () => undefined, onError = () => undefined, returnOnError = false, throwOnError = false, pauseAfterBatch = 0, } = {}) {
2
- const effectiveConcurrency = concurrency ?? batchSize;
3
- let iteration = 0;
4
- let hasError = false;
5
- let firstError;
6
- let currentBatch = [];
7
- const getNextBatch = async () => {
8
- if (typeof dataDelivery === 'function') {
9
- currentBatch = await dataDelivery(batchSize, iteration * batchSize);
10
- }
11
- else {
12
- currentBatch = dataDelivery.slice(batchSize * iteration, batchSize + batchSize * iteration);
13
- }
14
- };
15
- const runItem = async (entry, globalIndex) => {
16
- try {
17
- await functionToRun(entry, globalIndex);
18
- }
19
- catch (error) {
20
- hasError = true;
21
- if (throwOnError && !firstError)
22
- firstError = { error };
23
- await onError(entry, error);
24
- }
25
- };
26
- const processCurrentBatch = async () => {
27
- for (let start = 0; start < currentBatch.length; start += effectiveConcurrency) {
28
- if (firstError)
29
- return;
30
- const chunk = currentBatch.slice(start, start + effectiveConcurrency);
31
- await Promise.allSettled(chunk.map((entry, i) => runItem(entry, batchSize * iteration + start + i)));
32
- }
33
- };
34
- await getNextBatch();
35
- await onProgress(0);
36
- while (currentBatch.length) {
37
- await processCurrentBatch();
38
- if (firstError)
39
- throw firstError.error;
40
- if (currentBatch.length < batchSize) {
41
- await onProgress(batchSize * iteration + currentBatch.length);
42
- return;
43
- }
44
- iteration += 1;
45
- await onProgress(batchSize * iteration);
46
- if (hasError && returnOnError) {
47
- return;
48
- }
49
- await getNextBatch();
50
- if (currentBatch.length && pauseAfterBatch) {
51
- await new Promise((resolve) => {
52
- setTimeout(resolve, pauseAfterBatch);
53
- });
54
- }
55
- }
56
- }
57
- //# sourceMappingURL=batchify.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"batchify.js","sourceRoot":"","sources":["../src/batchify.ts"],"names":[],"mappings":"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"}