@stateledger/memory 0.0.1-experimental.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 Enow Divine
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,66 @@
1
+ # @stateledger/memory
2
+
3
+ > In-memory adapter for [stateledger](https://github.com/enowdivine/stateledger).
4
+ > Use it for tests, hello-world demos, and prototyping — **not production**.
5
+ > State is lost on process exit.
6
+
7
+ > ⚠️ **Placeholder release.** This `experimental` tag exists alongside the
8
+ > rest of the `@stateledger/*` scope while the API stabilizes. The first
9
+ > real release will publish to the `latest` tag.
10
+
11
+ ---
12
+
13
+ ## Install
14
+
15
+ ```
16
+ pnpm add @stateledger/core @stateledger/memory
17
+ ```
18
+
19
+ ## Use
20
+
21
+ ```ts
22
+ import { defineMachine } from "@stateledger/core";
23
+ import { InMemoryAdapter } from "@stateledger/memory";
24
+
25
+ const PaymentMachine = defineMachine({
26
+ name: "payment",
27
+ states: ["pending", "authorized", "captured", "settled"],
28
+ initialState: "pending",
29
+ transitions: [
30
+ { from: "pending", to: "authorized" },
31
+ { from: "authorized", to: "captured" },
32
+ { from: "captured", to: "settled" },
33
+ ],
34
+ } as const);
35
+
36
+ const adapter = new InMemoryAdapter();
37
+ const machine = PaymentMachine.for("payment-1", { adapter });
38
+
39
+ await machine.transitionTo("pending"); // bootstrap
40
+ await machine.transitionTo("authorized");
41
+ await machine.transitionTo("captured");
42
+
43
+ console.log(await machine.history());
44
+ // [
45
+ // { fromState: null, toState: "pending", sortKey: 1, ... },
46
+ // { fromState: "pending", toState: "authorized", sortKey: 2, ... },
47
+ // { fromState: "authorized", toState: "captured", sortKey: 3, ... },
48
+ // ]
49
+ ```
50
+
51
+ ## When to use this
52
+
53
+ - **Unit tests** in user code. Spin up a fresh adapter per test, no DB setup.
54
+ - **Hello-world demos** in documentation or tutorials.
55
+ - **Prototyping** an API design before wiring up real persistence.
56
+
57
+ ## When NOT to use it
58
+
59
+ - Anything where you'd be sad if a server restart wiped the state.
60
+
61
+ For production, use [`@stateledger/prisma`](https://www.npmjs.com/package/@stateledger/prisma)
62
+ (coming soon) or another persistent adapter.
63
+
64
+ ## License
65
+
66
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ var core = require('@stateledger/core');
4
+
5
+ // src/in-memory-adapter.ts
6
+ var KeyedMutex = class {
7
+ queues = /* @__PURE__ */ new Map();
8
+ async acquire(key) {
9
+ let release;
10
+ const next = new Promise((resolve) => {
11
+ release = resolve;
12
+ });
13
+ const prev = this.queues.get(key) ?? Promise.resolve();
14
+ this.queues.set(
15
+ key,
16
+ prev.then(() => next)
17
+ );
18
+ await prev;
19
+ return () => {
20
+ if (this.queues.get(key) === prev.then(() => next)) {
21
+ this.queues.delete(key);
22
+ }
23
+ release();
24
+ };
25
+ }
26
+ };
27
+ var InMemoryAdapter = class {
28
+ rows = [];
29
+ nextId = 1;
30
+ locks = new KeyedMutex();
31
+ // ── transaction lifecycle ──────────────────────────────────
32
+ async withTransaction(fn) {
33
+ const tx = {
34
+ id: `tx-${this.nextId++}`,
35
+ pendingAppends: [],
36
+ pendingPatches: /* @__PURE__ */ new Map(),
37
+ heldLocks: /* @__PURE__ */ new Set()
38
+ };
39
+ const releases = [];
40
+ tx._releases = releases;
41
+ try {
42
+ const result = await fn(tx);
43
+ for (const [rowId, patch] of tx.pendingPatches) {
44
+ const idx = this.rows.findIndex((r) => r.id === rowId);
45
+ if (idx >= 0) this.rows[idx] = { ...this.rows[idx], ...patch };
46
+ }
47
+ this.rows.push(...tx.pendingAppends);
48
+ return result;
49
+ } finally {
50
+ for (const release of releases) release();
51
+ }
52
+ }
53
+ // ── locking ────────────────────────────────────────────────
54
+ async acquireLock(tx, machine, subjectId) {
55
+ const key = `${machine}::${subjectId}`;
56
+ if (tx.heldLocks.has(key)) return;
57
+ const release = await this.locks.acquire(key);
58
+ tx.heldLocks.add(key);
59
+ tx._releases.push(release);
60
+ }
61
+ // ── reads ──────────────────────────────────────────────────
62
+ async readCurrent(tx, machine, subjectId) {
63
+ const all = this.snapshot(tx, machine, subjectId);
64
+ const current = all.find((r) => r.mostRecent);
65
+ return current ? { ...current } : null;
66
+ }
67
+ async readHistory(tx, machine, subjectId) {
68
+ return this.snapshot(tx, machine, subjectId).slice().sort((a, b) => a.sortKey - b.sortKey).map((r) => ({ ...r }));
69
+ }
70
+ /**
71
+ * Merge committed rows + this tx's pending appends/patches into a single
72
+ * view. Reads inside a tx see their own pending writes.
73
+ */
74
+ snapshot(tx, machine, subjectId) {
75
+ const committed = this.rows.filter(
76
+ (r) => r.machine === machine && r.subjectId === subjectId
77
+ );
78
+ if (!tx) return committed;
79
+ const patched = committed.map((r) => {
80
+ const patch = tx.pendingPatches.get(r.id);
81
+ return patch ? { ...r, ...patch } : r;
82
+ });
83
+ const pending = tx.pendingAppends.filter(
84
+ (r) => r.machine === machine && r.subjectId === subjectId
85
+ );
86
+ return [...patched, ...pending];
87
+ }
88
+ // ── writes ─────────────────────────────────────────────────
89
+ async appendTransition(tx, row) {
90
+ try {
91
+ const previousCurrent = await this.readCurrent(tx, row.machine, row.subjectId) ?? null;
92
+ if (previousCurrent) {
93
+ tx.pendingPatches.set(previousCurrent.id, { mostRecent: false });
94
+ }
95
+ const inserted = {
96
+ ...row,
97
+ id: `txn-${this.nextId++}`,
98
+ createdAt: /* @__PURE__ */ new Date()
99
+ };
100
+ tx.pendingAppends.push(inserted);
101
+ return { ...inserted };
102
+ } catch (err) {
103
+ throw new core.AdapterError("in-memory append failed", { cause: err });
104
+ }
105
+ }
106
+ async updateSubjectState(_tx, _hint, _newState) {
107
+ }
108
+ };
109
+
110
+ exports.InMemoryAdapter = InMemoryAdapter;
111
+ //# sourceMappingURL=index.cjs.map
112
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/in-memory-adapter.ts"],"names":["AdapterError"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * In-memory adapter for stateledger.\n *\n * Backed by a plain `Map`. Used by the core test suite to validate the\n * contract test pack itself and as a teaching fixture for adapter authors.\n *\n * Implements `Adapter<InMemoryTx>` with pessimistic semantics:\n * - acquireLock uses a per-`(machine, subjectId)` promise queue so two\n * concurrent transactions cannot hold the lock at once.\n * - appendTransition flips the prior mostRecent inside the same\n * transactional buffer.\n * - withTransaction commits the buffer on success, discards on throw.\n *\n * Not thread-safe in any meaningful sense; this is a teaching adapter, not\n * a production store.\n */\n\nimport type {\n Adapter,\n NewTransitionRow,\n SubjectStateHint,\n TransitionRow,\n} from \"@stateledger/core\";\nimport { AdapterError } from \"@stateledger/core\";\n\ntype StoredRow = TransitionRow;\n\n/** Opaque transaction handle. The adapter knows how to use it; users don't. */\nexport type InMemoryTx = {\n readonly id: string;\n /** Pending inserts staged inside this tx — applied to the store on commit. */\n pendingAppends: StoredRow[];\n /** Per-row patches (e.g. flipping mostRecent) staged inside this tx. */\n pendingPatches: Map<string, Partial<StoredRow>>;\n /** Locks held by this tx, released on commit/rollback. */\n heldLocks: Set<string>;\n};\n\n/** A simple FIFO mutex per key, used to serialize lock acquisition. */\nclass KeyedMutex {\n private queues = new Map<string, Promise<void>>();\n\n async acquire(key: string): Promise<() => void> {\n let release!: () => void;\n const next = new Promise<void>((resolve) => {\n release = resolve;\n });\n const prev = this.queues.get(key) ?? Promise.resolve();\n this.queues.set(\n key,\n prev.then(() => next),\n );\n await prev;\n return () => {\n // If we're the tail, clean the entry so the map doesn't grow forever.\n if (this.queues.get(key) === prev.then(() => next)) {\n this.queues.delete(key);\n }\n release();\n };\n }\n}\n\nexport class InMemoryAdapter implements Adapter<InMemoryTx> {\n private rows: StoredRow[] = [];\n private nextId = 1;\n private locks = new KeyedMutex();\n\n // ── transaction lifecycle ──────────────────────────────────\n\n async withTransaction<R>(fn: (tx: InMemoryTx) => Promise<R>): Promise<R> {\n const tx: InMemoryTx = {\n id: `tx-${this.nextId++}`,\n pendingAppends: [],\n pendingPatches: new Map(),\n heldLocks: new Set(),\n };\n\n const releases: Array<() => void> = [];\n // The InMemoryTx tracks its own held locks via heldLocks; we also keep\n // a parallel array of release callbacks so we can free them on\n // commit/rollback without re-querying the mutex.\n (tx as InMemoryTx & { _releases: Array<() => void> })._releases = releases;\n\n // On success: apply pending writes atomically.\n // On throw: pending buffer is dropped — that's the rollback.\n try {\n const result = await fn(tx);\n for (const [rowId, patch] of tx.pendingPatches) {\n const idx = this.rows.findIndex((r) => r.id === rowId);\n if (idx >= 0) this.rows[idx] = { ...this.rows[idx]!, ...patch } as StoredRow;\n }\n this.rows.push(...tx.pendingAppends);\n return result;\n } finally {\n for (const release of releases) release();\n }\n }\n\n // ── locking ────────────────────────────────────────────────\n\n async acquireLock(tx: InMemoryTx, machine: string, subjectId: string): Promise<void> {\n const key = `${machine}::${subjectId}`;\n if (tx.heldLocks.has(key)) return; // re-entrant within same tx\n const release = await this.locks.acquire(key);\n tx.heldLocks.add(key);\n (tx as InMemoryTx & { _releases: Array<() => void> })._releases.push(release);\n }\n\n // ── reads ──────────────────────────────────────────────────\n\n async readCurrent(\n tx: InMemoryTx | null,\n machine: string,\n subjectId: string,\n ): Promise<TransitionRow | null> {\n const all = this.snapshot(tx, machine, subjectId);\n const current = all.find((r) => r.mostRecent);\n return current ? { ...current } : null;\n }\n\n async readHistory(\n tx: InMemoryTx | null,\n machine: string,\n subjectId: string,\n ): Promise<TransitionRow[]> {\n return this.snapshot(tx, machine, subjectId)\n .slice()\n .sort((a, b) => a.sortKey - b.sortKey)\n .map((r) => ({ ...r }));\n }\n\n /**\n * Merge committed rows + this tx's pending appends/patches into a single\n * view. Reads inside a tx see their own pending writes.\n */\n private snapshot(\n tx: InMemoryTx | null,\n machine: string,\n subjectId: string,\n ): StoredRow[] {\n const committed = this.rows.filter(\n (r) => r.machine === machine && r.subjectId === subjectId,\n );\n if (!tx) return committed;\n\n const patched = committed.map((r) => {\n const patch = tx.pendingPatches.get(r.id);\n return patch ? ({ ...r, ...patch } as StoredRow) : r;\n });\n const pending = tx.pendingAppends.filter(\n (r) => r.machine === machine && r.subjectId === subjectId,\n );\n return [...patched, ...pending];\n }\n\n // ── writes ─────────────────────────────────────────────────\n\n async appendTransition(tx: InMemoryTx, row: NewTransitionRow): Promise<TransitionRow> {\n try {\n // Flip the previous mostRecent (in the tx's pending buffer, not committed yet).\n const previousCurrent = (await this.readCurrent(tx, row.machine, row.subjectId)) ?? null;\n if (previousCurrent) {\n tx.pendingPatches.set(previousCurrent.id, { mostRecent: false });\n }\n\n const inserted: StoredRow = {\n ...row,\n id: `txn-${this.nextId++}`,\n createdAt: new Date(),\n };\n tx.pendingAppends.push(inserted);\n return { ...inserted };\n } catch (err) {\n throw new AdapterError(\"in-memory append failed\", { cause: err });\n }\n }\n\n async updateSubjectState(\n _tx: InMemoryTx,\n _hint: SubjectStateHint,\n _newState: string,\n ): Promise<void> {\n // Optional method — the in-memory adapter has no subject row to update.\n }\n}\n"]}
@@ -0,0 +1,48 @@
1
+ import { Adapter, TransitionRow, NewTransitionRow, SubjectStateHint } from '@stateledger/core';
2
+
3
+ /**
4
+ * In-memory adapter for stateledger.
5
+ *
6
+ * Backed by a plain `Map`. Used by the core test suite to validate the
7
+ * contract test pack itself and as a teaching fixture for adapter authors.
8
+ *
9
+ * Implements `Adapter<InMemoryTx>` with pessimistic semantics:
10
+ * - acquireLock uses a per-`(machine, subjectId)` promise queue so two
11
+ * concurrent transactions cannot hold the lock at once.
12
+ * - appendTransition flips the prior mostRecent inside the same
13
+ * transactional buffer.
14
+ * - withTransaction commits the buffer on success, discards on throw.
15
+ *
16
+ * Not thread-safe in any meaningful sense; this is a teaching adapter, not
17
+ * a production store.
18
+ */
19
+
20
+ type StoredRow = TransitionRow;
21
+ /** Opaque transaction handle. The adapter knows how to use it; users don't. */
22
+ type InMemoryTx = {
23
+ readonly id: string;
24
+ /** Pending inserts staged inside this tx — applied to the store on commit. */
25
+ pendingAppends: StoredRow[];
26
+ /** Per-row patches (e.g. flipping mostRecent) staged inside this tx. */
27
+ pendingPatches: Map<string, Partial<StoredRow>>;
28
+ /** Locks held by this tx, released on commit/rollback. */
29
+ heldLocks: Set<string>;
30
+ };
31
+ declare class InMemoryAdapter implements Adapter<InMemoryTx> {
32
+ private rows;
33
+ private nextId;
34
+ private locks;
35
+ withTransaction<R>(fn: (tx: InMemoryTx) => Promise<R>): Promise<R>;
36
+ acquireLock(tx: InMemoryTx, machine: string, subjectId: string): Promise<void>;
37
+ readCurrent(tx: InMemoryTx | null, machine: string, subjectId: string): Promise<TransitionRow | null>;
38
+ readHistory(tx: InMemoryTx | null, machine: string, subjectId: string): Promise<TransitionRow[]>;
39
+ /**
40
+ * Merge committed rows + this tx's pending appends/patches into a single
41
+ * view. Reads inside a tx see their own pending writes.
42
+ */
43
+ private snapshot;
44
+ appendTransition(tx: InMemoryTx, row: NewTransitionRow): Promise<TransitionRow>;
45
+ updateSubjectState(_tx: InMemoryTx, _hint: SubjectStateHint, _newState: string): Promise<void>;
46
+ }
47
+
48
+ export { InMemoryAdapter, type InMemoryTx };
@@ -0,0 +1,48 @@
1
+ import { Adapter, TransitionRow, NewTransitionRow, SubjectStateHint } from '@stateledger/core';
2
+
3
+ /**
4
+ * In-memory adapter for stateledger.
5
+ *
6
+ * Backed by a plain `Map`. Used by the core test suite to validate the
7
+ * contract test pack itself and as a teaching fixture for adapter authors.
8
+ *
9
+ * Implements `Adapter<InMemoryTx>` with pessimistic semantics:
10
+ * - acquireLock uses a per-`(machine, subjectId)` promise queue so two
11
+ * concurrent transactions cannot hold the lock at once.
12
+ * - appendTransition flips the prior mostRecent inside the same
13
+ * transactional buffer.
14
+ * - withTransaction commits the buffer on success, discards on throw.
15
+ *
16
+ * Not thread-safe in any meaningful sense; this is a teaching adapter, not
17
+ * a production store.
18
+ */
19
+
20
+ type StoredRow = TransitionRow;
21
+ /** Opaque transaction handle. The adapter knows how to use it; users don't. */
22
+ type InMemoryTx = {
23
+ readonly id: string;
24
+ /** Pending inserts staged inside this tx — applied to the store on commit. */
25
+ pendingAppends: StoredRow[];
26
+ /** Per-row patches (e.g. flipping mostRecent) staged inside this tx. */
27
+ pendingPatches: Map<string, Partial<StoredRow>>;
28
+ /** Locks held by this tx, released on commit/rollback. */
29
+ heldLocks: Set<string>;
30
+ };
31
+ declare class InMemoryAdapter implements Adapter<InMemoryTx> {
32
+ private rows;
33
+ private nextId;
34
+ private locks;
35
+ withTransaction<R>(fn: (tx: InMemoryTx) => Promise<R>): Promise<R>;
36
+ acquireLock(tx: InMemoryTx, machine: string, subjectId: string): Promise<void>;
37
+ readCurrent(tx: InMemoryTx | null, machine: string, subjectId: string): Promise<TransitionRow | null>;
38
+ readHistory(tx: InMemoryTx | null, machine: string, subjectId: string): Promise<TransitionRow[]>;
39
+ /**
40
+ * Merge committed rows + this tx's pending appends/patches into a single
41
+ * view. Reads inside a tx see their own pending writes.
42
+ */
43
+ private snapshot;
44
+ appendTransition(tx: InMemoryTx, row: NewTransitionRow): Promise<TransitionRow>;
45
+ updateSubjectState(_tx: InMemoryTx, _hint: SubjectStateHint, _newState: string): Promise<void>;
46
+ }
47
+
48
+ export { InMemoryAdapter, type InMemoryTx };
package/dist/index.js ADDED
@@ -0,0 +1,110 @@
1
+ import { AdapterError } from '@stateledger/core';
2
+
3
+ // src/in-memory-adapter.ts
4
+ var KeyedMutex = class {
5
+ queues = /* @__PURE__ */ new Map();
6
+ async acquire(key) {
7
+ let release;
8
+ const next = new Promise((resolve) => {
9
+ release = resolve;
10
+ });
11
+ const prev = this.queues.get(key) ?? Promise.resolve();
12
+ this.queues.set(
13
+ key,
14
+ prev.then(() => next)
15
+ );
16
+ await prev;
17
+ return () => {
18
+ if (this.queues.get(key) === prev.then(() => next)) {
19
+ this.queues.delete(key);
20
+ }
21
+ release();
22
+ };
23
+ }
24
+ };
25
+ var InMemoryAdapter = class {
26
+ rows = [];
27
+ nextId = 1;
28
+ locks = new KeyedMutex();
29
+ // ── transaction lifecycle ──────────────────────────────────
30
+ async withTransaction(fn) {
31
+ const tx = {
32
+ id: `tx-${this.nextId++}`,
33
+ pendingAppends: [],
34
+ pendingPatches: /* @__PURE__ */ new Map(),
35
+ heldLocks: /* @__PURE__ */ new Set()
36
+ };
37
+ const releases = [];
38
+ tx._releases = releases;
39
+ try {
40
+ const result = await fn(tx);
41
+ for (const [rowId, patch] of tx.pendingPatches) {
42
+ const idx = this.rows.findIndex((r) => r.id === rowId);
43
+ if (idx >= 0) this.rows[idx] = { ...this.rows[idx], ...patch };
44
+ }
45
+ this.rows.push(...tx.pendingAppends);
46
+ return result;
47
+ } finally {
48
+ for (const release of releases) release();
49
+ }
50
+ }
51
+ // ── locking ────────────────────────────────────────────────
52
+ async acquireLock(tx, machine, subjectId) {
53
+ const key = `${machine}::${subjectId}`;
54
+ if (tx.heldLocks.has(key)) return;
55
+ const release = await this.locks.acquire(key);
56
+ tx.heldLocks.add(key);
57
+ tx._releases.push(release);
58
+ }
59
+ // ── reads ──────────────────────────────────────────────────
60
+ async readCurrent(tx, machine, subjectId) {
61
+ const all = this.snapshot(tx, machine, subjectId);
62
+ const current = all.find((r) => r.mostRecent);
63
+ return current ? { ...current } : null;
64
+ }
65
+ async readHistory(tx, machine, subjectId) {
66
+ return this.snapshot(tx, machine, subjectId).slice().sort((a, b) => a.sortKey - b.sortKey).map((r) => ({ ...r }));
67
+ }
68
+ /**
69
+ * Merge committed rows + this tx's pending appends/patches into a single
70
+ * view. Reads inside a tx see their own pending writes.
71
+ */
72
+ snapshot(tx, machine, subjectId) {
73
+ const committed = this.rows.filter(
74
+ (r) => r.machine === machine && r.subjectId === subjectId
75
+ );
76
+ if (!tx) return committed;
77
+ const patched = committed.map((r) => {
78
+ const patch = tx.pendingPatches.get(r.id);
79
+ return patch ? { ...r, ...patch } : r;
80
+ });
81
+ const pending = tx.pendingAppends.filter(
82
+ (r) => r.machine === machine && r.subjectId === subjectId
83
+ );
84
+ return [...patched, ...pending];
85
+ }
86
+ // ── writes ─────────────────────────────────────────────────
87
+ async appendTransition(tx, row) {
88
+ try {
89
+ const previousCurrent = await this.readCurrent(tx, row.machine, row.subjectId) ?? null;
90
+ if (previousCurrent) {
91
+ tx.pendingPatches.set(previousCurrent.id, { mostRecent: false });
92
+ }
93
+ const inserted = {
94
+ ...row,
95
+ id: `txn-${this.nextId++}`,
96
+ createdAt: /* @__PURE__ */ new Date()
97
+ };
98
+ tx.pendingAppends.push(inserted);
99
+ return { ...inserted };
100
+ } catch (err) {
101
+ throw new AdapterError("in-memory append failed", { cause: err });
102
+ }
103
+ }
104
+ async updateSubjectState(_tx, _hint, _newState) {
105
+ }
106
+ };
107
+
108
+ export { InMemoryAdapter };
109
+ //# sourceMappingURL=index.js.map
110
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/in-memory-adapter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * In-memory adapter for stateledger.\n *\n * Backed by a plain `Map`. Used by the core test suite to validate the\n * contract test pack itself and as a teaching fixture for adapter authors.\n *\n * Implements `Adapter<InMemoryTx>` with pessimistic semantics:\n * - acquireLock uses a per-`(machine, subjectId)` promise queue so two\n * concurrent transactions cannot hold the lock at once.\n * - appendTransition flips the prior mostRecent inside the same\n * transactional buffer.\n * - withTransaction commits the buffer on success, discards on throw.\n *\n * Not thread-safe in any meaningful sense; this is a teaching adapter, not\n * a production store.\n */\n\nimport type {\n Adapter,\n NewTransitionRow,\n SubjectStateHint,\n TransitionRow,\n} from \"@stateledger/core\";\nimport { AdapterError } from \"@stateledger/core\";\n\ntype StoredRow = TransitionRow;\n\n/** Opaque transaction handle. The adapter knows how to use it; users don't. */\nexport type InMemoryTx = {\n readonly id: string;\n /** Pending inserts staged inside this tx — applied to the store on commit. */\n pendingAppends: StoredRow[];\n /** Per-row patches (e.g. flipping mostRecent) staged inside this tx. */\n pendingPatches: Map<string, Partial<StoredRow>>;\n /** Locks held by this tx, released on commit/rollback. */\n heldLocks: Set<string>;\n};\n\n/** A simple FIFO mutex per key, used to serialize lock acquisition. */\nclass KeyedMutex {\n private queues = new Map<string, Promise<void>>();\n\n async acquire(key: string): Promise<() => void> {\n let release!: () => void;\n const next = new Promise<void>((resolve) => {\n release = resolve;\n });\n const prev = this.queues.get(key) ?? Promise.resolve();\n this.queues.set(\n key,\n prev.then(() => next),\n );\n await prev;\n return () => {\n // If we're the tail, clean the entry so the map doesn't grow forever.\n if (this.queues.get(key) === prev.then(() => next)) {\n this.queues.delete(key);\n }\n release();\n };\n }\n}\n\nexport class InMemoryAdapter implements Adapter<InMemoryTx> {\n private rows: StoredRow[] = [];\n private nextId = 1;\n private locks = new KeyedMutex();\n\n // ── transaction lifecycle ──────────────────────────────────\n\n async withTransaction<R>(fn: (tx: InMemoryTx) => Promise<R>): Promise<R> {\n const tx: InMemoryTx = {\n id: `tx-${this.nextId++}`,\n pendingAppends: [],\n pendingPatches: new Map(),\n heldLocks: new Set(),\n };\n\n const releases: Array<() => void> = [];\n // The InMemoryTx tracks its own held locks via heldLocks; we also keep\n // a parallel array of release callbacks so we can free them on\n // commit/rollback without re-querying the mutex.\n (tx as InMemoryTx & { _releases: Array<() => void> })._releases = releases;\n\n // On success: apply pending writes atomically.\n // On throw: pending buffer is dropped — that's the rollback.\n try {\n const result = await fn(tx);\n for (const [rowId, patch] of tx.pendingPatches) {\n const idx = this.rows.findIndex((r) => r.id === rowId);\n if (idx >= 0) this.rows[idx] = { ...this.rows[idx]!, ...patch } as StoredRow;\n }\n this.rows.push(...tx.pendingAppends);\n return result;\n } finally {\n for (const release of releases) release();\n }\n }\n\n // ── locking ────────────────────────────────────────────────\n\n async acquireLock(tx: InMemoryTx, machine: string, subjectId: string): Promise<void> {\n const key = `${machine}::${subjectId}`;\n if (tx.heldLocks.has(key)) return; // re-entrant within same tx\n const release = await this.locks.acquire(key);\n tx.heldLocks.add(key);\n (tx as InMemoryTx & { _releases: Array<() => void> })._releases.push(release);\n }\n\n // ── reads ──────────────────────────────────────────────────\n\n async readCurrent(\n tx: InMemoryTx | null,\n machine: string,\n subjectId: string,\n ): Promise<TransitionRow | null> {\n const all = this.snapshot(tx, machine, subjectId);\n const current = all.find((r) => r.mostRecent);\n return current ? { ...current } : null;\n }\n\n async readHistory(\n tx: InMemoryTx | null,\n machine: string,\n subjectId: string,\n ): Promise<TransitionRow[]> {\n return this.snapshot(tx, machine, subjectId)\n .slice()\n .sort((a, b) => a.sortKey - b.sortKey)\n .map((r) => ({ ...r }));\n }\n\n /**\n * Merge committed rows + this tx's pending appends/patches into a single\n * view. Reads inside a tx see their own pending writes.\n */\n private snapshot(\n tx: InMemoryTx | null,\n machine: string,\n subjectId: string,\n ): StoredRow[] {\n const committed = this.rows.filter(\n (r) => r.machine === machine && r.subjectId === subjectId,\n );\n if (!tx) return committed;\n\n const patched = committed.map((r) => {\n const patch = tx.pendingPatches.get(r.id);\n return patch ? ({ ...r, ...patch } as StoredRow) : r;\n });\n const pending = tx.pendingAppends.filter(\n (r) => r.machine === machine && r.subjectId === subjectId,\n );\n return [...patched, ...pending];\n }\n\n // ── writes ─────────────────────────────────────────────────\n\n async appendTransition(tx: InMemoryTx, row: NewTransitionRow): Promise<TransitionRow> {\n try {\n // Flip the previous mostRecent (in the tx's pending buffer, not committed yet).\n const previousCurrent = (await this.readCurrent(tx, row.machine, row.subjectId)) ?? null;\n if (previousCurrent) {\n tx.pendingPatches.set(previousCurrent.id, { mostRecent: false });\n }\n\n const inserted: StoredRow = {\n ...row,\n id: `txn-${this.nextId++}`,\n createdAt: new Date(),\n };\n tx.pendingAppends.push(inserted);\n return { ...inserted };\n } catch (err) {\n throw new AdapterError(\"in-memory append failed\", { cause: err });\n }\n }\n\n async updateSubjectState(\n _tx: InMemoryTx,\n _hint: SubjectStateHint,\n _newState: string,\n ): Promise<void> {\n // Optional method — the in-memory adapter has no subject row to update.\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@stateledger/memory",
3
+ "version": "0.0.1-experimental.0",
4
+ "description": "In-memory adapter for stateledger — for tests, hello-world demos, and prototyping.",
5
+ "license": "MIT",
6
+ "author": "Enow Divine",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/enowdivine/stateledger.git",
10
+ "directory": "packages/memory"
11
+ },
12
+ "homepage": "https://github.com/enowdivine/stateledger#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/enowdivine/stateledger/issues"
15
+ },
16
+ "keywords": [
17
+ "state-machine",
18
+ "in-memory",
19
+ "stateledger",
20
+ "testing",
21
+ "fixture"
22
+ ],
23
+ "type": "module",
24
+ "main": "./dist/index.cjs",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md"
37
+ ],
38
+ "engines": {
39
+ "node": ">=20"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "@stateledger/core": "0.0.1-experimental.0"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "dev": "tsup --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "clean": "rm -rf dist"
52
+ }
53
+ }