@magek/adapter-event-store-nedb 0.0.6 → 0.0.8

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.
@@ -12,4 +12,11 @@ export declare class EventRegistry {
12
12
  store(storableObject: EventEnvelope | EntitySnapshotEnvelope): Promise<void>;
13
13
  deleteAll(): Promise<number>;
14
14
  count(query?: object): Promise<number>;
15
+ /**
16
+ * Marks an event as processed by setting its processedAt timestamp.
17
+ *
18
+ * @param id - The event ID
19
+ * @param processedAt - The ISO timestamp when the event was processed
20
+ */
21
+ markProcessed(id: string, processedAt: string): Promise<void>;
15
22
  }
@@ -5,8 +5,9 @@ const path = require("path");
5
5
  exports.eventsDatabase = path.normalize(path.join('.', '.magek', 'events.json'));
6
6
  const DataStore = require('@seald-io/nedb');
7
7
  class EventRegistry {
8
+ events;
9
+ isLoaded = false;
8
10
  constructor() {
9
- this.isLoaded = false;
10
11
  this.events = new DataStore({ filename: exports.eventsDatabase });
11
12
  }
12
13
  async loadDatabaseIfNeeded() {
@@ -66,5 +67,20 @@ class EventRegistry {
66
67
  await this.loadDatabaseIfNeeded();
67
68
  return await this.events.countAsync(query);
68
69
  }
70
+ /**
71
+ * Marks an event as processed by setting its processedAt timestamp.
72
+ *
73
+ * @param id - The event ID
74
+ * @param processedAt - The ISO timestamp when the event was processed
75
+ */
76
+ async markProcessed(id, processedAt) {
77
+ await this.loadDatabaseIfNeeded();
78
+ await new Promise((resolve, reject) => this.events.update({ _id: id }, { $set: { processedAt } }, {}, (err, numUpdated) => {
79
+ if (err)
80
+ reject(err);
81
+ else
82
+ resolve(numUpdated);
83
+ }));
84
+ }
69
85
  }
70
86
  exports.EventRegistry = EventRegistry;
package/dist/index.js CHANGED
@@ -5,9 +5,9 @@ const event_registry_1 = require("./event-registry");
5
5
  const events_adapter_1 = require("./library/events-adapter");
6
6
  const events_search_adapter_1 = require("./library/events-search-adapter");
7
7
  const event_delete_adapter_1 = require("./library/event-delete-adapter");
8
+ const event_processing_adapter_1 = require("./library/event-processing-adapter");
8
9
  const paths_1 = require("./paths");
9
10
  const fs_1 = require("fs");
10
- const path = require("path");
11
11
  // Pre-built NeDB Event Store Adapter instance
12
12
  const eventRegistry = new event_registry_1.EventRegistry();
13
13
  function notImplemented() {
@@ -16,21 +16,7 @@ function notImplemented() {
16
16
  async function countAll(database) {
17
17
  await database.loadDatabaseAsync();
18
18
  const count = await database.countAsync({});
19
- return count !== null && count !== void 0 ? count : 0;
20
- }
21
- // Function to get userApp from config or load it from standard location
22
- function getUserApp(config) {
23
- // Check if userApp is attached to config
24
- if (config.userApp) {
25
- return config.userApp;
26
- }
27
- // Fallback to loading from standard location
28
- try {
29
- return require(path.join(process.cwd(), 'dist', 'index.js'));
30
- }
31
- catch (error) {
32
- throw new Error('Could not load userApp from config or standard location');
33
- }
19
+ return count ?? 0;
34
20
  }
35
21
  exports.eventStore = {
36
22
  rawToEnvelopes: events_adapter_1.rawEventsToEnvelopes,
@@ -39,10 +25,7 @@ exports.eventStore = {
39
25
  produce: notImplemented,
40
26
  forEntitySince: (config, entityTypeName, entityID, since) => (0, events_adapter_1.readEntityEventsSince)(eventRegistry, config, entityTypeName, entityID, since),
41
27
  latestEntitySnapshot: (config, entityTypeName, entityID) => (0, events_adapter_1.readEntityLatestSnapshot)(eventRegistry, config, entityTypeName, entityID),
42
- store: (eventEnvelopes, config) => {
43
- const userApp = getUserApp(config);
44
- return (0, events_adapter_1.storeEvents)(userApp, eventRegistry, eventEnvelopes, config);
45
- },
28
+ store: (eventEnvelopes, config) => (0, events_adapter_1.storeEvents)(eventRegistry, eventEnvelopes, config),
46
29
  storeSnapshot: (snapshotEnvelope, config) => (0, events_adapter_1.storeSnapshot)(eventRegistry, snapshotEnvelope, config),
47
30
  search: (config, parameters) => (0, events_search_adapter_1.searchEvents)(eventRegistry, config, parameters),
48
31
  searchEntitiesIDs: (config, limit, afterCursor, entityTypeName) => (0, events_search_adapter_1.searchEntitiesIds)(eventRegistry, config, limit, afterCursor, entityTypeName),
@@ -62,4 +45,6 @@ exports.eventStore = {
62
45
  },
63
46
  urls: async () => [paths_1.eventsDatabase],
64
47
  },
48
+ fetchUnprocessedEvents: (config) => (0, event_processing_adapter_1.fetchUnprocessedEvents)(eventRegistry, config),
49
+ markEventProcessed: (config, eventId) => (0, event_processing_adapter_1.markEventProcessed)(eventRegistry, config, eventId),
65
50
  };
@@ -0,0 +1,19 @@
1
+ import { MagekConfig, EventEnvelope, UUID } from '@magek/common';
2
+ import { EventRegistry } from '../event-registry';
3
+ /**
4
+ * Fetches the next batch of unprocessed events.
5
+ * Reads cursor from file and fetches events after that position.
6
+ *
7
+ * @param eventRegistry - The event registry instance
8
+ * @param config - The Magek configuration object
9
+ * @returns An array of EventEnvelope objects
10
+ */
11
+ export declare function fetchUnprocessedEvents(eventRegistry: EventRegistry, config: MagekConfig): Promise<Array<EventEnvelope>>;
12
+ /**
13
+ * Marks a single event as processed and advances the cursor.
14
+ *
15
+ * @param eventRegistry - The event registry instance
16
+ * @param config - The Magek configuration object
17
+ * @param eventId - The ID of the event that was processed
18
+ */
19
+ export declare function markEventProcessed(eventRegistry: EventRegistry, config: MagekConfig, eventId: UUID): Promise<void>;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchUnprocessedEvents = fetchUnprocessedEvents;
4
+ exports.markEventProcessed = markEventProcessed;
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const common_1 = require("@magek/common");
8
+ const paths_1 = require("../paths");
9
+ const originOfTime = new Date(0).toISOString();
10
+ function readCursor() {
11
+ try {
12
+ if (!fs.existsSync(paths_1.eventProcessingCursorFile)) {
13
+ return null;
14
+ }
15
+ const data = fs.readFileSync(paths_1.eventProcessingCursorFile, 'utf-8');
16
+ const cursorData = JSON.parse(data);
17
+ return cursorData.cursor;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ function writeCursor(cursor) {
24
+ const cursorData = { cursor };
25
+ // Ensure the directory exists before writing
26
+ const dir = path.dirname(paths_1.eventProcessingCursorFile);
27
+ if (!fs.existsSync(dir)) {
28
+ fs.mkdirSync(dir, { recursive: true });
29
+ }
30
+ fs.writeFileSync(paths_1.eventProcessingCursorFile, JSON.stringify(cursorData, null, 2), 'utf-8');
31
+ }
32
+ /**
33
+ * Fetches the next batch of unprocessed events.
34
+ * Reads cursor from file and fetches events after that position.
35
+ *
36
+ * @param eventRegistry - The event registry instance
37
+ * @param config - The Magek configuration object
38
+ * @returns An array of EventEnvelope objects
39
+ */
40
+ async function fetchUnprocessedEvents(eventRegistry, config) {
41
+ const logger = (0, common_1.getLogger)(config, 'event-processing-adapter#fetchUnprocessedEvents');
42
+ const cursor = readCursor() ?? originOfTime;
43
+ const batchSize = config.eventProcessingBatchSize;
44
+ const query = {
45
+ kind: 'event',
46
+ deletedAt: { $exists: false },
47
+ processedAt: { $exists: false },
48
+ createdAt: { $gt: cursor },
49
+ };
50
+ logger.debug(`Fetching events after cursor ${cursor} (batch size: ${batchSize})`);
51
+ const result = await eventRegistry.query(query, 1, batchSize); // Sort ascending by createdAt
52
+ logger.debug(`Fetched ${result.length} unprocessed events`);
53
+ return result;
54
+ }
55
+ /**
56
+ * Marks a single event as processed and advances the cursor.
57
+ *
58
+ * @param eventRegistry - The event registry instance
59
+ * @param config - The Magek configuration object
60
+ * @param eventId - The ID of the event that was processed
61
+ */
62
+ async function markEventProcessed(eventRegistry, config, eventId) {
63
+ const logger = (0, common_1.getLogger)(config, 'event-processing-adapter#markEventProcessed');
64
+ const processedAt = (0, common_1.getTimestampGenerator)().next();
65
+ // Fetch the event to get its createdAt for the cursor
66
+ const eventIdStr = eventId.toString();
67
+ const events = await eventRegistry.query({ _id: eventIdStr });
68
+ if (events.length === 0) {
69
+ logger.warn(`Event ${eventId} not found, cannot mark as processed`);
70
+ return;
71
+ }
72
+ const event = events[0];
73
+ logger.debug(`Marking event ${eventId} as processed at ${processedAt}`);
74
+ await eventRegistry.markProcessed(eventIdStr, processedAt);
75
+ // Update cursor to this event's createdAt timestamp
76
+ writeCursor(event.createdAt);
77
+ logger.debug(`Cursor advanced to ${event.createdAt}`);
78
+ }
@@ -1,9 +1,9 @@
1
- import { UUID, UserApp, MagekConfig, EventEnvelope, EntitySnapshotEnvelope, NonPersistedEventEnvelope, NonPersistedEntitySnapshotEnvelope } from '@magek/common';
1
+ import { UUID, MagekConfig, EventEnvelope, EntitySnapshotEnvelope, NonPersistedEventEnvelope, NonPersistedEntitySnapshotEnvelope } from '@magek/common';
2
2
  import { EventRegistry } from '../event-registry';
3
3
  export declare function rawEventsToEnvelopes(rawEvents: Array<unknown>): Array<EventEnvelope>;
4
4
  export declare function readEntityEventsSince(eventRegistry: EventRegistry, config: MagekConfig, entityTypeName: string, entityID: UUID, since?: string): Promise<Array<EventEnvelope>>;
5
5
  export declare function readEntityLatestSnapshot(eventRegistry: EventRegistry, config: MagekConfig, entityTypeName: string, entityID: UUID): Promise<EntitySnapshotEnvelope | undefined>;
6
- export declare function storeEvents(userApp: UserApp, eventRegistry: EventRegistry, nonPersistedEventEnvelopes: Array<NonPersistedEventEnvelope>, config: MagekConfig): Promise<Array<EventEnvelope>>;
6
+ export declare function storeEvents(eventRegistry: EventRegistry, nonPersistedEventEnvelopes: Array<NonPersistedEventEnvelope>, config: MagekConfig): Promise<Array<EventEnvelope>>;
7
7
  export declare function storeSnapshot(eventRegistry: EventRegistry, snapshotEnvelope: NonPersistedEntitySnapshotEnvelope, config: MagekConfig): Promise<EntitySnapshotEnvelope>;
8
8
  /**
9
9
  * Dummy method that'll always return true, since local provider won't be tracking dispatched events
@@ -46,20 +46,16 @@ async function readEntityLatestSnapshot(eventRegistry, config, entityTypeName, e
46
46
  return undefined;
47
47
  }
48
48
  }
49
- async function storeEvents(userApp, eventRegistry, nonPersistedEventEnvelopes, config) {
49
+ async function storeEvents(eventRegistry, nonPersistedEventEnvelopes, config) {
50
50
  const logger = (0, common_1.getLogger)(config, 'events-adapter#storeEvents');
51
51
  logger.debug('Storing the following event envelopes:', nonPersistedEventEnvelopes);
52
52
  const persistedEventEnvelopes = [];
53
53
  for (const nonPersistedEventEnvelope of nonPersistedEventEnvelopes) {
54
- const persistableEventEnvelope = {
55
- ...nonPersistedEventEnvelope,
56
- createdAt: new Date().toISOString(),
57
- };
54
+ const persistableEventEnvelope = nonPersistedEventEnvelope;
58
55
  await (0, common_1.retryIfError)(async () => await persistEvent(eventRegistry, persistableEventEnvelope), common_1.OptimisticConcurrencyUnexpectedVersionError);
59
56
  persistedEventEnvelopes.push(persistableEventEnvelope);
60
57
  }
61
58
  logger.debug('EventEnvelopes stored: ', persistedEventEnvelopes);
62
- await userApp.eventDispatcher(persistedEventEnvelopes);
63
59
  return persistedEventEnvelopes;
64
60
  }
65
61
  async function storeSnapshot(eventRegistry, snapshotEnvelope, config) {
@@ -68,7 +64,7 @@ async function storeSnapshot(eventRegistry, snapshotEnvelope, config) {
68
64
  const persistableEntitySnapshot = {
69
65
  ...snapshotEnvelope,
70
66
  createdAt: snapshotEnvelope.snapshottedEventCreatedAt,
71
- persistedAt: new Date().toISOString(),
67
+ persistedAt: (0, common_1.getTimestampGenerator)().next(),
72
68
  };
73
69
  await (0, common_1.retryIfError)(() => eventRegistry.store(persistableEntitySnapshot), common_1.OptimisticConcurrencyUnexpectedVersionError);
74
70
  logger.debug('Snapshot stored');
@@ -18,7 +18,6 @@ async function searchEvents(eventRegistry, config, parameters) {
18
18
  return eventsSearchResponses;
19
19
  }
20
20
  async function searchEntitiesIds(eventRegistry, config, limit, afterCursor, entityTypeName) {
21
- var _a;
22
21
  const logger = (0, common_1.getLogger)(config, 'events-searcher-adapter#searchEntitiesIds');
23
22
  logger.debug(`Initiating a paginated events search. limit: ${limit}, afterCursor: ${JSON.stringify(afterCursor)}, entityTypeName: ${entityTypeName}`);
24
23
  const filterQuery = {
@@ -30,15 +29,15 @@ async function searchEntitiesIds(eventRegistry, config, limit, afterCursor, enti
30
29
  entityID: 1,
31
30
  }));
32
31
  // Nedb doesn't support DISTINCT, so we need to paginate the results manually
33
- const entitiesIds = result ? result === null || result === void 0 ? void 0 : result.map((v) => v.entityID) : [];
32
+ const entitiesIds = result ? result?.map((v) => v.entityID) : [];
34
33
  const uniqueResult = (0, common_1.unique)(entitiesIds);
35
- const skipId = (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) ? parseInt(afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) : 0;
34
+ const skipId = afterCursor?.id ? parseInt(afterCursor?.id) : 0;
36
35
  const paginated = uniqueResult.slice(skipId, skipId + limit);
37
36
  const paginatedResult = paginated.map((v) => ({ entityID: v }));
38
37
  logger.debug('Unique events search result', paginatedResult);
39
38
  return {
40
39
  items: paginatedResult,
41
- count: (_a = paginatedResult === null || paginatedResult === void 0 ? void 0 : paginatedResult.length) !== null && _a !== void 0 ? _a : 0,
40
+ count: paginatedResult?.length ?? 0,
42
41
  cursor: { id: ((limit ? limit : 1) + skipId).toString() },
43
42
  };
44
43
  }
@@ -50,7 +50,7 @@ function resultToEventSearchResponse(result) {
50
50
  deletedAt: item.deletedAt,
51
51
  };
52
52
  });
53
- return eventSearchResult !== null && eventSearchResult !== void 0 ? eventSearchResult : [];
53
+ return eventSearchResult ?? [];
54
54
  }
55
55
  function buildByEntityAndID(entityValue, entityIdValue) {
56
56
  return {
package/dist/paths.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export declare const eventsDatabase: string;
2
+ export declare const eventProcessingCursorFile: string;
package/dist/paths.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.eventsDatabase = void 0;
3
+ exports.eventProcessingCursorFile = exports.eventsDatabase = void 0;
4
4
  const path = require("path");
5
5
  exports.eventsDatabase = internalPath('events.json');
6
+ exports.eventProcessingCursorFile = internalPath('event-processing-cursor.json');
6
7
  function internalPath(filename) {
7
8
  return path.normalize(path.join('.', '.magek', filename));
8
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magek/adapter-event-store-nedb",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Nedb-based event store adapter for the Magek framework",
5
5
  "keywords": [
6
6
  "event-store",
@@ -24,7 +24,7 @@
24
24
  "node": ">=22.0.0 <23.0.0"
25
25
  },
26
26
  "dependencies": {
27
- "@magek/common": "^0.0.6",
27
+ "@magek/common": "^0.0.8",
28
28
  "@seald-io/nedb": "4.1.2",
29
29
  "tslib": "2.8.1"
30
30
  },
@@ -32,7 +32,7 @@
32
32
  "url": "https://github.com/theam/magek/issues"
33
33
  },
34
34
  "devDependencies": {
35
- "@magek/eslint-config": "^0.0.6",
35
+ "@magek/eslint-config": "^0.0.8",
36
36
  "@types/chai": "5.2.3",
37
37
  "@types/chai-as-promised": "8.0.2",
38
38
  "@types/mocha": "10.0.10",