@ossy/event-store 3.2.0 → 3.6.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/event-store",
3
- "version": "3.2.0",
3
+ "version": "3.6.2",
4
4
  "description": "Ossy Event Store — Aggregate, EventStore, and MongoDB client",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
- "@ossy/fold": "^3.0.9",
28
+ "@ossy/fold": "^3.4.0",
29
29
  "@ossy/observability": "^3.0.9",
30
30
  "mongodb": "^7.2.0",
31
31
  "nanoid": "^5.1.11"
@@ -37,5 +37,5 @@
37
37
  "files": [
38
38
  "src"
39
39
  ],
40
- "gitHead": "b045f1fd7f2fe00c776b9ea96f76083b93fd8556"
40
+ "gitHead": "d81b840c5421995e8ac7550e5b37ae1145abcba9"
41
41
  }
@@ -95,18 +95,25 @@ export class AggregateRebuild {
95
95
  return
96
96
  }
97
97
 
98
- return EventStore.GetResourceStreams()
99
- .then(streams => Promise.allSettled(
100
- streams.map(({ resourceId, type }) => AggregateRebuild.BuildAndSaveResource(resourceId, type)),
101
- ))
102
- .then(async results => {
103
- await ProjectionRebuild.rebuildAll()
104
- const failed = results.filter(r => r.status === 'rejected')
105
- const success = results.filter(r => r.status === 'fulfilled')
106
- log.info('[AggregateRebuild][BuildAndSaveAll] Finished building aggregates')
107
- log.info(`[AggregateRebuild][BuildAndSaveAll] Failed: ${failed.length}`)
108
- log.info(`[AggregateRebuild][BuildAndSaveAll] Success: ${success.length}`)
109
- })
98
+ // Fan-out of every stream at once OOMs 1–2 GB Fargate tasks (website-ossy
99
+ // deploy 2026-08-20). Rebuild in batches so Mongo load and RSS stay bounded.
100
+ const streams = await EventStore.GetResourceStreams()
101
+ const results = []
102
+ for (let offset = 0; offset < streams.length; offset += 8) {
103
+ const batchResults = await Promise.allSettled(
104
+ streams.slice(offset, offset + 8).map(({ resourceId, type }) =>
105
+ AggregateRebuild.BuildAndSaveResource(resourceId, type),
106
+ ),
107
+ )
108
+ results.push(...batchResults)
109
+ }
110
+
111
+ await ProjectionRebuild.rebuildAll()
112
+ const failed = results.filter(r => r.status === 'rejected')
113
+ const success = results.filter(r => r.status === 'fulfilled')
114
+ log.info('[AggregateRebuild][BuildAndSaveAll] Finished building aggregates')
115
+ log.info(`[AggregateRebuild][BuildAndSaveAll] Failed: ${failed.length}`)
116
+ log.info(`[AggregateRebuild][BuildAndSaveAll] Success: ${success.length}`)
110
117
  }
111
118
  }
112
119
 
@@ -1,5 +1,7 @@
1
- import { describe, expect, it } from '@jest/globals'
2
- import { selectTailEventsForRebuild } from './aggregate-rebuild.js'
1
+ import { afterEach, describe, expect, it, jest } from '@jest/globals'
2
+ import { AggregateRebuild, selectTailEventsForRebuild } from './aggregate-rebuild.js'
3
+ import { EventStore } from './event-store.js'
4
+ import { ProjectionRebuild } from './projection-rebuild.js'
3
5
 
4
6
  describe('selectTailEventsForRebuild', () => {
5
7
  const resourceId = 'res_1'
@@ -40,6 +42,45 @@ describe('selectTailEventsForRebuild', () => {
40
42
  })
41
43
  })
42
44
 
45
+ describe('BuildAndSaveAll', () => {
46
+ afterEach(() => {
47
+ AggregateRebuild._schemaMap = {}
48
+ AggregateRebuild._aggregateMap = {}
49
+ jest.restoreAllMocks()
50
+ })
51
+
52
+ it('rebuilds resource streams in batches of 8', async () => {
53
+ AggregateRebuild.registerAggregate({
54
+ id: '@ossy/test/aggregate',
55
+ Aggregate: class {
56
+ static SchemaId = 'test.schema'
57
+ },
58
+ })
59
+
60
+ const streams = Array.from({ length: 10 }, (_, i) => ({
61
+ resourceId: `res_${i}`,
62
+ type: 'test.schema',
63
+ }))
64
+ jest.spyOn(EventStore, 'GetResourceStreams').mockResolvedValue(streams)
65
+
66
+ let inFlight = 0
67
+ let maxInFlight = 0
68
+ jest.spyOn(AggregateRebuild, 'BuildAndSaveResource').mockImplementation(async () => {
69
+ inFlight += 1
70
+ maxInFlight = Math.max(maxInFlight, inFlight)
71
+ await new Promise(resolve => setTimeout(resolve, 15))
72
+ inFlight -= 1
73
+ })
74
+ jest.spyOn(ProjectionRebuild, 'rebuildAll').mockResolvedValue()
75
+
76
+ await AggregateRebuild.BuildAndSaveAll()
77
+
78
+ expect(AggregateRebuild.BuildAndSaveResource).toHaveBeenCalledTimes(10)
79
+ expect(maxInFlight).toBeLessThanOrEqual(8)
80
+ expect(ProjectionRebuild.rebuildAll).toHaveBeenCalledTimes(1)
81
+ })
82
+ })
83
+
43
84
  describe('incremental rebuild semantics', () => {
44
85
  it('folds only tail events onto snapshot state', () => {
45
86
  const View = (events, state = {}) =>