@neofinancial/chrono-mongo-datastore 0.4.1-next.1 → 0.5.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/README.md CHANGED
@@ -22,6 +22,18 @@ pnpm add @neofinancial/chrono-mongo-datastore
22
22
  yarn add @neofinancial/chrono-mongo-datastore
23
23
  ```
24
24
 
25
+ This package supports both **CommonJS** and **ES Modules**:
26
+
27
+ ```typescript
28
+ // ESM
29
+ import { ChronoMongoDatastore } from "@neofinancial/chrono-mongo-datastore";
30
+
31
+ // CommonJS
32
+ const {
33
+ ChronoMongoDatastore,
34
+ } = require("@neofinancial/chrono-mongo-datastore");
35
+ ```
36
+
25
37
  ## Peer Dependencies
26
38
 
27
39
  `@neofinancial/chrono` and `mongodb`
@@ -0,0 +1,52 @@
1
+ import { ClaimTaskInput, Datastore, DeleteInput, DeleteOptions, ScheduleInput, Task, TaskMappingBase } from "@neofinancial/chrono";
2
+ import { ClientSession, Db } from "mongodb";
3
+
4
+ //#region src/chrono-mongo-datastore.d.ts
5
+ type ChronoMongoDatastoreConfig = {
6
+ /**
7
+ * The TTL (in seconds) for completed documents.
8
+ *
9
+ * @default 60 * 60 * 24 * 30 // 30 days
10
+ * @type {number}
11
+ */
12
+ completedDocumentTTLSeconds?: number;
13
+ /**
14
+ * The name of the collection to use for the datastore.
15
+ *
16
+ * @type {string}
17
+ */
18
+ collectionName: string;
19
+ };
20
+ type MongoDatastoreOptions = {
21
+ session?: ClientSession;
22
+ };
23
+ declare class ChronoMongoDatastore<TaskMapping extends TaskMappingBase> implements Datastore<TaskMapping, MongoDatastoreOptions> {
24
+ private config;
25
+ private database;
26
+ private databaseResolvers;
27
+ constructor(config?: Partial<ChronoMongoDatastoreConfig>);
28
+ /**
29
+ * Sets the database connection for the datastore. Ensures that the indexes are created and resolves any pending promises waiting for the database.
30
+ *
31
+ * @param database - The database to set.
32
+ */
33
+ initialize(database: Db): Promise<void>;
34
+ /**
35
+ * Asyncronously gets the database connection for the datastore. If the database is not set, it will return a promise that resolves when the database is set.
36
+ *
37
+ * @returns The database connection.
38
+ */
39
+ getDatabase(): Promise<Db>;
40
+ schedule<TaskKind extends keyof TaskMapping>(input: ScheduleInput<TaskKind, TaskMapping[TaskKind], MongoDatastoreOptions>): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
41
+ delete<TaskKind extends Extract<keyof TaskMapping, string>>(key: DeleteInput<TaskKind>, options?: DeleteOptions): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
42
+ claim<TaskKind extends Extract<keyof TaskMapping, string>>(input: ClaimTaskInput<TaskKind>): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
43
+ retry<TaskKind extends keyof TaskMapping>(taskId: string, retryAt: Date): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
44
+ complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
45
+ fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
46
+ private updateOrThrow;
47
+ private collection;
48
+ private toObject;
49
+ }
50
+ //#endregion
51
+ export { ChronoMongoDatastore, type ChronoMongoDatastoreConfig, type MongoDatastoreOptions };
52
+ //# sourceMappingURL=index.d.mts.map
package/build/index.d.ts CHANGED
@@ -1 +1,52 @@
1
- export { ChronoMongoDatastore, type ChronoMongoDatastoreConfig, type MongoDatastoreOptions, } from './chrono-mongo-datastore';
1
+ import { ClaimTaskInput, Datastore, DeleteInput, DeleteOptions, ScheduleInput, Task, TaskMappingBase } from "@neofinancial/chrono";
2
+ import { ClientSession, Db } from "mongodb";
3
+
4
+ //#region src/chrono-mongo-datastore.d.ts
5
+ type ChronoMongoDatastoreConfig = {
6
+ /**
7
+ * The TTL (in seconds) for completed documents.
8
+ *
9
+ * @default 60 * 60 * 24 * 30 // 30 days
10
+ * @type {number}
11
+ */
12
+ completedDocumentTTLSeconds?: number;
13
+ /**
14
+ * The name of the collection to use for the datastore.
15
+ *
16
+ * @type {string}
17
+ */
18
+ collectionName: string;
19
+ };
20
+ type MongoDatastoreOptions = {
21
+ session?: ClientSession;
22
+ };
23
+ declare class ChronoMongoDatastore<TaskMapping extends TaskMappingBase> implements Datastore<TaskMapping, MongoDatastoreOptions> {
24
+ private config;
25
+ private database;
26
+ private databaseResolvers;
27
+ constructor(config?: Partial<ChronoMongoDatastoreConfig>);
28
+ /**
29
+ * Sets the database connection for the datastore. Ensures that the indexes are created and resolves any pending promises waiting for the database.
30
+ *
31
+ * @param database - The database to set.
32
+ */
33
+ initialize(database: Db): Promise<void>;
34
+ /**
35
+ * Asyncronously gets the database connection for the datastore. If the database is not set, it will return a promise that resolves when the database is set.
36
+ *
37
+ * @returns The database connection.
38
+ */
39
+ getDatabase(): Promise<Db>;
40
+ schedule<TaskKind extends keyof TaskMapping>(input: ScheduleInput<TaskKind, TaskMapping[TaskKind], MongoDatastoreOptions>): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
41
+ delete<TaskKind extends Extract<keyof TaskMapping, string>>(key: DeleteInput<TaskKind>, options?: DeleteOptions): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
42
+ claim<TaskKind extends Extract<keyof TaskMapping, string>>(input: ClaimTaskInput<TaskKind>): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
43
+ retry<TaskKind extends keyof TaskMapping>(taskId: string, retryAt: Date): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
44
+ complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
45
+ fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
46
+ private updateOrThrow;
47
+ private collection;
48
+ private toObject;
49
+ }
50
+ //#endregion
51
+ export { ChronoMongoDatastore, type ChronoMongoDatastoreConfig, type MongoDatastoreOptions };
52
+ //# sourceMappingURL=index.d.ts.map
package/build/index.js CHANGED
@@ -1,6 +1,219 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ChronoMongoDatastore = void 0;
4
- var chrono_mongo_datastore_1 = require("./chrono-mongo-datastore");
5
- Object.defineProperty(exports, "ChronoMongoDatastore", { enumerable: true, get: function () { return chrono_mongo_datastore_1.ChronoMongoDatastore; } });
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let __neofinancial_chrono = require("@neofinancial/chrono");
25
+ __neofinancial_chrono = __toESM(__neofinancial_chrono);
26
+ let mongodb = require("mongodb");
27
+ mongodb = __toESM(mongodb);
28
+
29
+ //#region src/mongo-indexes.ts
30
+ const DEFAULT_EXPIRY_SECONDS = 3600 * 24 * 30;
31
+ const IndexNames = {
32
+ COMPLETED_DOCUMENT_TTL_INDEX: "chrono-completed-document-ttl-index",
33
+ CLAIM_DOCUMENT_INDEX: "chrono-claim-document-index",
34
+ IDEMPOTENCY_KEY_INDEX: "chrono-idempotency-key-index"
35
+ };
36
+ async function ensureIndexes(collection, options) {
37
+ await collection.createIndex({ completedAt: -1 }, {
38
+ partialFilterExpression: {
39
+ completedAt: { $exists: true },
40
+ status: { $eq: __neofinancial_chrono.TaskStatus.COMPLETED }
41
+ },
42
+ expireAfterSeconds: options.expireAfterSeconds || DEFAULT_EXPIRY_SECONDS,
43
+ name: IndexNames.COMPLETED_DOCUMENT_TTL_INDEX
44
+ });
45
+ await collection.createIndex({
46
+ kind: 1,
47
+ status: 1,
48
+ scheduledAt: 1,
49
+ priority: -1,
50
+ claimedAt: 1
51
+ }, { name: IndexNames.CLAIM_DOCUMENT_INDEX });
52
+ await collection.createIndex({ idempotencyKey: 1 }, {
53
+ name: IndexNames.IDEMPOTENCY_KEY_INDEX,
54
+ unique: true,
55
+ sparse: true
56
+ });
57
+ }
58
+
59
+ //#endregion
60
+ //#region src/chrono-mongo-datastore.ts
61
+ const DEFAULT_COLLECTION_NAME = "chrono-tasks";
62
+ var ChronoMongoDatastore = class {
63
+ config;
64
+ database;
65
+ databaseResolvers = [];
66
+ constructor(config) {
67
+ this.config = {
68
+ completedDocumentTTLSeconds: config?.completedDocumentTTLSeconds,
69
+ collectionName: config?.collectionName || DEFAULT_COLLECTION_NAME
70
+ };
71
+ }
72
+ /**
73
+ * Sets the database connection for the datastore. Ensures that the indexes are created and resolves any pending promises waiting for the database.
74
+ *
75
+ * @param database - The database to set.
76
+ */
77
+ async initialize(database) {
78
+ if (this.database) throw new Error("Database connection already set");
79
+ await ensureIndexes(database.collection(this.config.collectionName), { expireAfterSeconds: this.config.completedDocumentTTLSeconds });
80
+ this.database = database;
81
+ const resolvers = this.databaseResolvers.splice(0);
82
+ for (const resolve of resolvers) resolve(database);
83
+ }
84
+ /**
85
+ * Asyncronously gets the database connection for the datastore. If the database is not set, it will return a promise that resolves when the database is set.
86
+ *
87
+ * @returns The database connection.
88
+ */
89
+ async getDatabase() {
90
+ if (this.database) return this.database;
91
+ return new Promise((resolve) => {
92
+ this.databaseResolvers.push(resolve);
93
+ });
94
+ }
95
+ async schedule(input) {
96
+ const createInput = {
97
+ kind: input.kind,
98
+ status: __neofinancial_chrono.TaskStatus.PENDING,
99
+ data: input.data,
100
+ priority: input.priority,
101
+ idempotencyKey: input.idempotencyKey,
102
+ originalScheduleDate: input.when,
103
+ scheduledAt: input.when,
104
+ retryCount: 0
105
+ };
106
+ try {
107
+ const results = await (await this.getDatabase()).collection(this.config.collectionName).insertOne(createInput, {
108
+ ...input?.datastoreOptions?.session ? { session: input.datastoreOptions.session } : void 0,
109
+ ignoreUndefined: true
110
+ });
111
+ if (results.acknowledged) return this.toObject({
112
+ _id: results.insertedId,
113
+ ...createInput
114
+ });
115
+ } catch (error) {
116
+ if (input.idempotencyKey && error instanceof Error && "code" in error && (error.code === 11e3 || error.code === 11001)) {
117
+ const existingTask = await (await this.collection()).findOne({ idempotencyKey: input.idempotencyKey }, {
118
+ hint: IndexNames.IDEMPOTENCY_KEY_INDEX,
119
+ ...input.datastoreOptions?.session ? { session: input.datastoreOptions.session } : void 0
120
+ });
121
+ if (existingTask) return this.toObject(existingTask);
122
+ throw new Error(`Failed to find existing task with idempotency key ${input.idempotencyKey} despite unique index error`);
123
+ }
124
+ throw error;
125
+ }
126
+ throw new Error(`Failed to insert ${String(input.kind)} document`);
127
+ }
128
+ async delete(key, options) {
129
+ const filter = typeof key === "string" ? { _id: new mongodb.ObjectId(key) } : {
130
+ kind: key.kind,
131
+ idempotencyKey: key.idempotencyKey
132
+ };
133
+ const task = await (await this.collection()).findOneAndDelete({
134
+ ...filter,
135
+ ...options?.force ? {} : { status: __neofinancial_chrono.TaskStatus.PENDING }
136
+ });
137
+ if (!task) {
138
+ if (options?.force) return;
139
+ const description = typeof key === "string" ? `with id ${key}` : `with kind ${String(key.kind)} and idempotencyKey ${key.idempotencyKey}`;
140
+ throw new Error(`Task ${description} can not be deleted as it may not exist or it's not in PENDING status.`);
141
+ }
142
+ return this.toObject(task);
143
+ }
144
+ async claim(input) {
145
+ const now = /* @__PURE__ */ new Date();
146
+ const task = await (await this.collection()).findOneAndUpdate({
147
+ kind: input.kind,
148
+ scheduledAt: { $lte: now },
149
+ $or: [{ status: __neofinancial_chrono.TaskStatus.PENDING }, {
150
+ status: __neofinancial_chrono.TaskStatus.CLAIMED,
151
+ claimedAt: { $lte: new Date(now.getTime() - input.claimStaleTimeoutMs) }
152
+ }]
153
+ }, { $set: {
154
+ status: __neofinancial_chrono.TaskStatus.CLAIMED,
155
+ claimedAt: now
156
+ } }, {
157
+ sort: {
158
+ priority: -1,
159
+ scheduledAt: 1
160
+ },
161
+ returnDocument: "after"
162
+ });
163
+ return task ? this.toObject(task) : void 0;
164
+ }
165
+ async retry(taskId, retryAt) {
166
+ const taskDocument = await this.updateOrThrow(taskId, {
167
+ $set: {
168
+ status: __neofinancial_chrono.TaskStatus.PENDING,
169
+ scheduledAt: retryAt
170
+ },
171
+ $inc: { retryCount: 1 }
172
+ });
173
+ return this.toObject(taskDocument);
174
+ }
175
+ async complete(taskId) {
176
+ const now = /* @__PURE__ */ new Date();
177
+ const task = await this.updateOrThrow(taskId, { $set: {
178
+ status: __neofinancial_chrono.TaskStatus.COMPLETED,
179
+ completedAt: now,
180
+ lastExecutedAt: now
181
+ } });
182
+ return this.toObject(task);
183
+ }
184
+ async fail(taskId) {
185
+ const now = /* @__PURE__ */ new Date();
186
+ const task = await this.updateOrThrow(taskId, { $set: {
187
+ status: __neofinancial_chrono.TaskStatus.FAILED,
188
+ lastExecutedAt: now
189
+ } });
190
+ return this.toObject(task);
191
+ }
192
+ async updateOrThrow(taskId, update) {
193
+ const document = await (await this.collection()).findOneAndUpdate({ _id: new mongodb.ObjectId(taskId) }, update, { returnDocument: "after" });
194
+ if (!document) throw new Error(`Task with ID ${taskId} not found`);
195
+ return document;
196
+ }
197
+ async collection() {
198
+ return (await this.getDatabase()).collection(this.config.collectionName);
199
+ }
200
+ toObject(document) {
201
+ return {
202
+ id: document._id.toHexString(),
203
+ data: document.data,
204
+ kind: document.kind,
205
+ status: document.status,
206
+ priority: document.priority ?? void 0,
207
+ idempotencyKey: document.idempotencyKey ?? void 0,
208
+ originalScheduleDate: document.originalScheduleDate,
209
+ scheduledAt: document.scheduledAt,
210
+ claimedAt: document.claimedAt ?? void 0,
211
+ completedAt: document.completedAt ?? void 0,
212
+ retryCount: document.retryCount
213
+ };
214
+ }
215
+ };
216
+
217
+ //#endregion
218
+ exports.ChronoMongoDatastore = ChronoMongoDatastore;
6
219
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mEAIkC;AAHhC,8HAAA,oBAAoB,OAAA"}
1
+ {"version":3,"file":"index.js","names":["TaskStatus","createInput: OptionalId<TaskDocument<TaskKind, TaskMapping[TaskKind]>>","TaskStatus","ObjectId"],"sources":["../src/mongo-indexes.ts","../src/chrono-mongo-datastore.ts"],"sourcesContent":["import { TaskStatus } from '@neofinancial/chrono';\nimport type { Collection } from 'mongodb';\n\nexport const DEFAULT_EXPIRY_SECONDS = 60 * 60 * 24 * 30; // 30 days\n\nexport const IndexNames = {\n COMPLETED_DOCUMENT_TTL_INDEX: 'chrono-completed-document-ttl-index',\n CLAIM_DOCUMENT_INDEX: 'chrono-claim-document-index',\n IDEMPOTENCY_KEY_INDEX: 'chrono-idempotency-key-index',\n};\n\nexport type IndexDefinitionOptions = {\n expireAfterSeconds?: number;\n};\n\nexport async function ensureIndexes(collection: Collection, options: IndexDefinitionOptions): Promise<void> {\n await collection.createIndex(\n { completedAt: -1 },\n {\n partialFilterExpression: {\n completedAt: { $exists: true },\n status: { $eq: TaskStatus.COMPLETED },\n },\n expireAfterSeconds: options.expireAfterSeconds || DEFAULT_EXPIRY_SECONDS,\n name: IndexNames.COMPLETED_DOCUMENT_TTL_INDEX,\n },\n );\n\n await collection.createIndex(\n { kind: 1, status: 1, scheduledAt: 1, priority: -1, claimedAt: 1 },\n { name: IndexNames.CLAIM_DOCUMENT_INDEX },\n );\n\n await collection.createIndex(\n { idempotencyKey: 1 },\n { name: IndexNames.IDEMPOTENCY_KEY_INDEX, unique: true, sparse: true },\n );\n}\n","import {\n type ClaimTaskInput,\n type Datastore,\n type DeleteInput,\n type DeleteOptions,\n type ScheduleInput,\n type Task,\n type TaskMappingBase,\n TaskStatus,\n} from '@neofinancial/chrono';\nimport {\n type ClientSession,\n type Collection,\n type Db,\n ObjectId,\n type OptionalId,\n type UpdateFilter,\n type WithId,\n} from 'mongodb';\nimport { ensureIndexes, IndexNames } from './mongo-indexes';\n\nconst DEFAULT_COLLECTION_NAME = 'chrono-tasks';\n\nexport type ChronoMongoDatastoreConfig = {\n /**\n * The TTL (in seconds) for completed documents.\n *\n * @default 60 * 60 * 24 * 30 // 30 days\n * @type {number}\n */\n completedDocumentTTLSeconds?: number;\n\n /**\n * The name of the collection to use for the datastore.\n *\n * @type {string}\n */\n collectionName: string;\n};\n\nexport type MongoDatastoreOptions = {\n session?: ClientSession;\n};\n\nexport type TaskDocument<TaskKind, TaskData> = WithId<Omit<Task<TaskKind, TaskData>, 'id'>>;\n\nexport class ChronoMongoDatastore<TaskMapping extends TaskMappingBase>\n implements Datastore<TaskMapping, MongoDatastoreOptions>\n{\n private config: ChronoMongoDatastoreConfig;\n private database: Db | undefined;\n private databaseResolvers: Array<(database: Db) => void> = [];\n\n constructor(config?: Partial<ChronoMongoDatastoreConfig>) {\n this.config = {\n completedDocumentTTLSeconds: config?.completedDocumentTTLSeconds,\n collectionName: config?.collectionName || DEFAULT_COLLECTION_NAME,\n };\n }\n\n /**\n * Sets the database connection for the datastore. Ensures that the indexes are created and resolves any pending promises waiting for the database.\n *\n * @param database - The database to set.\n */\n async initialize(database: Db) {\n if (this.database) {\n throw new Error('Database connection already set');\n }\n\n await ensureIndexes(database.collection(this.config.collectionName), {\n expireAfterSeconds: this.config.completedDocumentTTLSeconds,\n });\n\n this.database = database;\n\n const resolvers = this.databaseResolvers.splice(0);\n for (const resolve of resolvers) {\n resolve(database);\n }\n }\n\n /**\n * Asyncronously gets the database connection for the datastore. If the database is not set, it will return a promise that resolves when the database is set.\n *\n * @returns The database connection.\n */\n public async getDatabase(): Promise<Db> {\n if (this.database) {\n return this.database;\n }\n\n return new Promise<Db>((resolve) => {\n this.databaseResolvers.push(resolve);\n });\n }\n\n async schedule<TaskKind extends keyof TaskMapping>(\n input: ScheduleInput<TaskKind, TaskMapping[TaskKind], MongoDatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const createInput: OptionalId<TaskDocument<TaskKind, TaskMapping[TaskKind]>> = {\n kind: input.kind,\n status: TaskStatus.PENDING,\n data: input.data,\n priority: input.priority,\n idempotencyKey: input.idempotencyKey,\n originalScheduleDate: input.when,\n scheduledAt: input.when,\n retryCount: 0,\n };\n\n try {\n const database = await this.getDatabase();\n const results = await database.collection(this.config.collectionName).insertOne(createInput, {\n ...(input?.datastoreOptions?.session ? { session: input.datastoreOptions.session } : undefined),\n ignoreUndefined: true,\n });\n\n if (results.acknowledged) {\n return this.toObject({ _id: results.insertedId, ...createInput });\n }\n } catch (error) {\n if (\n input.idempotencyKey &&\n error instanceof Error &&\n 'code' in error &&\n (error.code === 11000 || error.code === 11001)\n ) {\n const collection = await this.collection<TaskKind>();\n const existingTask = await collection.findOne(\n {\n idempotencyKey: input.idempotencyKey,\n },\n {\n hint: IndexNames.IDEMPOTENCY_KEY_INDEX,\n ...(input.datastoreOptions?.session ? { session: input.datastoreOptions.session } : undefined),\n },\n );\n\n if (existingTask) {\n return this.toObject(existingTask);\n }\n\n throw new Error(\n `Failed to find existing task with idempotency key ${input.idempotencyKey} despite unique index error`,\n );\n }\n throw error;\n }\n\n throw new Error(`Failed to insert ${String(input.kind)} document`);\n }\n\n async delete<TaskKind extends Extract<keyof TaskMapping, string>>(\n key: DeleteInput<TaskKind>,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined> {\n const filter =\n typeof key === 'string' ? { _id: new ObjectId(key) } : { kind: key.kind, idempotencyKey: key.idempotencyKey };\n const collection = await this.collection<TaskKind>();\n const task = await collection.findOneAndDelete({\n ...filter,\n ...(options?.force ? {} : { status: TaskStatus.PENDING }),\n });\n\n if (!task) {\n if (options?.force) {\n return;\n }\n\n const description =\n typeof key === 'string'\n ? `with id ${key}`\n : `with kind ${String(key.kind)} and idempotencyKey ${key.idempotencyKey}`;\n\n throw new Error(`Task ${description} can not be deleted as it may not exist or it's not in PENDING status.`);\n }\n\n return this.toObject(task);\n }\n\n async claim<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: ClaimTaskInput<TaskKind>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined> {\n const now = new Date();\n const collection = await this.collection<TaskKind>();\n const task = await collection.findOneAndUpdate(\n {\n kind: input.kind,\n scheduledAt: { $lte: now },\n $or: [\n { status: TaskStatus.PENDING },\n {\n status: TaskStatus.CLAIMED,\n claimedAt: {\n $lte: new Date(now.getTime() - input.claimStaleTimeoutMs),\n },\n },\n ],\n },\n { $set: { status: TaskStatus.CLAIMED, claimedAt: now } },\n {\n sort: { priority: -1, scheduledAt: 1 },\n // hint: IndexNames.CLAIM_DOCUMENT_INDEX as unknown as Document,\n returnDocument: 'after',\n },\n );\n\n return task ? this.toObject(task) : undefined;\n }\n\n async retry<TaskKind extends keyof TaskMapping>(\n taskId: string,\n retryAt: Date,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const taskDocument = await this.updateOrThrow<TaskKind>(taskId, {\n $set: {\n status: TaskStatus.PENDING,\n scheduledAt: retryAt,\n },\n $inc: {\n retryCount: 1,\n },\n });\n\n return this.toObject(taskDocument);\n }\n\n async complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const now = new Date();\n\n const task = await this.updateOrThrow<TaskKind>(taskId, {\n $set: {\n status: TaskStatus.COMPLETED,\n completedAt: now,\n lastExecutedAt: now,\n },\n });\n\n return this.toObject(task);\n }\n\n async fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const now = new Date();\n\n const task = await this.updateOrThrow<TaskKind>(taskId, {\n $set: {\n status: TaskStatus.FAILED,\n lastExecutedAt: now,\n },\n });\n\n return this.toObject(task);\n }\n\n private async updateOrThrow<TaskKind extends keyof TaskMapping>(\n taskId: string,\n update: UpdateFilter<TaskDocument<TaskKind, TaskMapping[TaskKind]>>,\n ): Promise<TaskDocument<TaskKind, TaskMapping[TaskKind]>> {\n const collection = await this.collection<TaskKind>();\n const document = await collection.findOneAndUpdate({ _id: new ObjectId(taskId) }, update, {\n returnDocument: 'after',\n });\n\n if (!document) {\n throw new Error(`Task with ID ${taskId} not found`);\n }\n return document;\n }\n\n private async collection<TaskKind extends keyof TaskMapping>(): Promise<\n Collection<TaskDocument<TaskKind, TaskMapping[TaskKind]>>\n > {\n const database = await this.getDatabase();\n return database.collection<TaskDocument<TaskKind, TaskMapping[TaskKind]>>(this.config.collectionName);\n }\n\n private toObject<TaskKind extends keyof TaskMapping>(\n document: TaskDocument<TaskKind, TaskMapping[TaskKind]>,\n ): Task<TaskKind, TaskMapping[TaskKind]> {\n return {\n id: document._id.toHexString(),\n data: document.data,\n kind: document.kind,\n status: document.status,\n priority: document.priority ?? undefined,\n idempotencyKey: document.idempotencyKey ?? undefined,\n originalScheduleDate: document.originalScheduleDate,\n scheduledAt: document.scheduledAt,\n claimedAt: document.claimedAt ?? undefined,\n completedAt: document.completedAt ?? undefined,\n retryCount: document.retryCount,\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,194 @@
1
+ import { TaskStatus } from "@neofinancial/chrono";
2
+ import { ObjectId } from "mongodb";
3
+
4
+ //#region src/mongo-indexes.ts
5
+ const DEFAULT_EXPIRY_SECONDS = 3600 * 24 * 30;
6
+ const IndexNames = {
7
+ COMPLETED_DOCUMENT_TTL_INDEX: "chrono-completed-document-ttl-index",
8
+ CLAIM_DOCUMENT_INDEX: "chrono-claim-document-index",
9
+ IDEMPOTENCY_KEY_INDEX: "chrono-idempotency-key-index"
10
+ };
11
+ async function ensureIndexes(collection, options) {
12
+ await collection.createIndex({ completedAt: -1 }, {
13
+ partialFilterExpression: {
14
+ completedAt: { $exists: true },
15
+ status: { $eq: TaskStatus.COMPLETED }
16
+ },
17
+ expireAfterSeconds: options.expireAfterSeconds || DEFAULT_EXPIRY_SECONDS,
18
+ name: IndexNames.COMPLETED_DOCUMENT_TTL_INDEX
19
+ });
20
+ await collection.createIndex({
21
+ kind: 1,
22
+ status: 1,
23
+ scheduledAt: 1,
24
+ priority: -1,
25
+ claimedAt: 1
26
+ }, { name: IndexNames.CLAIM_DOCUMENT_INDEX });
27
+ await collection.createIndex({ idempotencyKey: 1 }, {
28
+ name: IndexNames.IDEMPOTENCY_KEY_INDEX,
29
+ unique: true,
30
+ sparse: true
31
+ });
32
+ }
33
+
34
+ //#endregion
35
+ //#region src/chrono-mongo-datastore.ts
36
+ const DEFAULT_COLLECTION_NAME = "chrono-tasks";
37
+ var ChronoMongoDatastore = class {
38
+ config;
39
+ database;
40
+ databaseResolvers = [];
41
+ constructor(config) {
42
+ this.config = {
43
+ completedDocumentTTLSeconds: config?.completedDocumentTTLSeconds,
44
+ collectionName: config?.collectionName || DEFAULT_COLLECTION_NAME
45
+ };
46
+ }
47
+ /**
48
+ * Sets the database connection for the datastore. Ensures that the indexes are created and resolves any pending promises waiting for the database.
49
+ *
50
+ * @param database - The database to set.
51
+ */
52
+ async initialize(database) {
53
+ if (this.database) throw new Error("Database connection already set");
54
+ await ensureIndexes(database.collection(this.config.collectionName), { expireAfterSeconds: this.config.completedDocumentTTLSeconds });
55
+ this.database = database;
56
+ const resolvers = this.databaseResolvers.splice(0);
57
+ for (const resolve of resolvers) resolve(database);
58
+ }
59
+ /**
60
+ * Asyncronously gets the database connection for the datastore. If the database is not set, it will return a promise that resolves when the database is set.
61
+ *
62
+ * @returns The database connection.
63
+ */
64
+ async getDatabase() {
65
+ if (this.database) return this.database;
66
+ return new Promise((resolve) => {
67
+ this.databaseResolvers.push(resolve);
68
+ });
69
+ }
70
+ async schedule(input) {
71
+ const createInput = {
72
+ kind: input.kind,
73
+ status: TaskStatus.PENDING,
74
+ data: input.data,
75
+ priority: input.priority,
76
+ idempotencyKey: input.idempotencyKey,
77
+ originalScheduleDate: input.when,
78
+ scheduledAt: input.when,
79
+ retryCount: 0
80
+ };
81
+ try {
82
+ const results = await (await this.getDatabase()).collection(this.config.collectionName).insertOne(createInput, {
83
+ ...input?.datastoreOptions?.session ? { session: input.datastoreOptions.session } : void 0,
84
+ ignoreUndefined: true
85
+ });
86
+ if (results.acknowledged) return this.toObject({
87
+ _id: results.insertedId,
88
+ ...createInput
89
+ });
90
+ } catch (error) {
91
+ if (input.idempotencyKey && error instanceof Error && "code" in error && (error.code === 11e3 || error.code === 11001)) {
92
+ const existingTask = await (await this.collection()).findOne({ idempotencyKey: input.idempotencyKey }, {
93
+ hint: IndexNames.IDEMPOTENCY_KEY_INDEX,
94
+ ...input.datastoreOptions?.session ? { session: input.datastoreOptions.session } : void 0
95
+ });
96
+ if (existingTask) return this.toObject(existingTask);
97
+ throw new Error(`Failed to find existing task with idempotency key ${input.idempotencyKey} despite unique index error`);
98
+ }
99
+ throw error;
100
+ }
101
+ throw new Error(`Failed to insert ${String(input.kind)} document`);
102
+ }
103
+ async delete(key, options) {
104
+ const filter = typeof key === "string" ? { _id: new ObjectId(key) } : {
105
+ kind: key.kind,
106
+ idempotencyKey: key.idempotencyKey
107
+ };
108
+ const task = await (await this.collection()).findOneAndDelete({
109
+ ...filter,
110
+ ...options?.force ? {} : { status: TaskStatus.PENDING }
111
+ });
112
+ if (!task) {
113
+ if (options?.force) return;
114
+ const description = typeof key === "string" ? `with id ${key}` : `with kind ${String(key.kind)} and idempotencyKey ${key.idempotencyKey}`;
115
+ throw new Error(`Task ${description} can not be deleted as it may not exist or it's not in PENDING status.`);
116
+ }
117
+ return this.toObject(task);
118
+ }
119
+ async claim(input) {
120
+ const now = /* @__PURE__ */ new Date();
121
+ const task = await (await this.collection()).findOneAndUpdate({
122
+ kind: input.kind,
123
+ scheduledAt: { $lte: now },
124
+ $or: [{ status: TaskStatus.PENDING }, {
125
+ status: TaskStatus.CLAIMED,
126
+ claimedAt: { $lte: new Date(now.getTime() - input.claimStaleTimeoutMs) }
127
+ }]
128
+ }, { $set: {
129
+ status: TaskStatus.CLAIMED,
130
+ claimedAt: now
131
+ } }, {
132
+ sort: {
133
+ priority: -1,
134
+ scheduledAt: 1
135
+ },
136
+ returnDocument: "after"
137
+ });
138
+ return task ? this.toObject(task) : void 0;
139
+ }
140
+ async retry(taskId, retryAt) {
141
+ const taskDocument = await this.updateOrThrow(taskId, {
142
+ $set: {
143
+ status: TaskStatus.PENDING,
144
+ scheduledAt: retryAt
145
+ },
146
+ $inc: { retryCount: 1 }
147
+ });
148
+ return this.toObject(taskDocument);
149
+ }
150
+ async complete(taskId) {
151
+ const now = /* @__PURE__ */ new Date();
152
+ const task = await this.updateOrThrow(taskId, { $set: {
153
+ status: TaskStatus.COMPLETED,
154
+ completedAt: now,
155
+ lastExecutedAt: now
156
+ } });
157
+ return this.toObject(task);
158
+ }
159
+ async fail(taskId) {
160
+ const now = /* @__PURE__ */ new Date();
161
+ const task = await this.updateOrThrow(taskId, { $set: {
162
+ status: TaskStatus.FAILED,
163
+ lastExecutedAt: now
164
+ } });
165
+ return this.toObject(task);
166
+ }
167
+ async updateOrThrow(taskId, update) {
168
+ const document = await (await this.collection()).findOneAndUpdate({ _id: new ObjectId(taskId) }, update, { returnDocument: "after" });
169
+ if (!document) throw new Error(`Task with ID ${taskId} not found`);
170
+ return document;
171
+ }
172
+ async collection() {
173
+ return (await this.getDatabase()).collection(this.config.collectionName);
174
+ }
175
+ toObject(document) {
176
+ return {
177
+ id: document._id.toHexString(),
178
+ data: document.data,
179
+ kind: document.kind,
180
+ status: document.status,
181
+ priority: document.priority ?? void 0,
182
+ idempotencyKey: document.idempotencyKey ?? void 0,
183
+ originalScheduleDate: document.originalScheduleDate,
184
+ scheduledAt: document.scheduledAt,
185
+ claimedAt: document.claimedAt ?? void 0,
186
+ completedAt: document.completedAt ?? void 0,
187
+ retryCount: document.retryCount
188
+ };
189
+ }
190
+ };
191
+
192
+ //#endregion
193
+ export { ChronoMongoDatastore };
194
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["createInput: OptionalId<TaskDocument<TaskKind, TaskMapping[TaskKind]>>"],"sources":["../src/mongo-indexes.ts","../src/chrono-mongo-datastore.ts"],"sourcesContent":["import { TaskStatus } from '@neofinancial/chrono';\nimport type { Collection } from 'mongodb';\n\nexport const DEFAULT_EXPIRY_SECONDS = 60 * 60 * 24 * 30; // 30 days\n\nexport const IndexNames = {\n COMPLETED_DOCUMENT_TTL_INDEX: 'chrono-completed-document-ttl-index',\n CLAIM_DOCUMENT_INDEX: 'chrono-claim-document-index',\n IDEMPOTENCY_KEY_INDEX: 'chrono-idempotency-key-index',\n};\n\nexport type IndexDefinitionOptions = {\n expireAfterSeconds?: number;\n};\n\nexport async function ensureIndexes(collection: Collection, options: IndexDefinitionOptions): Promise<void> {\n await collection.createIndex(\n { completedAt: -1 },\n {\n partialFilterExpression: {\n completedAt: { $exists: true },\n status: { $eq: TaskStatus.COMPLETED },\n },\n expireAfterSeconds: options.expireAfterSeconds || DEFAULT_EXPIRY_SECONDS,\n name: IndexNames.COMPLETED_DOCUMENT_TTL_INDEX,\n },\n );\n\n await collection.createIndex(\n { kind: 1, status: 1, scheduledAt: 1, priority: -1, claimedAt: 1 },\n { name: IndexNames.CLAIM_DOCUMENT_INDEX },\n );\n\n await collection.createIndex(\n { idempotencyKey: 1 },\n { name: IndexNames.IDEMPOTENCY_KEY_INDEX, unique: true, sparse: true },\n );\n}\n","import {\n type ClaimTaskInput,\n type Datastore,\n type DeleteInput,\n type DeleteOptions,\n type ScheduleInput,\n type Task,\n type TaskMappingBase,\n TaskStatus,\n} from '@neofinancial/chrono';\nimport {\n type ClientSession,\n type Collection,\n type Db,\n ObjectId,\n type OptionalId,\n type UpdateFilter,\n type WithId,\n} from 'mongodb';\nimport { ensureIndexes, IndexNames } from './mongo-indexes';\n\nconst DEFAULT_COLLECTION_NAME = 'chrono-tasks';\n\nexport type ChronoMongoDatastoreConfig = {\n /**\n * The TTL (in seconds) for completed documents.\n *\n * @default 60 * 60 * 24 * 30 // 30 days\n * @type {number}\n */\n completedDocumentTTLSeconds?: number;\n\n /**\n * The name of the collection to use for the datastore.\n *\n * @type {string}\n */\n collectionName: string;\n};\n\nexport type MongoDatastoreOptions = {\n session?: ClientSession;\n};\n\nexport type TaskDocument<TaskKind, TaskData> = WithId<Omit<Task<TaskKind, TaskData>, 'id'>>;\n\nexport class ChronoMongoDatastore<TaskMapping extends TaskMappingBase>\n implements Datastore<TaskMapping, MongoDatastoreOptions>\n{\n private config: ChronoMongoDatastoreConfig;\n private database: Db | undefined;\n private databaseResolvers: Array<(database: Db) => void> = [];\n\n constructor(config?: Partial<ChronoMongoDatastoreConfig>) {\n this.config = {\n completedDocumentTTLSeconds: config?.completedDocumentTTLSeconds,\n collectionName: config?.collectionName || DEFAULT_COLLECTION_NAME,\n };\n }\n\n /**\n * Sets the database connection for the datastore. Ensures that the indexes are created and resolves any pending promises waiting for the database.\n *\n * @param database - The database to set.\n */\n async initialize(database: Db) {\n if (this.database) {\n throw new Error('Database connection already set');\n }\n\n await ensureIndexes(database.collection(this.config.collectionName), {\n expireAfterSeconds: this.config.completedDocumentTTLSeconds,\n });\n\n this.database = database;\n\n const resolvers = this.databaseResolvers.splice(0);\n for (const resolve of resolvers) {\n resolve(database);\n }\n }\n\n /**\n * Asyncronously gets the database connection for the datastore. If the database is not set, it will return a promise that resolves when the database is set.\n *\n * @returns The database connection.\n */\n public async getDatabase(): Promise<Db> {\n if (this.database) {\n return this.database;\n }\n\n return new Promise<Db>((resolve) => {\n this.databaseResolvers.push(resolve);\n });\n }\n\n async schedule<TaskKind extends keyof TaskMapping>(\n input: ScheduleInput<TaskKind, TaskMapping[TaskKind], MongoDatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const createInput: OptionalId<TaskDocument<TaskKind, TaskMapping[TaskKind]>> = {\n kind: input.kind,\n status: TaskStatus.PENDING,\n data: input.data,\n priority: input.priority,\n idempotencyKey: input.idempotencyKey,\n originalScheduleDate: input.when,\n scheduledAt: input.when,\n retryCount: 0,\n };\n\n try {\n const database = await this.getDatabase();\n const results = await database.collection(this.config.collectionName).insertOne(createInput, {\n ...(input?.datastoreOptions?.session ? { session: input.datastoreOptions.session } : undefined),\n ignoreUndefined: true,\n });\n\n if (results.acknowledged) {\n return this.toObject({ _id: results.insertedId, ...createInput });\n }\n } catch (error) {\n if (\n input.idempotencyKey &&\n error instanceof Error &&\n 'code' in error &&\n (error.code === 11000 || error.code === 11001)\n ) {\n const collection = await this.collection<TaskKind>();\n const existingTask = await collection.findOne(\n {\n idempotencyKey: input.idempotencyKey,\n },\n {\n hint: IndexNames.IDEMPOTENCY_KEY_INDEX,\n ...(input.datastoreOptions?.session ? { session: input.datastoreOptions.session } : undefined),\n },\n );\n\n if (existingTask) {\n return this.toObject(existingTask);\n }\n\n throw new Error(\n `Failed to find existing task with idempotency key ${input.idempotencyKey} despite unique index error`,\n );\n }\n throw error;\n }\n\n throw new Error(`Failed to insert ${String(input.kind)} document`);\n }\n\n async delete<TaskKind extends Extract<keyof TaskMapping, string>>(\n key: DeleteInput<TaskKind>,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined> {\n const filter =\n typeof key === 'string' ? { _id: new ObjectId(key) } : { kind: key.kind, idempotencyKey: key.idempotencyKey };\n const collection = await this.collection<TaskKind>();\n const task = await collection.findOneAndDelete({\n ...filter,\n ...(options?.force ? {} : { status: TaskStatus.PENDING }),\n });\n\n if (!task) {\n if (options?.force) {\n return;\n }\n\n const description =\n typeof key === 'string'\n ? `with id ${key}`\n : `with kind ${String(key.kind)} and idempotencyKey ${key.idempotencyKey}`;\n\n throw new Error(`Task ${description} can not be deleted as it may not exist or it's not in PENDING status.`);\n }\n\n return this.toObject(task);\n }\n\n async claim<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: ClaimTaskInput<TaskKind>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined> {\n const now = new Date();\n const collection = await this.collection<TaskKind>();\n const task = await collection.findOneAndUpdate(\n {\n kind: input.kind,\n scheduledAt: { $lte: now },\n $or: [\n { status: TaskStatus.PENDING },\n {\n status: TaskStatus.CLAIMED,\n claimedAt: {\n $lte: new Date(now.getTime() - input.claimStaleTimeoutMs),\n },\n },\n ],\n },\n { $set: { status: TaskStatus.CLAIMED, claimedAt: now } },\n {\n sort: { priority: -1, scheduledAt: 1 },\n // hint: IndexNames.CLAIM_DOCUMENT_INDEX as unknown as Document,\n returnDocument: 'after',\n },\n );\n\n return task ? this.toObject(task) : undefined;\n }\n\n async retry<TaskKind extends keyof TaskMapping>(\n taskId: string,\n retryAt: Date,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const taskDocument = await this.updateOrThrow<TaskKind>(taskId, {\n $set: {\n status: TaskStatus.PENDING,\n scheduledAt: retryAt,\n },\n $inc: {\n retryCount: 1,\n },\n });\n\n return this.toObject(taskDocument);\n }\n\n async complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const now = new Date();\n\n const task = await this.updateOrThrow<TaskKind>(taskId, {\n $set: {\n status: TaskStatus.COMPLETED,\n completedAt: now,\n lastExecutedAt: now,\n },\n });\n\n return this.toObject(task);\n }\n\n async fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const now = new Date();\n\n const task = await this.updateOrThrow<TaskKind>(taskId, {\n $set: {\n status: TaskStatus.FAILED,\n lastExecutedAt: now,\n },\n });\n\n return this.toObject(task);\n }\n\n private async updateOrThrow<TaskKind extends keyof TaskMapping>(\n taskId: string,\n update: UpdateFilter<TaskDocument<TaskKind, TaskMapping[TaskKind]>>,\n ): Promise<TaskDocument<TaskKind, TaskMapping[TaskKind]>> {\n const collection = await this.collection<TaskKind>();\n const document = await collection.findOneAndUpdate({ _id: new ObjectId(taskId) }, update, {\n returnDocument: 'after',\n });\n\n if (!document) {\n throw new Error(`Task with ID ${taskId} not found`);\n }\n return document;\n }\n\n private async collection<TaskKind extends keyof TaskMapping>(): Promise<\n Collection<TaskDocument<TaskKind, TaskMapping[TaskKind]>>\n > {\n const database = await this.getDatabase();\n return database.collection<TaskDocument<TaskKind, TaskMapping[TaskKind]>>(this.config.collectionName);\n }\n\n private toObject<TaskKind extends keyof TaskMapping>(\n document: TaskDocument<TaskKind, TaskMapping[TaskKind]>,\n ): Task<TaskKind, TaskMapping[TaskKind]> {\n return {\n id: document._id.toHexString(),\n data: document.data,\n kind: document.kind,\n status: document.status,\n priority: document.priority ?? undefined,\n idempotencyKey: document.idempotencyKey ?? undefined,\n originalScheduleDate: document.originalScheduleDate,\n scheduledAt: document.scheduledAt,\n claimedAt: document.claimedAt ?? undefined,\n completedAt: document.completedAt ?? undefined,\n retryCount: document.retryCount,\n };\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neofinancial/chrono-mongo-datastore",
3
- "version": "0.4.1-next.1",
3
+ "version": "0.5.1",
4
4
  "description": "MongoDB datastore implementation for Chrono task scheduling system",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -11,8 +11,21 @@
11
11
  "type": "git",
12
12
  "url": "https://github.com/neofinancial/chrono.git"
13
13
  },
14
- "main": "build/index.js",
15
- "types": "build/index.d.ts",
14
+ "main": "./build/index.js",
15
+ "module": "./build/index.mjs",
16
+ "types": "./build/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "import": {
20
+ "types": "./build/index.d.mts",
21
+ "default": "./build/index.mjs"
22
+ },
23
+ "require": {
24
+ "types": "./build/index.d.ts",
25
+ "default": "./build/index.js"
26
+ }
27
+ }
28
+ },
16
29
  "keywords": [],
17
30
  "author": "Neo Financial Engineering <engineering@neofinancial.com>",
18
31
  "license": "MIT",
@@ -23,15 +36,15 @@
23
36
  "devDependencies": {
24
37
  "mongodb": "^6",
25
38
  "mongodb-memory-server": "^10.1.4",
26
- "@neofinancial/chrono": "0.4.1-next.1"
39
+ "@neofinancial/chrono": "0.5.1"
27
40
  },
28
41
  "peerDependencies": {
29
42
  "mongodb": "^6",
30
- "@neofinancial/chrono": "0.4.1-next.1"
43
+ "@neofinancial/chrono": "0.5.1"
31
44
  },
32
45
  "scripts": {
33
46
  "clean": "rimraf ./build",
34
- "build": "tsc",
47
+ "build": "tsdown",
35
48
  "typecheck": "tsc -p ./tsconfig.json --noEmit",
36
49
  "test": "NODE_ENV=test TZ=UTC vitest run"
37
50
  }
@@ -1,48 +0,0 @@
1
- import { type ClaimTaskInput, type Datastore, type DeleteInput, type DeleteOptions, type ScheduleInput, type Task, type TaskMappingBase } from '@neofinancial/chrono';
2
- import { type ClientSession, type Db, type WithId } from 'mongodb';
3
- export type ChronoMongoDatastoreConfig = {
4
- /**
5
- * The TTL (in seconds) for completed documents.
6
- *
7
- * @default 60 * 60 * 24 * 30 // 30 days
8
- * @type {number}
9
- */
10
- completedDocumentTTLSeconds?: number;
11
- /**
12
- * The name of the collection to use for the datastore.
13
- *
14
- * @type {string}
15
- */
16
- collectionName: string;
17
- };
18
- export type MongoDatastoreOptions = {
19
- session?: ClientSession;
20
- };
21
- export type TaskDocument<TaskKind, TaskData> = WithId<Omit<Task<TaskKind, TaskData>, 'id'>>;
22
- export declare class ChronoMongoDatastore<TaskMapping extends TaskMappingBase> implements Datastore<TaskMapping, MongoDatastoreOptions> {
23
- private config;
24
- private database;
25
- private databaseResolvers;
26
- constructor(config?: Partial<ChronoMongoDatastoreConfig>);
27
- /**
28
- * Sets the database connection for the datastore. Ensures that the indexes are created and resolves any pending promises waiting for the database.
29
- *
30
- * @param database - The database to set.
31
- */
32
- initialize(database: Db): Promise<void>;
33
- /**
34
- * Asyncronously gets the database connection for the datastore. If the database is not set, it will return a promise that resolves when the database is set.
35
- *
36
- * @returns The database connection.
37
- */
38
- getDatabase(): Promise<Db>;
39
- schedule<TaskKind extends keyof TaskMapping>(input: ScheduleInput<TaskKind, TaskMapping[TaskKind], MongoDatastoreOptions>): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
40
- delete<TaskKind extends Extract<keyof TaskMapping, string>>(key: DeleteInput<TaskKind>, options?: DeleteOptions): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
41
- claim<TaskKind extends Extract<keyof TaskMapping, string>>(input: ClaimTaskInput<TaskKind>): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
42
- retry<TaskKind extends keyof TaskMapping>(taskId: string, retryAt: Date): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
43
- complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
44
- fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
45
- private updateOrThrow;
46
- private collection;
47
- private toObject;
48
- }
@@ -1,195 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ChronoMongoDatastore = void 0;
4
- const chrono_1 = require("@neofinancial/chrono");
5
- const mongodb_1 = require("mongodb");
6
- const mongo_indexes_1 = require("./mongo-indexes");
7
- const DEFAULT_COLLECTION_NAME = 'chrono-tasks';
8
- class ChronoMongoDatastore {
9
- config;
10
- database;
11
- databaseResolvers = [];
12
- constructor(config) {
13
- this.config = {
14
- completedDocumentTTLSeconds: config?.completedDocumentTTLSeconds,
15
- collectionName: config?.collectionName || DEFAULT_COLLECTION_NAME,
16
- };
17
- }
18
- /**
19
- * Sets the database connection for the datastore. Ensures that the indexes are created and resolves any pending promises waiting for the database.
20
- *
21
- * @param database - The database to set.
22
- */
23
- async initialize(database) {
24
- if (this.database) {
25
- throw new Error('Database connection already set');
26
- }
27
- await (0, mongo_indexes_1.ensureIndexes)(database.collection(this.config.collectionName), {
28
- expireAfterSeconds: this.config.completedDocumentTTLSeconds,
29
- });
30
- this.database = database;
31
- const resolvers = this.databaseResolvers.splice(0);
32
- for (const resolve of resolvers) {
33
- resolve(database);
34
- }
35
- }
36
- /**
37
- * Asyncronously gets the database connection for the datastore. If the database is not set, it will return a promise that resolves when the database is set.
38
- *
39
- * @returns The database connection.
40
- */
41
- async getDatabase() {
42
- if (this.database) {
43
- return this.database;
44
- }
45
- return new Promise((resolve) => {
46
- this.databaseResolvers.push(resolve);
47
- });
48
- }
49
- async schedule(input) {
50
- const createInput = {
51
- kind: input.kind,
52
- status: chrono_1.TaskStatus.PENDING,
53
- data: input.data,
54
- priority: input.priority,
55
- idempotencyKey: input.idempotencyKey,
56
- originalScheduleDate: input.when,
57
- scheduledAt: input.when,
58
- retryCount: 0,
59
- };
60
- try {
61
- const database = await this.getDatabase();
62
- const results = await database.collection(this.config.collectionName).insertOne(createInput, {
63
- ...(input?.datastoreOptions?.session ? { session: input.datastoreOptions.session } : undefined),
64
- ignoreUndefined: true,
65
- });
66
- if (results.acknowledged) {
67
- return this.toObject({ _id: results.insertedId, ...createInput });
68
- }
69
- }
70
- catch (error) {
71
- if (input.idempotencyKey &&
72
- error instanceof Error &&
73
- 'code' in error &&
74
- (error.code === 11000 || error.code === 11001)) {
75
- const collection = await this.collection();
76
- const existingTask = await collection.findOne({
77
- idempotencyKey: input.idempotencyKey,
78
- }, {
79
- hint: mongo_indexes_1.IndexNames.IDEMPOTENCY_KEY_INDEX,
80
- ...(input.datastoreOptions?.session ? { session: input.datastoreOptions.session } : undefined),
81
- });
82
- if (existingTask) {
83
- return this.toObject(existingTask);
84
- }
85
- throw new Error(`Failed to find existing task with idempotency key ${input.idempotencyKey} despite unique index error`);
86
- }
87
- throw error;
88
- }
89
- throw new Error(`Failed to insert ${String(input.kind)} document`);
90
- }
91
- async delete(key, options) {
92
- const filter = typeof key === 'string' ? { _id: new mongodb_1.ObjectId(key) } : { kind: key.kind, idempotencyKey: key.idempotencyKey };
93
- const collection = await this.collection();
94
- const task = await collection.findOneAndDelete({
95
- ...filter,
96
- ...(options?.force ? {} : { status: chrono_1.TaskStatus.PENDING }),
97
- });
98
- if (!task) {
99
- if (options?.force) {
100
- return;
101
- }
102
- const description = typeof key === 'string'
103
- ? `with id ${key}`
104
- : `with kind ${String(key.kind)} and idempotencyKey ${key.idempotencyKey}`;
105
- throw new Error(`Task ${description} can not be deleted as it may not exist or it's not in PENDING status.`);
106
- }
107
- return this.toObject(task);
108
- }
109
- async claim(input) {
110
- const now = new Date();
111
- const collection = await this.collection();
112
- const task = await collection.findOneAndUpdate({
113
- kind: input.kind,
114
- scheduledAt: { $lte: now },
115
- $or: [
116
- { status: chrono_1.TaskStatus.PENDING },
117
- {
118
- status: chrono_1.TaskStatus.CLAIMED,
119
- claimedAt: {
120
- $lte: new Date(now.getTime() - input.claimStaleTimeoutMs),
121
- },
122
- },
123
- ],
124
- }, { $set: { status: chrono_1.TaskStatus.CLAIMED, claimedAt: now } }, {
125
- sort: { priority: -1, scheduledAt: 1 },
126
- // hint: IndexNames.CLAIM_DOCUMENT_INDEX as unknown as Document,
127
- returnDocument: 'after',
128
- });
129
- return task ? this.toObject(task) : undefined;
130
- }
131
- async retry(taskId, retryAt) {
132
- const taskDocument = await this.updateOrThrow(taskId, {
133
- $set: {
134
- status: chrono_1.TaskStatus.PENDING,
135
- scheduledAt: retryAt,
136
- },
137
- $inc: {
138
- retryCount: 1,
139
- },
140
- });
141
- return this.toObject(taskDocument);
142
- }
143
- async complete(taskId) {
144
- const now = new Date();
145
- const task = await this.updateOrThrow(taskId, {
146
- $set: {
147
- status: chrono_1.TaskStatus.COMPLETED,
148
- completedAt: now,
149
- lastExecutedAt: now,
150
- },
151
- });
152
- return this.toObject(task);
153
- }
154
- async fail(taskId) {
155
- const now = new Date();
156
- const task = await this.updateOrThrow(taskId, {
157
- $set: {
158
- status: chrono_1.TaskStatus.FAILED,
159
- lastExecutedAt: now,
160
- },
161
- });
162
- return this.toObject(task);
163
- }
164
- async updateOrThrow(taskId, update) {
165
- const collection = await this.collection();
166
- const document = await collection.findOneAndUpdate({ _id: new mongodb_1.ObjectId(taskId) }, update, {
167
- returnDocument: 'after',
168
- });
169
- if (!document) {
170
- throw new Error(`Task with ID ${taskId} not found`);
171
- }
172
- return document;
173
- }
174
- async collection() {
175
- const database = await this.getDatabase();
176
- return database.collection(this.config.collectionName);
177
- }
178
- toObject(document) {
179
- return {
180
- id: document._id.toHexString(),
181
- data: document.data,
182
- kind: document.kind,
183
- status: document.status,
184
- priority: document.priority ?? undefined,
185
- idempotencyKey: document.idempotencyKey ?? undefined,
186
- originalScheduleDate: document.originalScheduleDate,
187
- scheduledAt: document.scheduledAt,
188
- claimedAt: document.claimedAt ?? undefined,
189
- completedAt: document.completedAt ?? undefined,
190
- retryCount: document.retryCount,
191
- };
192
- }
193
- }
194
- exports.ChronoMongoDatastore = ChronoMongoDatastore;
195
- //# sourceMappingURL=chrono-mongo-datastore.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"chrono-mongo-datastore.js","sourceRoot":"","sources":["../src/chrono-mongo-datastore.ts"],"names":[],"mappings":"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"}
package/build/main.d.ts DELETED
@@ -1 +0,0 @@
1
- export {};
package/build/main.js DELETED
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const chrono_core_1 = require("@neofinancial/chrono-core");
4
- const mongo_task_1 = require("./mongo-task");
5
- async function main() {
6
- const task = new mongo_task_1.MongoTask();
7
- const scheduler = new chrono_core_1.Scheduler();
8
- await scheduler.schedule(task);
9
- await scheduler.run();
10
- console.log('Successfully ran MongoTask!');
11
- }
12
- main().catch(console.error);
13
- //# sourceMappingURL=main.js.map
package/build/main.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,2DAAsD;AAEtD,6CAAyC;AAEzC,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,IAAI,sBAAS,EAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,uBAAS,EAAE,CAAC;IAElC,MAAM,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC;IAEtB,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;AAC7C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
@@ -1,11 +0,0 @@
1
- import type { Collection } from 'mongodb';
2
- export declare const DEFAULT_EXPIRY_SECONDS: number;
3
- export declare const IndexNames: {
4
- COMPLETED_DOCUMENT_TTL_INDEX: string;
5
- CLAIM_DOCUMENT_INDEX: string;
6
- IDEMPOTENCY_KEY_INDEX: string;
7
- };
8
- export type IndexDefinitionOptions = {
9
- expireAfterSeconds?: number;
10
- };
11
- export declare function ensureIndexes(collection: Collection, options: IndexDefinitionOptions): Promise<void>;
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.IndexNames = exports.DEFAULT_EXPIRY_SECONDS = void 0;
4
- exports.ensureIndexes = ensureIndexes;
5
- const chrono_1 = require("@neofinancial/chrono");
6
- exports.DEFAULT_EXPIRY_SECONDS = 60 * 60 * 24 * 30; // 30 days
7
- exports.IndexNames = {
8
- COMPLETED_DOCUMENT_TTL_INDEX: 'chrono-completed-document-ttl-index',
9
- CLAIM_DOCUMENT_INDEX: 'chrono-claim-document-index',
10
- IDEMPOTENCY_KEY_INDEX: 'chrono-idempotency-key-index',
11
- };
12
- async function ensureIndexes(collection, options) {
13
- await collection.createIndex({ completedAt: -1 }, {
14
- partialFilterExpression: {
15
- completedAt: { $exists: true },
16
- status: { $eq: chrono_1.TaskStatus.COMPLETED },
17
- },
18
- expireAfterSeconds: options.expireAfterSeconds || exports.DEFAULT_EXPIRY_SECONDS,
19
- name: exports.IndexNames.COMPLETED_DOCUMENT_TTL_INDEX,
20
- });
21
- await collection.createIndex({ kind: 1, status: 1, scheduledAt: 1, priority: -1, claimedAt: 1 }, { name: exports.IndexNames.CLAIM_DOCUMENT_INDEX });
22
- await collection.createIndex({ idempotencyKey: 1 }, { name: exports.IndexNames.IDEMPOTENCY_KEY_INDEX, unique: true, sparse: true });
23
- }
24
- //# sourceMappingURL=mongo-indexes.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mongo-indexes.js","sourceRoot":"","sources":["../src/mongo-indexes.ts"],"names":[],"mappings":";;;AAeA,sCAsBC;AArCD,iDAAkD;AAGrC,QAAA,sBAAsB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,UAAU;AAEtD,QAAA,UAAU,GAAG;IACxB,4BAA4B,EAAE,qCAAqC;IACnE,oBAAoB,EAAE,6BAA6B;IACnD,qBAAqB,EAAE,8BAA8B;CACtD,CAAC;AAMK,KAAK,UAAU,aAAa,CAAC,UAAsB,EAAE,OAA+B;IACzF,MAAM,UAAU,CAAC,WAAW,CAC1B,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,EACnB;QACE,uBAAuB,EAAE;YACvB,WAAW,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;YAC9B,MAAM,EAAE,EAAE,GAAG,EAAE,mBAAU,CAAC,SAAS,EAAE;SACtC;QACD,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,IAAI,8BAAsB;QACxE,IAAI,EAAE,kBAAU,CAAC,4BAA4B;KAC9C,CACF,CAAC;IAEF,MAAM,UAAU,CAAC,WAAW,CAC1B,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAClE,EAAE,IAAI,EAAE,kBAAU,CAAC,oBAAoB,EAAE,CAC1C,CAAC;IAEF,MAAM,UAAU,CAAC,WAAW,CAC1B,EAAE,cAAc,EAAE,CAAC,EAAE,EACrB,EAAE,IAAI,EAAE,kBAAU,CAAC,qBAAqB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CACvE,CAAC;AACJ,CAAC"}
@@ -1,4 +0,0 @@
1
- import type { Task } from '@neofinancial/chrono-core';
2
- export declare class MongoTask implements Task<boolean> {
3
- run(): Promise<boolean>;
4
- }
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MongoTask = void 0;
4
- class MongoTask {
5
- async run() {
6
- console.log('Running MongoTask');
7
- return true;
8
- }
9
- }
10
- exports.MongoTask = MongoTask;
11
- //# sourceMappingURL=mongo-task.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mongo-task.js","sourceRoot":"","sources":["../src/mongo-task.ts"],"names":[],"mappings":";;;AAEA,MAAa,SAAS;IACb,KAAK,CAAC,GAAG;QACd,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QAEjC,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAND,8BAMC"}