@nwire/mongo 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Gefter / 200apps Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @nwire/mongo
2
+
3
+ > MongoDB-backed `ActorStore` + `ProjectionStore` — the canonical production store.
4
+
5
+ ## What it does
6
+
7
+ Persists actor state and projection rows to MongoDB. Tenant-partitioned. Includes idempotent `ensureIndexes()` (`{actorName, tenant}`, `{actorName, fireAtList}` for the timer scheduler, `{projectionName, tenant}` for read paths). Tested via [`mongodb-memory-server`](https://github.com/nodkz/mongodb-memory-server) so suites run without Docker.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @nwire/mongo mongodb
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ import { MongoClient } from "mongodb";
19
+ import { MongoActorStore, MongoProjectionStore } from "@nwire/mongo";
20
+ import { createApp } from "@nwire/forge";
21
+
22
+ const client = await MongoClient.connect(process.env.MONGODB_URL!);
23
+ const db = client.db("learnflow");
24
+
25
+ const actorStore = new MongoActorStore(db.collection("actors"));
26
+ const projectionStore = new MongoProjectionStore(db.collection("projections"));
27
+ await actorStore.ensureIndexes();
28
+ await projectionStore.ensureIndexes();
29
+
30
+ const app = createApp("learnflow", { modules, actorStore, projectionStore });
31
+ ```
32
+
33
+ ## API surface
34
+
35
+ - `MongoActorStore(collection, options?)` — implements `ActorStore`; `.ensureIndexes()`.
36
+ - `MongoProjectionStore(collection)` — implements `ProjectionStore`; `.ensureIndexes()`.
37
+ - `createMongoActorStore(db, { actorCollection?, lockCollection?, lockTtlMs?, lockPollMs?, lockAcquireTimeoutMs? })` — convenience factory; defaults: `actors`, `actor_locks`, `30000`, `25`, `30000`.
38
+
39
+ ## Distributed locking (HA / multi-replica)
40
+
41
+ Single-process deployments are safe with the default options — the runtime's
42
+ in-memory lock chain serialises dispatches per actor key. For multi-replica
43
+ deployments, pass a `lockCollection` so the per-`(actor, key, tenant)` critical
44
+ section is serialised across processes:
45
+
46
+ ```ts
47
+ const actorStore = new MongoActorStore(db.collection("actors"), {
48
+ lockCollection: db.collection("actor_locks"),
49
+ lockTtlMs: 30_000, // max hold time before a competitor may reclaim
50
+ });
51
+ await actorStore.ensureIndexes(); // creates a TTL index on actor_locks.expiresAt
52
+ ```
53
+
54
+ Mechanism: `findOneAndUpdate({ _id, $or: [{ expiresAt: { $lt: now } }, { owner }] }, ..., { upsert: true })`
55
+ atomically claims the lock or waits. On release the doc is deleted; if the
56
+ holder crashes the TTL takes over (belt and braces — index reaps within ~1
57
+ minute, but a live competitor reclaims as soon as `expiresAt` passes).
58
+
59
+ ## When to use
60
+
61
+ Production workloads with stateful actors and read-model projections.
62
+
63
+ ## Within nwire-app
64
+
65
+ For developers using this package as part of the Nwire stack — register it via `app.use(...)` or it auto-wires when you compose `createApp({ modules })`.
66
+
67
+ ```ts
68
+ import { createApp } from "@nwire/forge";
69
+
70
+ const app = createApp({
71
+ /* ...config... */
72
+ });
73
+ // Adapter/plugin wiring happens here when applicable.
74
+ ```
@@ -0,0 +1,130 @@
1
+ /**
2
+ * MongoDB-backed actor + projection stores.
3
+ *
4
+ * const client = new MongoClient(env.MONGODB_URL)
5
+ * await client.connect()
6
+ * const db = client.db('lemida')
7
+ * const actorStore = new MongoActorStore(db.collection('amit_actors'))
8
+ * const projectionStore = new MongoProjectionStore(db.collection('amit_projections'))
9
+ * const app = createApp({ modules, actorStore, projectionStore })
10
+ *
11
+ * Schemas:
12
+ * actors collection:
13
+ * _id: "{tenant}::{actorName}::{key}" (compound PK)
14
+ * actorName: string (indexed for listInstances)
15
+ * key: string
16
+ * tenant: string (indexed for partition queries)
17
+ * state: string
18
+ * data: unknown (the actor's typed data)
19
+ * activeTimers: Record<string, ActorTimerHandle>
20
+ * fireAtList: number[] (denormalized fireAt values
21
+ * for index-friendly timer
22
+ * scheduler queries)
23
+ *
24
+ * projections collection:
25
+ * _id: "{tenant}::{projectionName}" (compound PK)
26
+ * projectionName: string
27
+ * tenant: string
28
+ * state: unknown
29
+ *
30
+ * Recommended indexes (callers create at boot):
31
+ * actors: { actorName: 1, tenant: 1 }
32
+ * { actorName: 1, "fireAtList": 1 } for timer scheduler
33
+ * projections: { projectionName: 1, tenant: 1 }
34
+ */
35
+ import type { Collection, Db, Document } from "mongodb";
36
+ import type * as forge from "@nwire/forge";
37
+ type ActorStore = forge.ActorStore;
38
+ type ActorInstance = forge.ActorInstance;
39
+ type ProjectionStore = forge.ProjectionStore;
40
+ interface LockDoc extends Document {
41
+ _id: string;
42
+ owner: string;
43
+ expiresAt: Date;
44
+ }
45
+ export interface MongoActorStoreOptions {
46
+ /**
47
+ * Collection that backs the per-(actor, key, tenant) distributed lock.
48
+ * Required for multi-replica deployments. Without it `lockKey` falls back
49
+ * to a no-op — safe for single-process, dangerous under HA.
50
+ */
51
+ readonly lockCollection?: Collection<LockDoc> | AnyCollection;
52
+ /**
53
+ * Lock TTL in milliseconds — the maximum time a single critical section
54
+ * may hold the lock. A crashed holder's lock becomes claimable after this.
55
+ * Keep it well above the longest expected load → reduce → save window.
56
+ * Default: 30s.
57
+ */
58
+ readonly lockTtlMs?: number;
59
+ /**
60
+ * Polling interval while waiting for a contended lock. Default: 25ms.
61
+ * Lower values reduce wait latency at the cost of more Mongo round-trips.
62
+ */
63
+ readonly lockPollMs?: number;
64
+ /**
65
+ * How long to keep retrying before giving up. Default: 30s.
66
+ * Acquisition failure throws — the runtime treats it as a transient
67
+ * error and the dispatching transport decides retry semantics.
68
+ */
69
+ readonly lockAcquireTimeoutMs?: number;
70
+ }
71
+ type AnyCollection = Collection<any>;
72
+ export declare class MongoActorStore implements ActorStore {
73
+ private readonly collection;
74
+ private readonly lockCollection;
75
+ private readonly lockTtlMs;
76
+ private readonly lockPollMs;
77
+ private readonly lockAcquireTimeoutMs;
78
+ /**
79
+ * Unique identifier for this process/instance. Stamped on every lock
80
+ * acquisition so a holder can renew (no-contention re-entry) and so
81
+ * release only removes its own lock — never a stranger's.
82
+ */
83
+ private readonly ownerId;
84
+ constructor(collection: AnyCollection, options?: MongoActorStoreOptions);
85
+ /**
86
+ * Create the recommended indexes for this store. Call once at boot. Safe
87
+ * to call repeatedly — Mongo's `createIndex` is idempotent.
88
+ *
89
+ * If a lock collection is configured, this also creates a TTL index on
90
+ * `expiresAt` so abandoned locks (e.g. from a crashed process) are
91
+ * eventually reaped by Mongo's background TTL monitor — a belt-and-braces
92
+ * safety net on top of the per-acquire expiry check.
93
+ */
94
+ ensureIndexes(): Promise<void>;
95
+ load(actorName: string, key: string, tenant?: string): Promise<ActorInstance | null>;
96
+ save(instance: ActorInstance): Promise<void>;
97
+ exists(actorName: string, key: string, tenant?: string): Promise<boolean>;
98
+ listInstances(actorName: string, tenant?: string): Promise<readonly ActorInstance[]>;
99
+ /**
100
+ * Acquire a distributed lock on `(actorName, key, tenant)` and return a
101
+ * release function. Multi-replica safe: the lock doc lives in Mongo and
102
+ * is claimed atomically via `findOneAndUpdate` with upsert. Holds the
103
+ * lock for at most `lockTtlMs` — if a holder crashes, a competitor can
104
+ * reclaim once the stored `expiresAt` passes.
105
+ *
106
+ * Without a configured `lockCollection` this is a no-op (single-process
107
+ * deployments). For HA, always pass `lockCollection`.
108
+ */
109
+ lockKey(actorName: string, key: string, tenant?: string): Promise<() => void>;
110
+ }
111
+ /**
112
+ * Convenience: derive collections + lock collection from a single `Db`.
113
+ * Defaults match the docs; opt out of the lock collection by passing
114
+ * `lockCollection: false` (no-op locking — single-process mode).
115
+ */
116
+ export declare function createMongoActorStore(db: Db, options?: {
117
+ actorCollection?: string;
118
+ lockCollection?: string | false;
119
+ lockTtlMs?: number;
120
+ lockPollMs?: number;
121
+ lockAcquireTimeoutMs?: number;
122
+ }): MongoActorStore;
123
+ export declare class MongoProjectionStore implements ProjectionStore {
124
+ private readonly collection;
125
+ constructor(collection: AnyCollection);
126
+ ensureIndexes(): Promise<void>;
127
+ load<TState = unknown>(projectionName: string, tenant?: string): Promise<TState | null>;
128
+ save<TState = unknown>(projectionName: string, state: TState, tenant?: string): Promise<void>;
129
+ }
130
+ export {};
@@ -0,0 +1,229 @@
1
+ /**
2
+ * MongoDB-backed actor + projection stores.
3
+ *
4
+ * const client = new MongoClient(env.MONGODB_URL)
5
+ * await client.connect()
6
+ * const db = client.db('lemida')
7
+ * const actorStore = new MongoActorStore(db.collection('amit_actors'))
8
+ * const projectionStore = new MongoProjectionStore(db.collection('amit_projections'))
9
+ * const app = createApp({ modules, actorStore, projectionStore })
10
+ *
11
+ * Schemas:
12
+ * actors collection:
13
+ * _id: "{tenant}::{actorName}::{key}" (compound PK)
14
+ * actorName: string (indexed for listInstances)
15
+ * key: string
16
+ * tenant: string (indexed for partition queries)
17
+ * state: string
18
+ * data: unknown (the actor's typed data)
19
+ * activeTimers: Record<string, ActorTimerHandle>
20
+ * fireAtList: number[] (denormalized fireAt values
21
+ * for index-friendly timer
22
+ * scheduler queries)
23
+ *
24
+ * projections collection:
25
+ * _id: "{tenant}::{projectionName}" (compound PK)
26
+ * projectionName: string
27
+ * tenant: string
28
+ * state: unknown
29
+ *
30
+ * Recommended indexes (callers create at boot):
31
+ * actors: { actorName: 1, tenant: 1 }
32
+ * { actorName: 1, "fireAtList": 1 } for timer scheduler
33
+ * projections: { projectionName: 1, tenant: 1 }
34
+ */
35
+ import { randomUUID } from "node:crypto";
36
+ function actorPk(actorName, key, tenant) {
37
+ return `${tenant}::${actorName}::${key}`;
38
+ }
39
+ function projectionPk(projectionName, tenant) {
40
+ return `${tenant}::${projectionName}`;
41
+ }
42
+ function lockPk(actorName, key, tenant) {
43
+ return `${tenant}::${actorName}::${key}`;
44
+ }
45
+ const DEFAULT_LOCK_TTL_MS = 30_000;
46
+ const DEFAULT_LOCK_POLL_MS = 25;
47
+ const DEFAULT_LOCK_ACQUIRE_TIMEOUT_MS = 30_000;
48
+ export class MongoActorStore {
49
+ collection;
50
+ lockCollection;
51
+ lockTtlMs;
52
+ lockPollMs;
53
+ lockAcquireTimeoutMs;
54
+ /**
55
+ * Unique identifier for this process/instance. Stamped on every lock
56
+ * acquisition so a holder can renew (no-contention re-entry) and so
57
+ * release only removes its own lock — never a stranger's.
58
+ */
59
+ ownerId = randomUUID();
60
+ constructor(collection, options = {}) {
61
+ this.collection = collection;
62
+ this.lockCollection = options.lockCollection ?? null;
63
+ this.lockTtlMs = options.lockTtlMs ?? DEFAULT_LOCK_TTL_MS;
64
+ this.lockPollMs = options.lockPollMs ?? DEFAULT_LOCK_POLL_MS;
65
+ this.lockAcquireTimeoutMs = options.lockAcquireTimeoutMs ?? DEFAULT_LOCK_ACQUIRE_TIMEOUT_MS;
66
+ }
67
+ /**
68
+ * Create the recommended indexes for this store. Call once at boot. Safe
69
+ * to call repeatedly — Mongo's `createIndex` is idempotent.
70
+ *
71
+ * If a lock collection is configured, this also creates a TTL index on
72
+ * `expiresAt` so abandoned locks (e.g. from a crashed process) are
73
+ * eventually reaped by Mongo's background TTL monitor — a belt-and-braces
74
+ * safety net on top of the per-acquire expiry check.
75
+ */
76
+ async ensureIndexes() {
77
+ await this.collection.createIndex({ actorName: 1, tenant: 1 });
78
+ await this.collection.createIndex({ actorName: 1, fireAtList: 1 });
79
+ if (this.lockCollection) {
80
+ // expireAfterSeconds: 0 means "expire AT the timestamp stored in
81
+ // expiresAt". Mongo's TTL monitor runs ~once/minute, so this is a
82
+ // crash-recovery fallback only — live contention relies on the
83
+ // per-acquire `expiresAt < now` check inside findOneAndUpdate.
84
+ await this.lockCollection.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 });
85
+ }
86
+ }
87
+ async load(actorName, key, tenant = "") {
88
+ const doc = await this.collection.findOne({ _id: actorPk(actorName, key, tenant) });
89
+ if (!doc)
90
+ return null;
91
+ return docToInstance(doc);
92
+ }
93
+ async save(instance) {
94
+ const fireAtList = Object.values(instance.activeTimers).map((t) => t.fireAt);
95
+ const doc = {
96
+ _id: actorPk(instance.actorName, instance.key, instance.tenant),
97
+ actorName: instance.actorName,
98
+ key: instance.key,
99
+ tenant: instance.tenant,
100
+ state: instance.state,
101
+ data: instance.data,
102
+ version: instance.version,
103
+ activeTimers: instance.activeTimers,
104
+ fireAtList,
105
+ };
106
+ await this.collection.replaceOne({ _id: doc._id }, doc, { upsert: true });
107
+ }
108
+ async exists(actorName, key, tenant = "") {
109
+ const count = await this.collection.countDocuments({ _id: actorPk(actorName, key, tenant) }, { limit: 1 });
110
+ return count > 0;
111
+ }
112
+ async listInstances(actorName, tenant) {
113
+ const filter = { actorName };
114
+ if (tenant !== undefined)
115
+ filter.tenant = tenant;
116
+ const docs = await this.collection.find(filter).toArray();
117
+ return docs.map(docToInstance);
118
+ }
119
+ /**
120
+ * Acquire a distributed lock on `(actorName, key, tenant)` and return a
121
+ * release function. Multi-replica safe: the lock doc lives in Mongo and
122
+ * is claimed atomically via `findOneAndUpdate` with upsert. Holds the
123
+ * lock for at most `lockTtlMs` — if a holder crashes, a competitor can
124
+ * reclaim once the stored `expiresAt` passes.
125
+ *
126
+ * Without a configured `lockCollection` this is a no-op (single-process
127
+ * deployments). For HA, always pass `lockCollection`.
128
+ */
129
+ async lockKey(actorName, key, tenant = "") {
130
+ if (!this.lockCollection)
131
+ return () => { };
132
+ const lockId = lockPk(actorName, key, tenant);
133
+ const lockCollection = this.lockCollection;
134
+ const deadline = Date.now() + this.lockAcquireTimeoutMs;
135
+ while (true) {
136
+ const now = new Date();
137
+ const expiresAt = new Date(now.getTime() + this.lockTtlMs);
138
+ try {
139
+ // Atomic claim: match either (a) no existing doc (upsert path),
140
+ // (b) an expired doc (previous holder's TTL passed), or (c) our
141
+ // own doc (re-entry by the same owner — safe to refresh). Any
142
+ // other state means a live competitor holds the lock and we wait.
143
+ const result = await lockCollection.findOneAndUpdate({
144
+ _id: lockId,
145
+ $or: [{ expiresAt: { $lt: now } }, { owner: this.ownerId }],
146
+ }, { $set: { owner: this.ownerId, expiresAt } }, { upsert: true, returnDocument: "after" });
147
+ if (result && result.owner === this.ownerId) {
148
+ return async () => {
149
+ // Release only if we still own it — never delete a stranger's
150
+ // lock. If our TTL expired and a competitor claimed it, our
151
+ // delete becomes a harmless no-op.
152
+ await lockCollection.deleteOne({ _id: lockId, owner: this.ownerId });
153
+ };
154
+ }
155
+ }
156
+ catch (err) {
157
+ // E11000 duplicate-key races happen when two acquirers upsert
158
+ // simultaneously; one wins, the other should retry. Anything else
159
+ // is a real failure — surface it.
160
+ if (!isDuplicateKeyError(err))
161
+ throw err;
162
+ }
163
+ if (Date.now() >= deadline) {
164
+ throw new Error(`MongoActorStore.lockKey: timed out acquiring lock for ${lockId} after ${this.lockAcquireTimeoutMs}ms`);
165
+ }
166
+ await sleep(this.lockPollMs);
167
+ }
168
+ }
169
+ }
170
+ /**
171
+ * Convenience: derive collections + lock collection from a single `Db`.
172
+ * Defaults match the docs; opt out of the lock collection by passing
173
+ * `lockCollection: false` (no-op locking — single-process mode).
174
+ */
175
+ export function createMongoActorStore(db, options = {}) {
176
+ const actorCollection = options.actorCollection ?? "actors";
177
+ const lockCollectionName = options.lockCollection === undefined ? "actor_locks" : options.lockCollection;
178
+ const lockCollection = lockCollectionName === false ? undefined : db.collection(lockCollectionName);
179
+ return new MongoActorStore(db.collection(actorCollection), {
180
+ lockCollection,
181
+ lockTtlMs: options.lockTtlMs,
182
+ lockPollMs: options.lockPollMs,
183
+ lockAcquireTimeoutMs: options.lockAcquireTimeoutMs,
184
+ });
185
+ }
186
+ function sleep(ms) {
187
+ return new Promise((resolve) => setTimeout(resolve, ms));
188
+ }
189
+ function isDuplicateKeyError(err) {
190
+ return (typeof err === "object" &&
191
+ err !== null &&
192
+ "code" in err &&
193
+ err.code === 11000);
194
+ }
195
+ export class MongoProjectionStore {
196
+ collection;
197
+ constructor(collection) {
198
+ this.collection = collection;
199
+ }
200
+ async ensureIndexes() {
201
+ await this.collection.createIndex({ projectionName: 1, tenant: 1 });
202
+ }
203
+ async load(projectionName, tenant = "") {
204
+ const doc = await this.collection.findOne({ _id: projectionPk(projectionName, tenant) });
205
+ if (!doc)
206
+ return null;
207
+ return doc.state;
208
+ }
209
+ async save(projectionName, state, tenant = "") {
210
+ const doc = {
211
+ _id: projectionPk(projectionName, tenant),
212
+ projectionName,
213
+ tenant,
214
+ state,
215
+ };
216
+ await this.collection.replaceOne({ _id: doc._id }, doc, { upsert: true });
217
+ }
218
+ }
219
+ function docToInstance(doc) {
220
+ return {
221
+ actorName: doc.actorName,
222
+ key: doc.key,
223
+ tenant: doc.tenant,
224
+ state: doc.state,
225
+ data: doc.data,
226
+ version: doc.version ?? 0,
227
+ activeTimers: doc.activeTimers,
228
+ };
229
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@nwire/mongo` — MongoDB-backed ActorStore + ProjectionStore.
3
+ *
4
+ * import { MongoActorStore, MongoProjectionStore } from '@nwire/mongo'
5
+ * import { MongoClient } from 'mongodb'
6
+ *
7
+ * const client = await MongoClient.connect(env.MONGODB_URL)
8
+ * const db = client.db('learnflow')
9
+ * const actorStore = new MongoActorStore(db.collection('actors'))
10
+ * const projectionStore = new MongoProjectionStore(db.collection('projections'))
11
+ * await actorStore.ensureIndexes()
12
+ * await projectionStore.ensureIndexes()
13
+ *
14
+ * const app = createApp({ modules, actorStore, projectionStore })
15
+ *
16
+ * Indexes (idempotent):
17
+ * actors: { actorName: 1, tenant: 1 }
18
+ * { actorName: 1, fireAtList: 1 } ← for timer scheduler
19
+ * projections: { projectionName: 1, tenant: 1 }
20
+ */
21
+ export { MongoActorStore, MongoProjectionStore, createMongoActorStore, type MongoActorStoreOptions, } from "./mongo-store.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@nwire/mongo` — MongoDB-backed ActorStore + ProjectionStore.
3
+ *
4
+ * import { MongoActorStore, MongoProjectionStore } from '@nwire/mongo'
5
+ * import { MongoClient } from 'mongodb'
6
+ *
7
+ * const client = await MongoClient.connect(env.MONGODB_URL)
8
+ * const db = client.db('learnflow')
9
+ * const actorStore = new MongoActorStore(db.collection('actors'))
10
+ * const projectionStore = new MongoProjectionStore(db.collection('projections'))
11
+ * await actorStore.ensureIndexes()
12
+ * await projectionStore.ensureIndexes()
13
+ *
14
+ * const app = createApp({ modules, actorStore, projectionStore })
15
+ *
16
+ * Indexes (idempotent):
17
+ * actors: { actorName: 1, tenant: 1 }
18
+ * { actorName: 1, fireAtList: 1 } ← for timer scheduler
19
+ * projections: { projectionName: 1, tenant: 1 }
20
+ */
21
+ export { MongoActorStore, MongoProjectionStore, createMongoActorStore, } from "./mongo-store.js";
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@nwire/mongo",
3
+ "version": "0.10.0",
4
+ "description": "Nwire — MongoDB adapters for ActorStore + ProjectionStore. (actorName, key, tenant) compound PK; fireAtList denormalized for indexable timer-scheduler queries; ensureIndexes() idempotent.",
5
+ "keywords": [
6
+ "actor-store",
7
+ "adapter",
8
+ "mongodb",
9
+ "nwire",
10
+ "projection-store",
11
+ "store"
12
+ ],
13
+ "license": "MIT",
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "type": "module",
20
+ "main": "./dist/store-mongo.js",
21
+ "types": "./dist/store-mongo.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/store-mongo.js",
25
+ "types": "./dist/store-mongo.d.ts"
26
+ }
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "dependencies": {
32
+ "mongodb": "^6.21.0",
33
+ "@nwire/forge": "0.10.0",
34
+ "@nwire/envelope": "0.10.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.19.9",
38
+ "mongodb-memory-server": "^10.1.4",
39
+ "typescript": "^5.9.3",
40
+ "vitest": "^4.0.18"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc && node ../../scripts/fix-dist-extensions.mjs dist",
44
+ "dev": "tsc --watch",
45
+ "typecheck": "tsc --noEmit"
46
+ }
47
+ }