@vielzeug/ledger 26.10.14 → 26.10.16

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.
@@ -1 +1 @@
1
- {"version":3,"file":"compose.cjs","names":[],"sources":["../src/compose.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n"],"mappings":"AAEA,SAAS,gBAAuB,EAA6D,CAC3F,GAAM,CAAE,QAAO,QAAO,OAAM,UAAW,EAEvC,MAAO,CAAE,QAAO,QAAO,OAAM,QAAO,CACtC,CAcA,SAAgB,QACd,EACA,EAC0B,CAC1B,IAAM,EAAQ,EAAS,IAAI,eAAe,EAE1C,MAAO,CACL,MAAO,KAAO,IAA4B,CACxC,IAAM,EAAsC,CAAC,EAE7C,GAAI,CACF,IAAK,IAAM,KAAW,EACpB,MAAM,EAAQ,MAAM,CAAO,EAC3B,EAAQ,KAAK,CAAO,CAExB,OAAS,EAAO,CACd,IAAM,EAAkC,CAAC,EAEzC,IAAK,IAAM,IAAW,CAAC,GAAG,CAAO,CAAC,CAAC,QAAQ,EACzC,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAmB,CAC1B,EAAqB,KAAK,CAAiB,CAC7C,CASF,MANI,EAAqB,OAAS,EACtB,eAAe,CAAC,EAAO,GAAG,CAAoB,EAAG,8CAA+C,CACxG,MAAO,CACT,CAAC,EAGG,CACR,CACF,EACA,QACA,OAAQ,KAAO,IAA4B,CACzC,IAAM,EAAsB,CAAC,EAE7B,IAAK,IAAM,IAAW,CAAC,GAAG,CAAK,CAAC,CAAC,QAAQ,EACvC,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAO,CACd,EAAS,KAAK,CAAK,CACrB,CAGF,GAAI,EAAS,OAAS,EAAG,MAAU,eAAe,EAAU,0BAA0B,CACxF,CACF,CACF"}
1
+ {"version":3,"file":"compose.cjs","names":["command","steps","commands","applied","context","error","compensationFailures","compensationError","failures"],"sources":["../src/compose.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n"],"mappings":"AAEA,SAAS,gBAAuB,EAA6D,CAC3F,GAAM,CAAE,QAAO,QAAO,OAAM,UAAWA,EAEvC,MAAO,CAAE,QAAO,QAAO,OAAM,QAAO,CACtC,CAcA,SAAgB,QACd,EACA,EAC0B,CAC1B,IAAMC,EAAQC,EAAS,IAAI,eAAe,EAE1C,MAAO,CACL,MAAO,KAAO,IAA4B,CACxC,IAAMC,EAAsC,CAAC,EAE7C,GAAI,CACF,IAAK,IAAMH,KAAWC,EACpB,MAAMD,EAAQ,MAAMI,CAAO,EAC3BD,EAAQ,KAAKH,CAAO,CAExB,OAASK,EAAO,CACd,IAAMC,EAAkC,CAAC,EAEzC,IAAK,IAAMN,IAAW,CAAC,GAAGG,CAAO,CAAC,CAAC,QAAQ,EACzC,GAAI,CACF,MAAMH,EAAQ,OAAOI,CAAO,CAC9B,OAASG,EAAmB,CAC1BD,EAAqB,KAAKC,CAAiB,CAC7C,CASF,MANID,EAAqB,OAAS,EACtB,eAAe,CAACD,EAAO,GAAGC,CAAoB,EAAG,8CAA+C,CACxG,MAAOD,CACT,CAAC,EAGGA,CACR,CACF,EACA,QACA,OAAQ,KAAO,IAA4B,CACzC,IAAMG,EAAsB,CAAC,EAE7B,IAAK,IAAMR,IAAW,CAAC,GAAGC,CAAK,CAAC,CAAC,QAAQ,EACvC,GAAI,CACF,MAAMD,EAAQ,OAAOI,CAAO,CAC9B,OAASC,EAAO,CACdG,EAAS,KAAKH,CAAK,CACrB,CAGF,GAAIG,EAAS,OAAS,EAAG,MAAU,eAAeA,EAAU,0BAA0B,CACxF,CACF,CACF,C"}
package/dist/compose.js CHANGED
@@ -1,2 +1,43 @@
1
- function snapshotCommand(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,label:n,meta:r,revert:i}}function compose(e,t){let n=e.map(snapshotCommand);return{apply:async e=>{let t=[];try{for(let r of n)await r.apply(e),t.push(r)}catch(n){let r=[];for(let n of[...t].reverse())try{await n.revert(e)}catch(e){r.push(e)}throw r.length>0?AggregateError([n,...r],`Command application and compensation failed`,{cause:n}):n}},label:t,revert:async e=>{let t=[];for(let r of[...n].reverse())try{await r.revert(e)}catch(e){t.push(e)}if(t.length>0)throw AggregateError(t,`Command reversion failed`)}}}export{compose};
1
+ //#region src/compose.ts
2
+ function snapshotCommand(e) {
3
+ let { apply: t, label: n, meta: r, revert: i } = e;
4
+ return {
5
+ apply: t,
6
+ label: n,
7
+ meta: r,
8
+ revert: i
9
+ };
10
+ }
11
+ function compose(e, t) {
12
+ let n = e.map(snapshotCommand);
13
+ return {
14
+ apply: async (e) => {
15
+ let t = [];
16
+ try {
17
+ for (let r of n) await r.apply(e), t.push(r);
18
+ } catch (n) {
19
+ let r = [];
20
+ for (let n of [...t].reverse()) try {
21
+ await n.revert(e);
22
+ } catch (e) {
23
+ r.push(e);
24
+ }
25
+ throw r.length > 0 ? AggregateError([n, ...r], "Command application and compensation failed", { cause: n }) : n;
26
+ }
27
+ },
28
+ label: t,
29
+ revert: async (e) => {
30
+ let t = [];
31
+ for (let r of [...n].reverse()) try {
32
+ await r.revert(e);
33
+ } catch (e) {
34
+ t.push(e);
35
+ }
36
+ if (t.length > 0) throw AggregateError(t, "Command reversion failed");
37
+ }
38
+ };
39
+ }
40
+ //#endregion
41
+ export { compose };
42
+
2
43
  //# sourceMappingURL=compose.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","names":[],"sources":["../src/compose.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n"],"mappings":"AAEA,SAAS,gBAAuB,EAA6D,CAC3F,GAAM,CAAE,QAAO,QAAO,OAAM,UAAW,EAEvC,MAAO,CAAE,QAAO,QAAO,OAAM,QAAO,CACtC,CAcA,SAAgB,QACd,EACA,EAC0B,CAC1B,IAAM,EAAQ,EAAS,IAAI,eAAe,EAE1C,MAAO,CACL,MAAO,KAAO,IAA4B,CACxC,IAAM,EAAsC,CAAC,EAE7C,GAAI,CACF,IAAK,IAAM,KAAW,EACpB,MAAM,EAAQ,MAAM,CAAO,EAC3B,EAAQ,KAAK,CAAO,CAExB,OAAS,EAAO,CACd,IAAM,EAAkC,CAAC,EAEzC,IAAK,IAAM,IAAW,CAAC,GAAG,CAAO,CAAC,CAAC,QAAQ,EACzC,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAmB,CAC1B,EAAqB,KAAK,CAAiB,CAC7C,CASF,MANI,EAAqB,OAAS,EACtB,eAAe,CAAC,EAAO,GAAG,CAAoB,EAAG,8CAA+C,CACxG,MAAO,CACT,CAAC,EAGG,CACR,CACF,EACA,QACA,OAAQ,KAAO,IAA4B,CACzC,IAAM,EAAsB,CAAC,EAE7B,IAAK,IAAM,IAAW,CAAC,GAAG,CAAK,CAAC,CAAC,QAAQ,EACvC,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAO,CACd,EAAS,KAAK,CAAK,CACrB,CAGF,GAAI,EAAS,OAAS,EAAG,MAAU,eAAe,EAAU,0BAA0B,CACxF,CACF,CACF"}
1
+ {"version":3,"file":"compose.js","names":["command","steps","commands","applied","context","error","compensationFailures","compensationError","failures"],"sources":["../src/compose.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n"],"mappings":";AAEA,SAAS,gBAAuB,GAA6D;CAC3F,IAAM,EAAE,UAAO,UAAO,SAAM,cAAWA;CAEvC,OAAO;EAAE;EAAO;EAAO;EAAM;CAAO;AACtC;AAcA,SAAgB,QACd,GACA,GAC0B;CAC1B,IAAMC,IAAQC,EAAS,IAAI,eAAe;CAE1C,OAAO;EACL,OAAO,OAAO,MAA4B;GACxC,IAAMC,IAAsC,CAAC;GAE7C,IAAI;IACF,KAAK,IAAMH,KAAWC,GAEpB,AADA,MAAMD,EAAQ,MAAMI,CAAO,GAC3BD,EAAQ,KAAKH,CAAO;GAExB,SAASK,GAAO;IACd,IAAMC,IAAkC,CAAC;IAEzC,KAAK,IAAMN,KAAW,CAAC,GAAGG,CAAO,CAAC,CAAC,QAAQ,GACzC,IAAI;KACF,MAAMH,EAAQ,OAAOI,CAAO;IAC9B,SAASG,GAAmB;KAC1B,EAAqB,KAAKA,CAAiB;IAC7C;IASF,MANID,EAAqB,SAAS,IACtB,eAAe,CAACD,GAAO,GAAGC,CAAoB,GAAG,+CAA+C,EACxG,OAAOD,EACT,CAAC,IAGGA;GACR;EACF;EACA;EACA,QAAQ,OAAO,MAA4B;GACzC,IAAMG,IAAsB,CAAC;GAE7B,KAAK,IAAMR,KAAW,CAAC,GAAGC,CAAK,CAAC,CAAC,QAAQ,GACvC,IAAI;IACF,MAAMD,EAAQ,OAAOI,CAAO;GAC9B,SAASC,GAAO;IACd,EAAS,KAAKA,CAAK;GACrB;GAGF,IAAIG,EAAS,SAAS,GAAG,MAAU,eAAeA,GAAU,0BAA0B;EACxF;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n"],"mappings":"AACA,IAAa,YAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,kBAAb,cAAuC,WAAY,CAAC"}
1
+ {"version":3,"file":"errors.cjs","names":["message","opts"],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n"],"mappings":"AACA,IAAa,YAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAMA,EAASC,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,kBAAb,cAAuC,WAAY,CAAC,E"}
package/dist/errors.js CHANGED
@@ -1,2 +1,10 @@
1
- var LedgerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},LedgerCancelledError=class extends LedgerError{},LedgerDisposedError=class extends LedgerError{},LedgerExecutionError=class extends LedgerError{},LedgerRollbackError=class extends LedgerError{},LedgerConfigError=class extends LedgerError{};export{LedgerCancelledError,LedgerConfigError,LedgerDisposedError,LedgerError,LedgerExecutionError,LedgerRollbackError};
1
+ //#region src/errors.ts
2
+ var LedgerError = class extends Error {
3
+ constructor(e, t) {
4
+ super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
+ }
6
+ }, LedgerCancelledError = class extends LedgerError {}, LedgerDisposedError = class extends LedgerError {}, LedgerExecutionError = class extends LedgerError {}, LedgerRollbackError = class extends LedgerError {}, LedgerConfigError = class extends LedgerError {};
7
+ //#endregion
8
+ export { LedgerCancelledError, LedgerConfigError, LedgerDisposedError, LedgerError, LedgerExecutionError, LedgerRollbackError };
9
+
2
10
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n"],"mappings":"AACA,IAAa,YAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,kBAAb,cAAuC,WAAY,CAAC"}
1
+ {"version":3,"file":"errors.js","names":["message","opts"],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n"],"mappings":";AACA,IAAa,cAAb,cAAiC,MAAM;CACrC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAMA,GAASC,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAGa,uBAAb,cAA0C,YAAY,CAAC,GAG1C,sBAAb,cAAyC,YAAY,CAAC,GAGzC,uBAAb,cAA0C,YAAY,CAAC,GAG1C,sBAAb,cAAyC,YAAY,CAAC,GAGzC,oBAAb,cAAuC,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1 +1,4 @@
1
- import{compose as e}from"./compose.js";import{LedgerCancelledError as t,LedgerConfigError as n,LedgerDisposedError as r,LedgerError as i,LedgerExecutionError as a,LedgerRollbackError as o}from"./errors.js";import{createLedger as s}from"./ledger.js";export{t as LedgerCancelledError,n as LedgerConfigError,r as LedgerDisposedError,i as LedgerError,a as LedgerExecutionError,o as LedgerRollbackError,e as compose,s as createLedger};
1
+ import { compose as e } from "./compose.js";
2
+ import { LedgerCancelledError as t, LedgerConfigError as n, LedgerDisposedError as r, LedgerError as i, LedgerExecutionError as a, LedgerRollbackError as o } from "./errors.js";
3
+ import { createLedger as s } from "./ledger.js";
4
+ export { t as LedgerCancelledError, n as LedgerConfigError, r as LedgerDisposedError, i as LedgerError, a as LedgerExecutionError, o as LedgerRollbackError, e as compose, s as createLedger };
package/dist/ledger.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function snapshotCommand$1(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,label:n,meta:r,revert:i}}function compose(e,t){let n=e.map(snapshotCommand$1);return{apply:async e=>{let t=[];try{for(let r of n)await r.apply(e),t.push(r)}catch(n){let r=[];for(let n of[...t].reverse())try{await n.revert(e)}catch(e){r.push(e)}throw r.length>0?AggregateError([n,...r],`Command application and compensation failed`,{cause:n}):n}},label:t,revert:async e=>{let t=[];for(let r of[...n].reverse())try{await r.revert(e)}catch(e){t.push(e)}if(t.length>0)throw AggregateError(t,`Command reversion failed`)}}}var LedgerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},LedgerCancelledError=class extends LedgerError{},LedgerDisposedError=class extends LedgerError{},LedgerExecutionError=class extends LedgerError{},LedgerRollbackError=class extends LedgerError{},LedgerConfigError=class extends LedgerError{},StateStore=class{current;listeners=new Set;constructor(e){this.current=e}getSnapshot(){return this.current}set(e){this.current=e;for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function toMessage(e){return e instanceof Error?e.message:String(e)}function snapshotCommand(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,entry:Object.freeze({label:n,meta:r}),revert:i}}function snapshotState(e){return Object.freeze({...e,redo:Object.freeze([...e.redo]),undo:Object.freeze([...e.undo])})}function operationError(e,t){return t?new LedgerDisposedError(`Cannot call ${e}() on a disposed ledger.`):new LedgerCancelledError(`${e}() was cancelled before it started.`)}function createLedger(e={}){let{maxHistory:t=100}=e;if(!Number.isSafeInteger(t)||t<0)throw new LedgerConfigError(`maxHistory must be a non-negative safe integer`);let n=new StateStore(snapshotState({queued:0,redo:[],running:0,undo:[]})),r=new WeakMap,i=new AbortController,a=new Set,o=new Set,s=!1,c=Promise.resolve();function updateState(e){if(n.set(snapshotState(e(n.getSnapshot()))),n.getSnapshot().queued===0&&n.getSnapshot().running===0){for(let e of a)e.resolve();a.clear()}}function settle(e,t){e.settled||(e.settled=!0,o.delete(e),e.cancel(),t===void 0?e.resolve():e.reject(t))}function enqueue(e,t,n){if(s)return Promise.reject(operationError(e,!0));let r=t?AbortSignal.any([t,i.signal]):i.signal;return new Promise((t,i)=>{let onAbort=()=>{a.started||a.settled||s||(updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,!1)))},a={cancel:()=>r.removeEventListener(`abort`,onAbort),reject:i,resolve:t,settled:!1,start:async()=>{if(!a.settled){if(s||r.aborted){updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,s));return}a.started=!0,updateState(e=>({...e,queued:e.queued-1,running:e.running+1}));try{await n({signal:r}),settle(a)}catch(e){settle(a,e)}finally{updateState(e=>({...e,running:e.running-1}))}}},started:!1};r.addEventListener(`abort`,onAbort,{once:!0}),o.add(a),updateState(e=>({...e,queued:e.queued+1})),c=c.then(a.start,a.start)})}async function runDo(e,n){try{await e.apply(n)}catch(e){throw n.signal.aborted?new LedgerCancelledError(`do() was cancelled while running.`,{cause:e}):new LedgerExecutionError(toMessage(e),{cause:e})}if(s||n.signal.aborted)throw new LedgerCancelledError(`do() was cancelled while running.`);updateState(n=>{if(t===0)return{...n,redo:[]};let r=[...n.undo,e.entry];return r.length>t&&r.shift(),{...n,redo:[],undo:r}}),r.set(e.entry,e)}async function runUndo(e){let t=n.getSnapshot().undo[n.getSnapshot().undo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Undo history is corrupted.`);try{await i.revert(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`undo() was cancelled while running.`,{cause:t}):new LedgerRollbackError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`undo() was cancelled while running.`);updateState(e=>({...e,redo:[...e.redo,t],undo:e.undo.slice(0,-1)}))}async function runRedo(e){let t=n.getSnapshot().redo[n.getSnapshot().redo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Redo history is corrupted.`);try{await i.apply(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`redo() was cancelled while running.`,{cause:t}):new LedgerExecutionError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`redo() was cancelled while running.`);updateState(e=>({...e,redo:e.redo.slice(0,-1),undo:[...e.undo,t]}))}return{clear(){return n.getSnapshot().undo.length===0&&n.getSnapshot().redo.length===0?s?Promise.reject(operationError(`clear`,!0)):Promise.resolve():enqueue(`clear`,void 0,async()=>{updateState(e=>({...e,redo:[],undo:[]}))})},get disposalSignal(){return i.signal},dispose(){if(s)return;s=!0,i.abort();let e=[...o].filter(e=>!e.started);for(let t of e)settle(t,operationError(`operation`,!0));for(let e of a)e.reject(operationError(`whenIdle`,!0));a.clear(),updateState(e=>({...e,queued:0,redo:[],undo:[]}))},get disposed(){return s},do(e,t){let n=snapshotCommand(e);return enqueue(`do`,t?.signal,e=>runDo(n,e))},record(e){if(s)throw new LedgerDisposedError(`Cannot call record() on a disposed ledger.`);let n=snapshotCommand(e);updateState(e=>{if(t===0)return{...e,redo:[]};let r=[...e.undo,n.entry];return r.length>t&&r.shift(),{...e,redo:[],undo:r}}),r.set(n.entry,n)},redo(e){return enqueue(`redo`,e?.signal,runRedo)},get state(){return n},[Symbol.dispose](){this.dispose()},undo(e){return enqueue(`undo`,e?.signal,runUndo)},whenIdle(){return n.getSnapshot().queued===0&&n.getSnapshot().running===0?s?Promise.reject(operationError(`whenIdle`,!0)):Promise.resolve():new Promise((e,t)=>a.add({reject:t,resolve:e}))}}}exports.LedgerCancelledError=LedgerCancelledError,exports.LedgerConfigError=LedgerConfigError,exports.LedgerDisposedError=LedgerDisposedError,exports.LedgerError=LedgerError,exports.LedgerExecutionError=LedgerExecutionError,exports.LedgerRollbackError=LedgerRollbackError,exports.compose=compose,exports.createLedger=createLedger;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function snapshotCommand$1(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,label:n,meta:r,revert:i}}function compose(e,t){let n=e.map(snapshotCommand$1);return{apply:async e=>{let t=[];try{for(let r of n)await r.apply(e),t.push(r)}catch(n){let r=[];for(let n of[...t].reverse())try{await n.revert(e)}catch(e){r.push(e)}throw r.length>0?AggregateError([n,...r],`Command application and compensation failed`,{cause:n}):n}},label:t,revert:async e=>{let t=[];for(let r of[...n].reverse())try{await r.revert(e)}catch(e){t.push(e)}if(t.length>0)throw AggregateError(t,`Command reversion failed`)}}}var LedgerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},LedgerCancelledError=class extends LedgerError{},LedgerDisposedError=class extends LedgerError{},LedgerExecutionError=class extends LedgerError{},LedgerRollbackError=class extends LedgerError{},LedgerConfigError=class extends LedgerError{},StateStore=class{current;listeners=new Set;constructor(e){this.current=e}getSnapshot(){return this.current}set(e){this.current=e;for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function toMessage(e){return e instanceof Error?e.message:String(e)}function snapshotCommand(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,entry:Object.freeze({label:n,meta:r}),revert:i}}function snapshotState(e){return Object.freeze({...e,redo:Object.freeze([...e.redo]),undo:Object.freeze([...e.undo])})}function operationError(e,t){return t?new LedgerDisposedError(`Cannot call ${e}() on a disposed ledger.`):new LedgerCancelledError(`${e}() was cancelled before it started.`)}function createLedger(e={}){let{maxHistory:t=100}=e;if(!Number.isSafeInteger(t)||t<0)throw new LedgerConfigError(`maxHistory must be a non-negative safe integer`);let n=new StateStore(snapshotState({queued:0,redo:[],running:0,undo:[]})),r=new WeakMap,i=new AbortController,a=new Set,o=new Set,s=!1,c=Promise.resolve();function updateState(e){if(n.set(snapshotState(e(n.getSnapshot()))),n.getSnapshot().queued===0&&n.getSnapshot().running===0){for(let e of a)e.resolve();a.clear()}}function settle(e,t){e.settled||(e.settled=!0,o.delete(e),e.cancel(),t===void 0?e.resolve():e.reject(t))}function enqueue(e,t,n){if(s)return Promise.reject(operationError(e,!0));let r=t?AbortSignal.any([t,i.signal]):i.signal;return new Promise((t,i)=>{let onAbort=()=>{a.started||a.settled||s||(updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,!1)))},a={cancel:()=>r.removeEventListener(`abort`,onAbort),reject:i,resolve:t,settled:!1,start:async()=>{if(!a.settled){if(s||r.aborted)updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,s));else{a.started=!0,updateState(e=>({...e,queued:e.queued-1,running:e.running+1}));try{await n({signal:r}),settle(a)}catch(e){settle(a,e)}finally{updateState(e=>({...e,running:e.running-1}))}}}},started:!1};r.addEventListener(`abort`,onAbort,{once:!0}),o.add(a),updateState(e=>({...e,queued:e.queued+1})),c=c.then(a.start,a.start)})}async function runDo(e,n){try{await e.apply(n)}catch(e){throw n.signal.aborted?new LedgerCancelledError(`do() was cancelled while running.`,{cause:e}):new LedgerExecutionError(toMessage(e),{cause:e})}if(s||n.signal.aborted)throw new LedgerCancelledError(`do() was cancelled while running.`);updateState(n=>{if(t===0)return{...n,redo:[]};let r=[...n.undo,e.entry];return r.length>t&&r.shift(),{...n,redo:[],undo:r}}),r.set(e.entry,e)}async function runUndo(e){let t=n.getSnapshot().undo[n.getSnapshot().undo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Undo history is corrupted.`);try{await i.revert(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`undo() was cancelled while running.`,{cause:t}):new LedgerRollbackError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`undo() was cancelled while running.`);updateState(e=>({...e,redo:[...e.redo,t],undo:e.undo.slice(0,-1)}))}async function runRedo(e){let t=n.getSnapshot().redo[n.getSnapshot().redo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Redo history is corrupted.`);try{await i.apply(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`redo() was cancelled while running.`,{cause:t}):new LedgerExecutionError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`redo() was cancelled while running.`);updateState(e=>({...e,redo:e.redo.slice(0,-1),undo:[...e.undo,t]}))}return{clear(){return n.getSnapshot().undo.length===0&&n.getSnapshot().redo.length===0?s?Promise.reject(operationError(`clear`,!0)):Promise.resolve():enqueue(`clear`,void 0,async()=>{updateState(e=>({...e,redo:[],undo:[]}))})},get disposalSignal(){return i.signal},dispose(){if(s)return;s=!0,i.abort();let e=[...o].filter(e=>!e.started);for(let t of e)settle(t,operationError(`operation`,!0));for(let e of a)e.reject(operationError(`whenIdle`,!0));a.clear(),updateState(e=>({...e,queued:0,redo:[],undo:[]}))},get disposed(){return s},do(e,t){let n=snapshotCommand(e);return enqueue(`do`,t?.signal,e=>runDo(n,e))},record(e){if(s)throw new LedgerDisposedError(`Cannot call record() on a disposed ledger.`);let n=snapshotCommand(e);updateState(e=>{if(t===0)return{...e,redo:[]};let r=[...e.undo,n.entry];return r.length>t&&r.shift(),{...e,redo:[],undo:r}}),r.set(n.entry,n)},redo(e){return enqueue(`redo`,e?.signal,runRedo)},get state(){return n},[Symbol.dispose](){this.dispose()},undo(e){return enqueue(`undo`,e?.signal,runUndo)},whenIdle(){return n.getSnapshot().queued===0&&n.getSnapshot().running===0?s?Promise.reject(operationError(`whenIdle`,!0)):Promise.resolve():new Promise((e,t)=>a.add({reject:t,resolve:e}))}}}exports.LedgerCancelledError=LedgerCancelledError,exports.LedgerConfigError=LedgerConfigError,exports.LedgerDisposedError=LedgerDisposedError,exports.LedgerError=LedgerError,exports.LedgerExecutionError=LedgerExecutionError,exports.LedgerRollbackError=LedgerRollbackError,exports.compose=compose,exports.createLedger=createLedger;
2
2
  //# sourceMappingURL=ledger.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ledger.cjs","names":[],"sources":["../src/compose.ts","../src/errors.ts","../src/ledger.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n","/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n","import {\n LedgerCancelledError,\n LedgerConfigError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';\nimport type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n Subscribable,\n Unsubscribe,\n} from './types';\n\ntype StoredCommand<TMeta> = {\n apply: (context: CommandContext) => Promise<void> | void;\n entry: HistoryEntry<TMeta>;\n revert: (context: CommandContext) => Promise<void> | void;\n};\n\ntype Operation = {\n cancel: () => void;\n reject: (reason?: unknown) => void;\n resolve: () => void;\n settled: boolean;\n start: () => Promise<void>;\n started: boolean;\n};\n\nclass StateStore<T> implements Subscribable<T> {\n private current: T;\n private readonly listeners = new Set<() => void>();\n\n constructor(initial: T) {\n this.current = initial;\n }\n\n getSnapshot(): T {\n return this.current;\n }\n\n set(next: T): void {\n this.current = next;\n\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n subscribe(listener: () => void): Unsubscribe {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n\nfunction toMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): StoredCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, entry: Object.freeze({ label, meta }), revert };\n}\n\nfunction snapshotState<TMeta>(state: LedgerState<TMeta>): LedgerState<TMeta> {\n return Object.freeze({\n ...state,\n redo: Object.freeze([...state.redo]),\n undo: Object.freeze([...state.undo]),\n });\n}\n\nfunction operationError(method: string, disposed: boolean): LedgerCancelledError | LedgerDisposedError {\n return disposed\n ? new LedgerDisposedError(`Cannot call ${method}() on a disposed ledger.`)\n : new LedgerCancelledError(`${method}() was cancelled before it started.`);\n}\n\n/**\n * Creates a serialized history of reversible commands.\n *\n * Commands are snapshotted when submitted. Queued commands cancelled before their queue turn do\n * not invoke user code. Active commands receive an abort signal and stop cooperatively.\n *\n * @example\n * const ledger = createLedger({ maxHistory: 50 });\n * await ledger.do({\n * apply: () => { item.name = next; },\n * revert: () => { item.name = previous; },\n * });\n * await ledger.undo();\n * await ledger.redo();\n * using ledger = createLedger();\n */\nexport function createLedger<TMeta = undefined>(options: LedgerOptions = {}): Ledger<TMeta> {\n const { maxHistory = 100 } = options;\n\n if (!Number.isSafeInteger(maxHistory) || maxHistory < 0) {\n throw new LedgerConfigError('maxHistory must be a non-negative safe integer');\n }\n\n const state = new StateStore<LedgerState<TMeta>>(snapshotState({ queued: 0, redo: [], running: 0, undo: [] }));\n const commandStore = new WeakMap<HistoryEntry<TMeta>, StoredCommand<TMeta>>();\n const disposalController = new AbortController();\n const idleWaiters = new Set<{ readonly reject: (reason?: unknown) => void; readonly resolve: () => void }>();\n const operations = new Set<Operation>();\n let disposed = false;\n let queue = Promise.resolve();\n\n function updateState(update: (current: LedgerState<TMeta>) => LedgerState<TMeta>): void {\n state.set(snapshotState(update(state.getSnapshot())));\n\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n for (const waiter of idleWaiters) waiter.resolve();\n idleWaiters.clear();\n }\n }\n\n function settle(operation: Operation, error?: unknown): void {\n if (operation.settled) return;\n\n operation.settled = true;\n operations.delete(operation);\n operation.cancel();\n\n if (error === undefined) operation.resolve();\n else operation.reject(error);\n }\n\n function enqueue(\n method: string,\n externalSignal: AbortSignal | undefined,\n task: (context: CommandContext) => Promise<void>,\n ): Promise<void> {\n if (disposed) return Promise.reject(operationError(method, true));\n\n const abortSignal = externalSignal\n ? AbortSignal.any([externalSignal, disposalController.signal])\n : disposalController.signal;\n\n return new Promise<void>((resolve, reject) => {\n const onAbort = (): void => {\n if (operation.started || operation.settled || disposed) return;\n\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, false));\n };\n\n const operation: Operation = {\n cancel: () => abortSignal.removeEventListener('abort', onAbort),\n reject,\n resolve,\n settled: false,\n start: async () => {\n if (operation.settled) return;\n\n if (disposed || abortSignal.aborted) {\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, disposed));\n\n return;\n }\n\n operation.started = true;\n updateState((current) => ({ ...current, queued: current.queued - 1, running: current.running + 1 }));\n\n try {\n await task({ signal: abortSignal });\n settle(operation);\n } catch (error) {\n settle(operation, error);\n } finally {\n updateState((current) => ({ ...current, running: current.running - 1 }));\n }\n },\n started: false,\n };\n\n abortSignal.addEventListener('abort', onAbort, { once: true });\n operations.add(operation);\n updateState((current) => ({ ...current, queued: current.queued + 1 }));\n queue = queue.then(operation.start, operation.start);\n });\n }\n\n async function runDo(command: StoredCommand<TMeta>, context: CommandContext): Promise<void> {\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('do() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('do() was cancelled while running.');\n }\n\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, command.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(command.entry, command);\n }\n\n async function runUndo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().undo[state.getSnapshot().undo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Undo history is corrupted.');\n\n try {\n await command.revert(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('undo() was cancelled while running.', { cause: error })\n : new LedgerRollbackError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('undo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: [...current.redo, entry], undo: current.undo.slice(0, -1) }));\n }\n\n async function runRedo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().redo[state.getSnapshot().redo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Redo history is corrupted.');\n\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('redo() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('redo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: current.redo.slice(0, -1), undo: [...current.undo, entry] }));\n }\n\n return {\n clear(): Promise<void> {\n if (state.getSnapshot().undo.length === 0 && state.getSnapshot().redo.length === 0) {\n return disposed ? Promise.reject(operationError('clear', true)) : Promise.resolve();\n }\n\n return enqueue('clear', undefined, async () => {\n updateState((current) => ({ ...current, redo: [], undo: [] }));\n });\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n const queued = [...operations].filter((operation) => !operation.started);\n\n for (const operation of queued) settle(operation, operationError('operation', true));\n\n for (const waiter of idleWaiters) waiter.reject(operationError('whenIdle', true));\n idleWaiters.clear();\n\n updateState((current) => ({ ...current, queued: 0, redo: [], undo: [] }));\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n do(command: ReversibleCommand<TMeta>, callOptions?: LedgerCallOptions): Promise<void> {\n const snapshot = snapshotCommand(command);\n\n return enqueue('do', callOptions?.signal, (context) => runDo(snapshot, context));\n },\n\n record(command: ReversibleCommand<TMeta>): void {\n if (disposed) throw new LedgerDisposedError('Cannot call record() on a disposed ledger.');\n\n const snapshot = snapshotCommand(command);\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, snapshot.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(snapshot.entry, snapshot);\n },\n\n redo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('redo', callOptions?.signal, runRedo);\n },\n\n get state() {\n return state;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n undo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('undo', callOptions?.signal, runUndo);\n },\n\n whenIdle(): Promise<void> {\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n return disposed ? Promise.reject(operationError('whenIdle', true)) : Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => idleWaiters.add({ reject, resolve }));\n },\n };\n}\n"],"mappings":"mEAEA,SAAS,kBAAuB,EAA6D,CAC3F,GAAM,CAAE,QAAO,QAAO,OAAM,UAAW,EAEvC,MAAO,CAAE,QAAO,QAAO,OAAM,QAAO,CACtC,CAcA,SAAgB,QACd,EACA,EAC0B,CAC1B,IAAM,EAAQ,EAAS,IAAI,iBAAe,EAE1C,MAAO,CACL,MAAO,KAAO,IAA4B,CACxC,IAAM,EAAsC,CAAC,EAE7C,GAAI,CACF,IAAK,IAAM,KAAW,EACpB,MAAM,EAAQ,MAAM,CAAO,EAC3B,EAAQ,KAAK,CAAO,CAExB,OAAS,EAAO,CACd,IAAM,EAAkC,CAAC,EAEzC,IAAK,IAAM,IAAW,CAAC,GAAG,CAAO,CAAC,CAAC,QAAQ,EACzC,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAmB,CAC1B,EAAqB,KAAK,CAAiB,CAC7C,CASF,MANI,EAAqB,OAAS,EACtB,eAAe,CAAC,EAAO,GAAG,CAAoB,EAAG,8CAA+C,CACxG,MAAO,CACT,CAAC,EAGG,CACR,CACF,EACA,QACA,OAAQ,KAAO,IAA4B,CACzC,IAAM,EAAsB,CAAC,EAE7B,IAAK,IAAM,IAAW,CAAC,GAAG,CAAK,CAAC,CAAC,QAAQ,EACvC,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAO,CACd,EAAS,KAAK,CAAK,CACrB,CAGF,GAAI,EAAS,OAAS,EAAG,MAAU,eAAe,EAAU,0BAA0B,CACxF,CACF,CACF,CCrEA,IAAa,YAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,kBAAb,cAAuC,WAAY,CAAC,ECa9C,WAAN,KAA+C,CAC7C,QACA,UAA6B,IAAI,IAEjC,YAAY,EAAY,CACtB,KAAK,QAAU,CACjB,CAEA,aAAiB,CACf,OAAO,KAAK,OACd,CAEA,IAAI,EAAe,CACjB,KAAK,QAAU,EAEf,IAAK,IAAM,IAAY,CAAC,GAAG,KAAK,SAAS,EACvC,GAAI,CACF,EAAS,CACX,OAAS,EAAO,CACd,mBAAqB,CACnB,MAAM,CACR,CAAC,CACH,CAEJ,CAEA,UAAU,EAAmC,CAE3C,OADA,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,CAC7C,CACF,EAEA,SAAS,UAAU,EAAwB,CACzC,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D,CAEA,SAAS,gBAAuB,EAAyD,CACvF,GAAM,CAAE,QAAO,QAAO,OAAM,UAAW,EAEvC,MAAO,CAAE,QAAO,MAAO,OAAO,OAAO,CAAE,QAAO,MAAK,CAAC,EAAG,QAAO,CAChE,CAEA,SAAS,cAAqB,EAA+C,CAC3E,OAAO,OAAO,OAAO,CACnB,GAAG,EACH,KAAM,OAAO,OAAO,CAAC,GAAG,EAAM,IAAI,CAAC,EACnC,KAAM,OAAO,OAAO,CAAC,GAAG,EAAM,IAAI,CAAC,CACrC,CAAC,CACH,CAEA,SAAS,eAAe,EAAgB,EAA+D,CACrG,OAAO,EACH,IAAI,oBAAoB,eAAe,EAAO,yBAAyB,EACvE,IAAI,qBAAqB,GAAG,EAAO,oCAAoC,CAC7E,CAkBA,SAAgB,aAAgC,EAAyB,CAAC,EAAkB,CAC1F,GAAM,CAAE,aAAa,KAAQ,EAE7B,GAAI,CAAC,OAAO,cAAc,CAAU,GAAK,EAAa,EACpD,MAAM,IAAI,kBAAkB,gDAAgD,EAG9E,IAAM,EAAQ,IAAI,WAA+B,cAAc,CAAE,OAAQ,EAAG,KAAM,CAAC,EAAG,QAAS,EAAG,KAAM,CAAC,CAAE,CAAC,CAAC,EACvG,EAAe,IAAI,QACnB,EAAqB,IAAI,gBACzB,EAAc,IAAI,IAClB,EAAa,IAAI,IACnB,EAAW,GACX,EAAQ,QAAQ,QAAQ,EAE5B,SAAS,YAAY,EAAmE,CAGtF,GAFA,EAAM,IAAI,cAAc,EAAO,EAAM,YAAY,CAAC,CAAC,CAAC,EAEhD,EAAM,YAAY,CAAC,CAAC,SAAW,GAAK,EAAM,YAAY,CAAC,CAAC,UAAY,EAAG,CACzE,IAAK,IAAM,KAAU,EAAa,EAAO,QAAQ,EACjD,EAAY,MAAM,CACpB,CACF,CAEA,SAAS,OAAO,EAAsB,EAAuB,CACvD,EAAU,UAEd,EAAU,QAAU,GACpB,EAAW,OAAO,CAAS,EAC3B,EAAU,OAAO,EAEb,IAAU,IAAA,GAAW,EAAU,QAAQ,EACtC,EAAU,OAAO,CAAK,EAC7B,CAEA,SAAS,QACP,EACA,EACA,EACe,CACf,GAAI,EAAU,OAAO,QAAQ,OAAO,eAAe,EAAQ,EAAI,CAAC,EAEhE,IAAM,EAAc,EAChB,YAAY,IAAI,CAAC,EAAgB,EAAmB,MAAM,CAAC,EAC3D,EAAmB,OAEvB,OAAO,IAAI,SAAe,EAAS,IAAW,CAC5C,IAAM,YAAsB,CACtB,EAAU,SAAW,EAAU,SAAW,IAE9C,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAQ,OAAS,CAAE,EAAE,EACrE,OAAO,EAAW,eAAe,EAAQ,EAAK,CAAC,EACjD,EAEM,EAAuB,CAC3B,WAAc,EAAY,oBAAoB,QAAS,OAAO,EAC9D,SACA,UACA,QAAS,GACT,MAAO,SAAY,CACb,MAAU,QAEd,IAAI,GAAY,EAAY,QAAS,CACnC,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAQ,OAAS,CAAE,EAAE,EACrE,OAAO,EAAW,eAAe,EAAQ,CAAQ,CAAC,EAElD,MACF,CAEA,EAAU,QAAU,GACpB,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAQ,OAAS,EAAG,QAAS,EAAQ,QAAU,CAAE,EAAE,EAEnG,GAAI,CACF,MAAM,EAAK,CAAE,OAAQ,CAAY,CAAC,EAClC,OAAO,CAAS,CAClB,OAAS,EAAO,CACd,OAAO,EAAW,CAAK,CACzB,QAAU,CACR,YAAa,IAAa,CAAE,GAAG,EAAS,QAAS,EAAQ,QAAU,CAAE,EAAE,CACzE,CAZA,CAaF,EACA,QAAS,EACX,EAEA,EAAY,iBAAiB,QAAS,QAAS,CAAE,KAAM,EAAK,CAAC,EAC7D,EAAW,IAAI,CAAS,EACxB,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAQ,OAAS,CAAE,EAAE,EACrE,EAAQ,EAAM,KAAK,EAAU,MAAO,EAAU,KAAK,CACrD,CAAC,CACH,CAEA,eAAe,MAAM,EAA+B,EAAwC,CAC1F,GAAI,CACF,MAAM,EAAQ,MAAM,CAAO,CAC7B,OAAS,EAAO,CACd,MAAM,EAAQ,OAAO,QACjB,IAAI,qBAAqB,oCAAqC,CAAE,MAAO,CAAM,CAAC,EAC9E,IAAI,qBAAqB,UAAU,CAAK,EAAG,CAAE,MAAO,CAAM,CAAC,CACjE,CAEA,GAAI,GAAY,EAAQ,OAAO,QAC7B,MAAM,IAAI,qBAAqB,mCAAmC,EAGpE,YAAa,GAAY,CACvB,GAAI,IAAe,EAAG,MAAO,CAAE,GAAG,EAAS,KAAM,CAAC,CAAE,EAEpD,IAAM,EAAO,CAAC,GAAG,EAAQ,KAAM,EAAQ,KAAK,EAI5C,OAFI,EAAK,OAAS,GAAY,EAAK,MAAM,EAElC,CAAE,GAAG,EAAS,KAAM,CAAC,EAAG,MAAK,CACtC,CAAC,EACD,EAAa,IAAI,EAAQ,MAAO,CAAO,CACzC,CAEA,eAAe,QAAQ,EAAwC,CAC7D,IAAM,EAAQ,EAAM,YAAY,CAAC,CAAC,KAAK,EAAM,YAAY,CAAC,CAAC,KAAK,OAAS,GAEzE,GAAI,CAAC,EAAO,OAEZ,IAAM,EAAU,EAAa,IAAI,CAAK,EAEtC,GAAI,CAAC,EAAS,MAAM,IAAI,YAAY,4BAA4B,EAEhE,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAO,CACd,MAAM,EAAQ,OAAO,QACjB,IAAI,qBAAqB,sCAAuC,CAAE,MAAO,CAAM,CAAC,EAChF,IAAI,oBAAoB,UAAU,CAAK,EAAG,CAAE,MAAO,CAAM,CAAC,CAChE,CAEA,GAAI,GAAY,EAAQ,OAAO,QAC7B,MAAM,IAAI,qBAAqB,qCAAqC,EAGtE,YAAa,IAAa,CAAE,GAAG,EAAS,KAAM,CAAC,GAAG,EAAQ,KAAM,CAAK,EAAG,KAAM,EAAQ,KAAK,MAAM,EAAG,EAAE,CAAE,EAAE,CAC5G,CAEA,eAAe,QAAQ,EAAwC,CAC7D,IAAM,EAAQ,EAAM,YAAY,CAAC,CAAC,KAAK,EAAM,YAAY,CAAC,CAAC,KAAK,OAAS,GAEzE,GAAI,CAAC,EAAO,OAEZ,IAAM,EAAU,EAAa,IAAI,CAAK,EAEtC,GAAI,CAAC,EAAS,MAAM,IAAI,YAAY,4BAA4B,EAEhE,GAAI,CACF,MAAM,EAAQ,MAAM,CAAO,CAC7B,OAAS,EAAO,CACd,MAAM,EAAQ,OAAO,QACjB,IAAI,qBAAqB,sCAAuC,CAAE,MAAO,CAAM,CAAC,EAChF,IAAI,qBAAqB,UAAU,CAAK,EAAG,CAAE,MAAO,CAAM,CAAC,CACjE,CAEA,GAAI,GAAY,EAAQ,OAAO,QAC7B,MAAM,IAAI,qBAAqB,qCAAqC,EAGtE,YAAa,IAAa,CAAE,GAAG,EAAS,KAAM,EAAQ,KAAK,MAAM,EAAG,EAAE,EAAG,KAAM,CAAC,GAAG,EAAQ,KAAM,CAAK,CAAE,EAAE,CAC5G,CAEA,MAAO,CACL,OAAuB,CAKrB,OAJI,EAAM,YAAY,CAAC,CAAC,KAAK,SAAW,GAAK,EAAM,YAAY,CAAC,CAAC,KAAK,SAAW,EACxE,EAAW,QAAQ,OAAO,eAAe,QAAS,EAAI,CAAC,EAAI,QAAQ,QAAQ,EAG7E,QAAQ,QAAS,IAAA,GAAW,SAAY,CAC7C,YAAa,IAAa,CAAE,GAAG,EAAS,KAAM,CAAC,EAAG,KAAM,CAAC,CAAE,EAAE,CAC/D,CAAC,CACH,EAEA,IAAI,gBAA8B,CAChC,OAAO,EAAmB,MAC5B,EAEA,SAAgB,CACd,GAAI,EAAU,OAEd,EAAW,GACX,EAAmB,MAAM,EAEzB,IAAM,EAAS,CAAC,GAAG,CAAU,CAAC,CAAC,OAAQ,GAAc,CAAC,EAAU,OAAO,EAEvE,IAAK,IAAM,KAAa,EAAQ,OAAO,EAAW,eAAe,YAAa,EAAI,CAAC,EAEnF,IAAK,IAAM,KAAU,EAAa,EAAO,OAAO,eAAe,WAAY,EAAI,CAAC,EAChF,EAAY,MAAM,EAElB,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAG,KAAM,CAAC,EAAG,KAAM,CAAC,CAAE,EAAE,CAC1E,EAEA,IAAI,UAAoB,CACtB,OAAO,CACT,EAEA,GAAG,EAAmC,EAAgD,CACpF,IAAM,EAAW,gBAAgB,CAAO,EAExC,OAAO,QAAQ,KAAM,GAAa,OAAS,GAAY,MAAM,EAAU,CAAO,CAAC,CACjF,EAEA,OAAO,EAAyC,CAC9C,GAAI,EAAU,MAAM,IAAI,oBAAoB,4CAA4C,EAExF,IAAM,EAAW,gBAAgB,CAAO,EACxC,YAAa,GAAY,CACvB,GAAI,IAAe,EAAG,MAAO,CAAE,GAAG,EAAS,KAAM,CAAC,CAAE,EAEpD,IAAM,EAAO,CAAC,GAAG,EAAQ,KAAM,EAAS,KAAK,EAI7C,OAFI,EAAK,OAAS,GAAY,EAAK,MAAM,EAElC,CAAE,GAAG,EAAS,KAAM,CAAC,EAAG,MAAK,CACtC,CAAC,EACD,EAAa,IAAI,EAAS,MAAO,CAAQ,CAC3C,EAEA,KAAK,EAAgD,CACnD,OAAO,QAAQ,OAAQ,GAAa,OAAQ,OAAO,CACrD,EAEA,IAAI,OAAQ,CACV,OAAO,CACT,EAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,EAEA,KAAK,EAAgD,CACnD,OAAO,QAAQ,OAAQ,GAAa,OAAQ,OAAO,CACrD,EAEA,UAA0B,CAKxB,OAJI,EAAM,YAAY,CAAC,CAAC,SAAW,GAAK,EAAM,YAAY,CAAC,CAAC,UAAY,EAC/D,EAAW,QAAQ,OAAO,eAAe,WAAY,EAAI,CAAC,EAAI,QAAQ,QAAQ,EAGhF,IAAI,SAAe,EAAS,IAAW,EAAY,IAAI,CAAE,SAAQ,SAAQ,CAAC,CAAC,CACpF,CACF,CACF"}
1
+ {"version":3,"file":"ledger.cjs","names":["snapshotCommand","command","steps","commands","applied","context","error","compensationFailures","compensationError","failures","message","opts","initial","next","listener","state","disposed","method","options","maxHistory","commandStore","disposalController","idleWaiters","operations","queue","update","waiter","operation","abortSignal","externalSignal","current","task","undo","entry","queued","snapshot","callOptions"],"sources":["../src/compose.ts","../src/errors.ts","../src/ledger.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n","/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n","import {\n LedgerCancelledError,\n LedgerConfigError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';\nimport type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n Subscribable,\n Unsubscribe,\n} from './types';\n\ntype StoredCommand<TMeta> = {\n apply: (context: CommandContext) => Promise<void> | void;\n entry: HistoryEntry<TMeta>;\n revert: (context: CommandContext) => Promise<void> | void;\n};\n\ntype Operation = {\n cancel: () => void;\n reject: (reason?: unknown) => void;\n resolve: () => void;\n settled: boolean;\n start: () => Promise<void>;\n started: boolean;\n};\n\nclass StateStore<T> implements Subscribable<T> {\n private current: T;\n private readonly listeners = new Set<() => void>();\n\n constructor(initial: T) {\n this.current = initial;\n }\n\n getSnapshot(): T {\n return this.current;\n }\n\n set(next: T): void {\n this.current = next;\n\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n subscribe(listener: () => void): Unsubscribe {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n\nfunction toMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): StoredCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, entry: Object.freeze({ label, meta }), revert };\n}\n\nfunction snapshotState<TMeta>(state: LedgerState<TMeta>): LedgerState<TMeta> {\n return Object.freeze({\n ...state,\n redo: Object.freeze([...state.redo]),\n undo: Object.freeze([...state.undo]),\n });\n}\n\nfunction operationError(method: string, disposed: boolean): LedgerCancelledError | LedgerDisposedError {\n return disposed\n ? new LedgerDisposedError(`Cannot call ${method}() on a disposed ledger.`)\n : new LedgerCancelledError(`${method}() was cancelled before it started.`);\n}\n\n/**\n * Creates a serialized history of reversible commands.\n *\n * Commands are snapshotted when submitted. Queued commands cancelled before their queue turn do\n * not invoke user code. Active commands receive an abort signal and stop cooperatively.\n *\n * @example\n * const ledger = createLedger({ maxHistory: 50 });\n * await ledger.do({\n * apply: () => { item.name = next; },\n * revert: () => { item.name = previous; },\n * });\n * await ledger.undo();\n * await ledger.redo();\n * using ledger = createLedger();\n */\nexport function createLedger<TMeta = undefined>(options: LedgerOptions = {}): Ledger<TMeta> {\n const { maxHistory = 100 } = options;\n\n if (!Number.isSafeInteger(maxHistory) || maxHistory < 0) {\n throw new LedgerConfigError('maxHistory must be a non-negative safe integer');\n }\n\n const state = new StateStore<LedgerState<TMeta>>(snapshotState({ queued: 0, redo: [], running: 0, undo: [] }));\n const commandStore = new WeakMap<HistoryEntry<TMeta>, StoredCommand<TMeta>>();\n const disposalController = new AbortController();\n const idleWaiters = new Set<{ readonly reject: (reason?: unknown) => void; readonly resolve: () => void }>();\n const operations = new Set<Operation>();\n let disposed = false;\n let queue = Promise.resolve();\n\n function updateState(update: (current: LedgerState<TMeta>) => LedgerState<TMeta>): void {\n state.set(snapshotState(update(state.getSnapshot())));\n\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n for (const waiter of idleWaiters) waiter.resolve();\n idleWaiters.clear();\n }\n }\n\n function settle(operation: Operation, error?: unknown): void {\n if (operation.settled) return;\n\n operation.settled = true;\n operations.delete(operation);\n operation.cancel();\n\n if (error === undefined) operation.resolve();\n else operation.reject(error);\n }\n\n function enqueue(\n method: string,\n externalSignal: AbortSignal | undefined,\n task: (context: CommandContext) => Promise<void>,\n ): Promise<void> {\n if (disposed) return Promise.reject(operationError(method, true));\n\n const abortSignal = externalSignal\n ? AbortSignal.any([externalSignal, disposalController.signal])\n : disposalController.signal;\n\n return new Promise<void>((resolve, reject) => {\n const onAbort = (): void => {\n if (operation.started || operation.settled || disposed) return;\n\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, false));\n };\n\n const operation: Operation = {\n cancel: () => abortSignal.removeEventListener('abort', onAbort),\n reject,\n resolve,\n settled: false,\n start: async () => {\n if (operation.settled) return;\n\n if (disposed || abortSignal.aborted) {\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, disposed));\n\n return;\n }\n\n operation.started = true;\n updateState((current) => ({ ...current, queued: current.queued - 1, running: current.running + 1 }));\n\n try {\n await task({ signal: abortSignal });\n settle(operation);\n } catch (error) {\n settle(operation, error);\n } finally {\n updateState((current) => ({ ...current, running: current.running - 1 }));\n }\n },\n started: false,\n };\n\n abortSignal.addEventListener('abort', onAbort, { once: true });\n operations.add(operation);\n updateState((current) => ({ ...current, queued: current.queued + 1 }));\n queue = queue.then(operation.start, operation.start);\n });\n }\n\n async function runDo(command: StoredCommand<TMeta>, context: CommandContext): Promise<void> {\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('do() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('do() was cancelled while running.');\n }\n\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, command.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(command.entry, command);\n }\n\n async function runUndo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().undo[state.getSnapshot().undo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Undo history is corrupted.');\n\n try {\n await command.revert(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('undo() was cancelled while running.', { cause: error })\n : new LedgerRollbackError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('undo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: [...current.redo, entry], undo: current.undo.slice(0, -1) }));\n }\n\n async function runRedo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().redo[state.getSnapshot().redo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Redo history is corrupted.');\n\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('redo() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('redo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: current.redo.slice(0, -1), undo: [...current.undo, entry] }));\n }\n\n return {\n clear(): Promise<void> {\n if (state.getSnapshot().undo.length === 0 && state.getSnapshot().redo.length === 0) {\n return disposed ? Promise.reject(operationError('clear', true)) : Promise.resolve();\n }\n\n return enqueue('clear', undefined, async () => {\n updateState((current) => ({ ...current, redo: [], undo: [] }));\n });\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n const queued = [...operations].filter((operation) => !operation.started);\n\n for (const operation of queued) settle(operation, operationError('operation', true));\n\n for (const waiter of idleWaiters) waiter.reject(operationError('whenIdle', true));\n idleWaiters.clear();\n\n updateState((current) => ({ ...current, queued: 0, redo: [], undo: [] }));\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n do(command: ReversibleCommand<TMeta>, callOptions?: LedgerCallOptions): Promise<void> {\n const snapshot = snapshotCommand(command);\n\n return enqueue('do', callOptions?.signal, (context) => runDo(snapshot, context));\n },\n\n record(command: ReversibleCommand<TMeta>): void {\n if (disposed) throw new LedgerDisposedError('Cannot call record() on a disposed ledger.');\n\n const snapshot = snapshotCommand(command);\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, snapshot.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(snapshot.entry, snapshot);\n },\n\n redo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('redo', callOptions?.signal, runRedo);\n },\n\n get state() {\n return state;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n undo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('undo', callOptions?.signal, runUndo);\n },\n\n whenIdle(): Promise<void> {\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n return disposed ? Promise.reject(operationError('whenIdle', true)) : Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => idleWaiters.add({ reject, resolve }));\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var Ledger=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function snapshotCommand$1(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,label:n,meta:r,revert:i}}function compose(e,t){let n=e.map(snapshotCommand$1);return{apply:async e=>{let t=[];try{for(let r of n)await r.apply(e),t.push(r)}catch(n){let r=[];for(let n of[...t].reverse())try{await n.revert(e)}catch(e){r.push(e)}throw r.length>0?AggregateError([n,...r],`Command application and compensation failed`,{cause:n}):n}},label:t,revert:async e=>{let t=[];for(let r of[...n].reverse())try{await r.revert(e)}catch(e){t.push(e)}if(t.length>0)throw AggregateError(t,`Command reversion failed`)}}}var LedgerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},LedgerCancelledError=class extends LedgerError{},LedgerDisposedError=class extends LedgerError{},LedgerExecutionError=class extends LedgerError{},LedgerRollbackError=class extends LedgerError{},LedgerConfigError=class extends LedgerError{},StateStore=class{current;listeners=new Set;constructor(e){this.current=e}getSnapshot(){return this.current}set(e){this.current=e;for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function toMessage(e){return e instanceof Error?e.message:String(e)}function snapshotCommand(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,entry:Object.freeze({label:n,meta:r}),revert:i}}function snapshotState(e){return Object.freeze({...e,redo:Object.freeze([...e.redo]),undo:Object.freeze([...e.undo])})}function operationError(e,t){return t?new LedgerDisposedError(`Cannot call ${e}() on a disposed ledger.`):new LedgerCancelledError(`${e}() was cancelled before it started.`)}function createLedger(e={}){let{maxHistory:t=100}=e;if(!Number.isSafeInteger(t)||t<0)throw new LedgerConfigError(`maxHistory must be a non-negative safe integer`);let n=new StateStore(snapshotState({queued:0,redo:[],running:0,undo:[]})),r=new WeakMap,i=new AbortController,a=new Set,o=new Set,s=!1,c=Promise.resolve();function updateState(e){if(n.set(snapshotState(e(n.getSnapshot()))),n.getSnapshot().queued===0&&n.getSnapshot().running===0){for(let e of a)e.resolve();a.clear()}}function settle(e,t){e.settled||(e.settled=!0,o.delete(e),e.cancel(),t===void 0?e.resolve():e.reject(t))}function enqueue(e,t,n){if(s)return Promise.reject(operationError(e,!0));let r=t?AbortSignal.any([t,i.signal]):i.signal;return new Promise((t,i)=>{let onAbort=()=>{a.started||a.settled||s||(updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,!1)))},a={cancel:()=>r.removeEventListener(`abort`,onAbort),reject:i,resolve:t,settled:!1,start:async()=>{if(!a.settled){if(s||r.aborted){updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,s));return}a.started=!0,updateState(e=>({...e,queued:e.queued-1,running:e.running+1}));try{await n({signal:r}),settle(a)}catch(e){settle(a,e)}finally{updateState(e=>({...e,running:e.running-1}))}}},started:!1};r.addEventListener(`abort`,onAbort,{once:!0}),o.add(a),updateState(e=>({...e,queued:e.queued+1})),c=c.then(a.start,a.start)})}async function runDo(e,n){try{await e.apply(n)}catch(e){throw n.signal.aborted?new LedgerCancelledError(`do() was cancelled while running.`,{cause:e}):new LedgerExecutionError(toMessage(e),{cause:e})}if(s||n.signal.aborted)throw new LedgerCancelledError(`do() was cancelled while running.`);updateState(n=>{if(t===0)return{...n,redo:[]};let r=[...n.undo,e.entry];return r.length>t&&r.shift(),{...n,redo:[],undo:r}}),r.set(e.entry,e)}async function runUndo(e){let t=n.getSnapshot().undo[n.getSnapshot().undo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Undo history is corrupted.`);try{await i.revert(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`undo() was cancelled while running.`,{cause:t}):new LedgerRollbackError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`undo() was cancelled while running.`);updateState(e=>({...e,redo:[...e.redo,t],undo:e.undo.slice(0,-1)}))}async function runRedo(e){let t=n.getSnapshot().redo[n.getSnapshot().redo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Redo history is corrupted.`);try{await i.apply(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`redo() was cancelled while running.`,{cause:t}):new LedgerExecutionError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`redo() was cancelled while running.`);updateState(e=>({...e,redo:e.redo.slice(0,-1),undo:[...e.undo,t]}))}return{clear(){return n.getSnapshot().undo.length===0&&n.getSnapshot().redo.length===0?s?Promise.reject(operationError(`clear`,!0)):Promise.resolve():enqueue(`clear`,void 0,async()=>{updateState(e=>({...e,redo:[],undo:[]}))})},get disposalSignal(){return i.signal},dispose(){if(s)return;s=!0,i.abort();let e=[...o].filter(e=>!e.started);for(let t of e)settle(t,operationError(`operation`,!0));for(let e of a)e.reject(operationError(`whenIdle`,!0));a.clear(),updateState(e=>({...e,queued:0,redo:[],undo:[]}))},get disposed(){return s},do(e,t){let n=snapshotCommand(e);return enqueue(`do`,t?.signal,e=>runDo(n,e))},record(e){if(s)throw new LedgerDisposedError(`Cannot call record() on a disposed ledger.`);let n=snapshotCommand(e);updateState(e=>{if(t===0)return{...e,redo:[]};let r=[...e.undo,n.entry];return r.length>t&&r.shift(),{...e,redo:[],undo:r}}),r.set(n.entry,n)},redo(e){return enqueue(`redo`,e?.signal,runRedo)},get state(){return n},[Symbol.dispose](){this.dispose()},undo(e){return enqueue(`undo`,e?.signal,runUndo)},whenIdle(){return n.getSnapshot().queued===0&&n.getSnapshot().running===0?s?Promise.reject(operationError(`whenIdle`,!0)):Promise.resolve():new Promise((e,t)=>a.add({reject:t,resolve:e}))}}}return e.LedgerCancelledError=LedgerCancelledError,e.LedgerConfigError=LedgerConfigError,e.LedgerDisposedError=LedgerDisposedError,e.LedgerError=LedgerError,e.LedgerExecutionError=LedgerExecutionError,e.LedgerRollbackError=LedgerRollbackError,e.compose=compose,e.createLedger=createLedger,e})({});
1
+ var Ledger=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function snapshotCommand$1(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,label:n,meta:r,revert:i}}function compose(e,t){let n=e.map(snapshotCommand$1);return{apply:async e=>{let t=[];try{for(let r of n)await r.apply(e),t.push(r)}catch(n){let r=[];for(let n of[...t].reverse())try{await n.revert(e)}catch(e){r.push(e)}throw r.length>0?AggregateError([n,...r],`Command application and compensation failed`,{cause:n}):n}},label:t,revert:async e=>{let t=[];for(let r of[...n].reverse())try{await r.revert(e)}catch(e){t.push(e)}if(t.length>0)throw AggregateError(t,`Command reversion failed`)}}}var LedgerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},LedgerCancelledError=class extends LedgerError{},LedgerDisposedError=class extends LedgerError{},LedgerExecutionError=class extends LedgerError{},LedgerRollbackError=class extends LedgerError{},LedgerConfigError=class extends LedgerError{},StateStore=class{current;listeners=new Set;constructor(e){this.current=e}getSnapshot(){return this.current}set(e){this.current=e;for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function toMessage(e){return e instanceof Error?e.message:String(e)}function snapshotCommand(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,entry:Object.freeze({label:n,meta:r}),revert:i}}function snapshotState(e){return Object.freeze({...e,redo:Object.freeze([...e.redo]),undo:Object.freeze([...e.undo])})}function operationError(e,t){return t?new LedgerDisposedError(`Cannot call ${e}() on a disposed ledger.`):new LedgerCancelledError(`${e}() was cancelled before it started.`)}function createLedger(e={}){let{maxHistory:t=100}=e;if(!Number.isSafeInteger(t)||t<0)throw new LedgerConfigError(`maxHistory must be a non-negative safe integer`);let n=new StateStore(snapshotState({queued:0,redo:[],running:0,undo:[]})),r=new WeakMap,i=new AbortController,a=new Set,o=new Set,s=!1,c=Promise.resolve();function updateState(e){if(n.set(snapshotState(e(n.getSnapshot()))),n.getSnapshot().queued===0&&n.getSnapshot().running===0){for(let e of a)e.resolve();a.clear()}}function settle(e,t){e.settled||(e.settled=!0,o.delete(e),e.cancel(),t===void 0?e.resolve():e.reject(t))}function enqueue(e,t,n){if(s)return Promise.reject(operationError(e,!0));let r=t?AbortSignal.any([t,i.signal]):i.signal;return new Promise((t,i)=>{let onAbort=()=>{a.started||a.settled||s||(updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,!1)))},a={cancel:()=>r.removeEventListener(`abort`,onAbort),reject:i,resolve:t,settled:!1,start:async()=>{if(!a.settled){if(s||r.aborted)updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,s));else{a.started=!0,updateState(e=>({...e,queued:e.queued-1,running:e.running+1}));try{await n({signal:r}),settle(a)}catch(e){settle(a,e)}finally{updateState(e=>({...e,running:e.running-1}))}}}},started:!1};r.addEventListener(`abort`,onAbort,{once:!0}),o.add(a),updateState(e=>({...e,queued:e.queued+1})),c=c.then(a.start,a.start)})}async function runDo(e,n){try{await e.apply(n)}catch(e){throw n.signal.aborted?new LedgerCancelledError(`do() was cancelled while running.`,{cause:e}):new LedgerExecutionError(toMessage(e),{cause:e})}if(s||n.signal.aborted)throw new LedgerCancelledError(`do() was cancelled while running.`);updateState(n=>{if(t===0)return{...n,redo:[]};let r=[...n.undo,e.entry];return r.length>t&&r.shift(),{...n,redo:[],undo:r}}),r.set(e.entry,e)}async function runUndo(e){let t=n.getSnapshot().undo[n.getSnapshot().undo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Undo history is corrupted.`);try{await i.revert(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`undo() was cancelled while running.`,{cause:t}):new LedgerRollbackError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`undo() was cancelled while running.`);updateState(e=>({...e,redo:[...e.redo,t],undo:e.undo.slice(0,-1)}))}async function runRedo(e){let t=n.getSnapshot().redo[n.getSnapshot().redo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Redo history is corrupted.`);try{await i.apply(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`redo() was cancelled while running.`,{cause:t}):new LedgerExecutionError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`redo() was cancelled while running.`);updateState(e=>({...e,redo:e.redo.slice(0,-1),undo:[...e.undo,t]}))}return{clear(){return n.getSnapshot().undo.length===0&&n.getSnapshot().redo.length===0?s?Promise.reject(operationError(`clear`,!0)):Promise.resolve():enqueue(`clear`,void 0,async()=>{updateState(e=>({...e,redo:[],undo:[]}))})},get disposalSignal(){return i.signal},dispose(){if(s)return;s=!0,i.abort();let e=[...o].filter(e=>!e.started);for(let t of e)settle(t,operationError(`operation`,!0));for(let e of a)e.reject(operationError(`whenIdle`,!0));a.clear(),updateState(e=>({...e,queued:0,redo:[],undo:[]}))},get disposed(){return s},do(e,t){let n=snapshotCommand(e);return enqueue(`do`,t?.signal,e=>runDo(n,e))},record(e){if(s)throw new LedgerDisposedError(`Cannot call record() on a disposed ledger.`);let n=snapshotCommand(e);updateState(e=>{if(t===0)return{...e,redo:[]};let r=[...e.undo,n.entry];return r.length>t&&r.shift(),{...e,redo:[],undo:r}}),r.set(n.entry,n)},redo(e){return enqueue(`redo`,e?.signal,runRedo)},get state(){return n},[Symbol.dispose](){this.dispose()},undo(e){return enqueue(`undo`,e?.signal,runUndo)},whenIdle(){return n.getSnapshot().queued===0&&n.getSnapshot().running===0?s?Promise.reject(operationError(`whenIdle`,!0)):Promise.resolve():new Promise((e,t)=>a.add({reject:t,resolve:e}))}}}return e.LedgerCancelledError=LedgerCancelledError,e.LedgerConfigError=LedgerConfigError,e.LedgerDisposedError=LedgerDisposedError,e.LedgerError=LedgerError,e.LedgerExecutionError=LedgerExecutionError,e.LedgerRollbackError=LedgerRollbackError,e.compose=compose,e.createLedger=createLedger,e})({});
2
2
  //# sourceMappingURL=ledger.iife.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ledger.iife.js","names":[],"sources":["../src/compose.ts","../src/errors.ts","../src/ledger.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n","/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n","import {\n LedgerCancelledError,\n LedgerConfigError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';\nimport type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n Subscribable,\n Unsubscribe,\n} from './types';\n\ntype StoredCommand<TMeta> = {\n apply: (context: CommandContext) => Promise<void> | void;\n entry: HistoryEntry<TMeta>;\n revert: (context: CommandContext) => Promise<void> | void;\n};\n\ntype Operation = {\n cancel: () => void;\n reject: (reason?: unknown) => void;\n resolve: () => void;\n settled: boolean;\n start: () => Promise<void>;\n started: boolean;\n};\n\nclass StateStore<T> implements Subscribable<T> {\n private current: T;\n private readonly listeners = new Set<() => void>();\n\n constructor(initial: T) {\n this.current = initial;\n }\n\n getSnapshot(): T {\n return this.current;\n }\n\n set(next: T): void {\n this.current = next;\n\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n subscribe(listener: () => void): Unsubscribe {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n\nfunction toMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): StoredCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, entry: Object.freeze({ label, meta }), revert };\n}\n\nfunction snapshotState<TMeta>(state: LedgerState<TMeta>): LedgerState<TMeta> {\n return Object.freeze({\n ...state,\n redo: Object.freeze([...state.redo]),\n undo: Object.freeze([...state.undo]),\n });\n}\n\nfunction operationError(method: string, disposed: boolean): LedgerCancelledError | LedgerDisposedError {\n return disposed\n ? new LedgerDisposedError(`Cannot call ${method}() on a disposed ledger.`)\n : new LedgerCancelledError(`${method}() was cancelled before it started.`);\n}\n\n/**\n * Creates a serialized history of reversible commands.\n *\n * Commands are snapshotted when submitted. Queued commands cancelled before their queue turn do\n * not invoke user code. Active commands receive an abort signal and stop cooperatively.\n *\n * @example\n * const ledger = createLedger({ maxHistory: 50 });\n * await ledger.do({\n * apply: () => { item.name = next; },\n * revert: () => { item.name = previous; },\n * });\n * await ledger.undo();\n * await ledger.redo();\n * using ledger = createLedger();\n */\nexport function createLedger<TMeta = undefined>(options: LedgerOptions = {}): Ledger<TMeta> {\n const { maxHistory = 100 } = options;\n\n if (!Number.isSafeInteger(maxHistory) || maxHistory < 0) {\n throw new LedgerConfigError('maxHistory must be a non-negative safe integer');\n }\n\n const state = new StateStore<LedgerState<TMeta>>(snapshotState({ queued: 0, redo: [], running: 0, undo: [] }));\n const commandStore = new WeakMap<HistoryEntry<TMeta>, StoredCommand<TMeta>>();\n const disposalController = new AbortController();\n const idleWaiters = new Set<{ readonly reject: (reason?: unknown) => void; readonly resolve: () => void }>();\n const operations = new Set<Operation>();\n let disposed = false;\n let queue = Promise.resolve();\n\n function updateState(update: (current: LedgerState<TMeta>) => LedgerState<TMeta>): void {\n state.set(snapshotState(update(state.getSnapshot())));\n\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n for (const waiter of idleWaiters) waiter.resolve();\n idleWaiters.clear();\n }\n }\n\n function settle(operation: Operation, error?: unknown): void {\n if (operation.settled) return;\n\n operation.settled = true;\n operations.delete(operation);\n operation.cancel();\n\n if (error === undefined) operation.resolve();\n else operation.reject(error);\n }\n\n function enqueue(\n method: string,\n externalSignal: AbortSignal | undefined,\n task: (context: CommandContext) => Promise<void>,\n ): Promise<void> {\n if (disposed) return Promise.reject(operationError(method, true));\n\n const abortSignal = externalSignal\n ? AbortSignal.any([externalSignal, disposalController.signal])\n : disposalController.signal;\n\n return new Promise<void>((resolve, reject) => {\n const onAbort = (): void => {\n if (operation.started || operation.settled || disposed) return;\n\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, false));\n };\n\n const operation: Operation = {\n cancel: () => abortSignal.removeEventListener('abort', onAbort),\n reject,\n resolve,\n settled: false,\n start: async () => {\n if (operation.settled) return;\n\n if (disposed || abortSignal.aborted) {\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, disposed));\n\n return;\n }\n\n operation.started = true;\n updateState((current) => ({ ...current, queued: current.queued - 1, running: current.running + 1 }));\n\n try {\n await task({ signal: abortSignal });\n settle(operation);\n } catch (error) {\n settle(operation, error);\n } finally {\n updateState((current) => ({ ...current, running: current.running - 1 }));\n }\n },\n started: false,\n };\n\n abortSignal.addEventListener('abort', onAbort, { once: true });\n operations.add(operation);\n updateState((current) => ({ ...current, queued: current.queued + 1 }));\n queue = queue.then(operation.start, operation.start);\n });\n }\n\n async function runDo(command: StoredCommand<TMeta>, context: CommandContext): Promise<void> {\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('do() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('do() was cancelled while running.');\n }\n\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, command.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(command.entry, command);\n }\n\n async function runUndo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().undo[state.getSnapshot().undo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Undo history is corrupted.');\n\n try {\n await command.revert(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('undo() was cancelled while running.', { cause: error })\n : new LedgerRollbackError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('undo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: [...current.redo, entry], undo: current.undo.slice(0, -1) }));\n }\n\n async function runRedo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().redo[state.getSnapshot().redo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Redo history is corrupted.');\n\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('redo() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('redo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: current.redo.slice(0, -1), undo: [...current.undo, entry] }));\n }\n\n return {\n clear(): Promise<void> {\n if (state.getSnapshot().undo.length === 0 && state.getSnapshot().redo.length === 0) {\n return disposed ? Promise.reject(operationError('clear', true)) : Promise.resolve();\n }\n\n return enqueue('clear', undefined, async () => {\n updateState((current) => ({ ...current, redo: [], undo: [] }));\n });\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n const queued = [...operations].filter((operation) => !operation.started);\n\n for (const operation of queued) settle(operation, operationError('operation', true));\n\n for (const waiter of idleWaiters) waiter.reject(operationError('whenIdle', true));\n idleWaiters.clear();\n\n updateState((current) => ({ ...current, queued: 0, redo: [], undo: [] }));\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n do(command: ReversibleCommand<TMeta>, callOptions?: LedgerCallOptions): Promise<void> {\n const snapshot = snapshotCommand(command);\n\n return enqueue('do', callOptions?.signal, (context) => runDo(snapshot, context));\n },\n\n record(command: ReversibleCommand<TMeta>): void {\n if (disposed) throw new LedgerDisposedError('Cannot call record() on a disposed ledger.');\n\n const snapshot = snapshotCommand(command);\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, snapshot.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(snapshot.entry, snapshot);\n },\n\n redo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('redo', callOptions?.signal, runRedo);\n },\n\n get state() {\n return state;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n undo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('undo', callOptions?.signal, runUndo);\n },\n\n whenIdle(): Promise<void> {\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n return disposed ? Promise.reject(operationError('whenIdle', true)) : Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => idleWaiters.add({ reject, resolve }));\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ledger.iife.js","names":["snapshotCommand","exports","command","steps","commands","applied","context","error","compensationFailures","compensationError","failures","message","opts","initial","next","listener","state","disposed","method","options","maxHistory","commandStore","disposalController","idleWaiters","operations","queue","update","waiter","operation","abortSignal","externalSignal","current","task","undo","entry","queued","snapshot","callOptions"],"sources":["../src/compose.ts","../src/errors.ts","../src/ledger.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n","/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n","import {\n LedgerCancelledError,\n LedgerConfigError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';\nimport type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n Subscribable,\n Unsubscribe,\n} from './types';\n\ntype StoredCommand<TMeta> = {\n apply: (context: CommandContext) => Promise<void> | void;\n entry: HistoryEntry<TMeta>;\n revert: (context: CommandContext) => Promise<void> | void;\n};\n\ntype Operation = {\n cancel: () => void;\n reject: (reason?: unknown) => void;\n resolve: () => void;\n settled: boolean;\n start: () => Promise<void>;\n started: boolean;\n};\n\nclass StateStore<T> implements Subscribable<T> {\n private current: T;\n private readonly listeners = new Set<() => void>();\n\n constructor(initial: T) {\n this.current = initial;\n }\n\n getSnapshot(): T {\n return this.current;\n }\n\n set(next: T): void {\n this.current = next;\n\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n subscribe(listener: () => void): Unsubscribe {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n\nfunction toMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): StoredCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, entry: Object.freeze({ label, meta }), revert };\n}\n\nfunction snapshotState<TMeta>(state: LedgerState<TMeta>): LedgerState<TMeta> {\n return Object.freeze({\n ...state,\n redo: Object.freeze([...state.redo]),\n undo: Object.freeze([...state.undo]),\n });\n}\n\nfunction operationError(method: string, disposed: boolean): LedgerCancelledError | LedgerDisposedError {\n return disposed\n ? new LedgerDisposedError(`Cannot call ${method}() on a disposed ledger.`)\n : new LedgerCancelledError(`${method}() was cancelled before it started.`);\n}\n\n/**\n * Creates a serialized history of reversible commands.\n *\n * Commands are snapshotted when submitted. Queued commands cancelled before their queue turn do\n * not invoke user code. Active commands receive an abort signal and stop cooperatively.\n *\n * @example\n * const ledger = createLedger({ maxHistory: 50 });\n * await ledger.do({\n * apply: () => { item.name = next; },\n * revert: () => { item.name = previous; },\n * });\n * await ledger.undo();\n * await ledger.redo();\n * using ledger = createLedger();\n */\nexport function createLedger<TMeta = undefined>(options: LedgerOptions = {}): Ledger<TMeta> {\n const { maxHistory = 100 } = options;\n\n if (!Number.isSafeInteger(maxHistory) || maxHistory < 0) {\n throw new LedgerConfigError('maxHistory must be a non-negative safe integer');\n }\n\n const state = new StateStore<LedgerState<TMeta>>(snapshotState({ queued: 0, redo: [], running: 0, undo: [] }));\n const commandStore = new WeakMap<HistoryEntry<TMeta>, StoredCommand<TMeta>>();\n const disposalController = new AbortController();\n const idleWaiters = new Set<{ readonly reject: (reason?: unknown) => void; readonly resolve: () => void }>();\n const operations = new Set<Operation>();\n let disposed = false;\n let queue = Promise.resolve();\n\n function updateState(update: (current: LedgerState<TMeta>) => LedgerState<TMeta>): void {\n state.set(snapshotState(update(state.getSnapshot())));\n\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n for (const waiter of idleWaiters) waiter.resolve();\n idleWaiters.clear();\n }\n }\n\n function settle(operation: Operation, error?: unknown): void {\n if (operation.settled) return;\n\n operation.settled = true;\n operations.delete(operation);\n operation.cancel();\n\n if (error === undefined) operation.resolve();\n else operation.reject(error);\n }\n\n function enqueue(\n method: string,\n externalSignal: AbortSignal | undefined,\n task: (context: CommandContext) => Promise<void>,\n ): Promise<void> {\n if (disposed) return Promise.reject(operationError(method, true));\n\n const abortSignal = externalSignal\n ? AbortSignal.any([externalSignal, disposalController.signal])\n : disposalController.signal;\n\n return new Promise<void>((resolve, reject) => {\n const onAbort = (): void => {\n if (operation.started || operation.settled || disposed) return;\n\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, false));\n };\n\n const operation: Operation = {\n cancel: () => abortSignal.removeEventListener('abort', onAbort),\n reject,\n resolve,\n settled: false,\n start: async () => {\n if (operation.settled) return;\n\n if (disposed || abortSignal.aborted) {\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, disposed));\n\n return;\n }\n\n operation.started = true;\n updateState((current) => ({ ...current, queued: current.queued - 1, running: current.running + 1 }));\n\n try {\n await task({ signal: abortSignal });\n settle(operation);\n } catch (error) {\n settle(operation, error);\n } finally {\n updateState((current) => ({ ...current, running: current.running - 1 }));\n }\n },\n started: false,\n };\n\n abortSignal.addEventListener('abort', onAbort, { once: true });\n operations.add(operation);\n updateState((current) => ({ ...current, queued: current.queued + 1 }));\n queue = queue.then(operation.start, operation.start);\n });\n }\n\n async function runDo(command: StoredCommand<TMeta>, context: CommandContext): Promise<void> {\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('do() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('do() was cancelled while running.');\n }\n\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, command.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(command.entry, command);\n }\n\n async function runUndo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().undo[state.getSnapshot().undo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Undo history is corrupted.');\n\n try {\n await command.revert(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('undo() was cancelled while running.', { cause: error })\n : new LedgerRollbackError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('undo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: [...current.redo, entry], undo: current.undo.slice(0, -1) }));\n }\n\n async function runRedo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().redo[state.getSnapshot().redo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Redo history is corrupted.');\n\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('redo() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('redo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: current.redo.slice(0, -1), undo: [...current.undo, entry] }));\n }\n\n return {\n clear(): Promise<void> {\n if (state.getSnapshot().undo.length === 0 && state.getSnapshot().redo.length === 0) {\n return disposed ? Promise.reject(operationError('clear', true)) : Promise.resolve();\n }\n\n return enqueue('clear', undefined, async () => {\n updateState((current) => ({ ...current, redo: [], undo: [] }));\n });\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n const queued = [...operations].filter((operation) => !operation.started);\n\n for (const operation of queued) settle(operation, operationError('operation', true));\n\n for (const waiter of idleWaiters) waiter.reject(operationError('whenIdle', true));\n idleWaiters.clear();\n\n updateState((current) => ({ ...current, queued: 0, redo: [], undo: [] }));\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n do(command: ReversibleCommand<TMeta>, callOptions?: LedgerCallOptions): Promise<void> {\n const snapshot = snapshotCommand(command);\n\n return enqueue('do', callOptions?.signal, (context) => runDo(snapshot, context));\n },\n\n record(command: ReversibleCommand<TMeta>): void {\n if (disposed) throw new LedgerDisposedError('Cannot call record() on a disposed ledger.');\n\n const snapshot = snapshotCommand(command);\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, snapshot.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(snapshot.entry, snapshot);\n },\n\n redo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('redo', callOptions?.signal, runRedo);\n },\n\n get state() {\n return state;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n undo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('undo', callOptions?.signal, runUndo);\n },\n\n whenIdle(): Promise<void> {\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n return disposed ? Promise.reject(operationError('whenIdle', true)) : Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => idleWaiters.add({ reject, resolve }));\n },\n };\n}\n"],"mappings":"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"}
package/dist/ledger.js CHANGED
@@ -1,2 +1,284 @@
1
- function snapshotCommand$1(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,label:n,meta:r,revert:i}}function compose(e,t){let n=e.map(snapshotCommand$1);return{apply:async e=>{let t=[];try{for(let r of n)await r.apply(e),t.push(r)}catch(n){let r=[];for(let n of[...t].reverse())try{await n.revert(e)}catch(e){r.push(e)}throw r.length>0?AggregateError([n,...r],`Command application and compensation failed`,{cause:n}):n}},label:t,revert:async e=>{let t=[];for(let r of[...n].reverse())try{await r.revert(e)}catch(e){t.push(e)}if(t.length>0)throw AggregateError(t,`Command reversion failed`)}}}var LedgerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},LedgerCancelledError=class extends LedgerError{},LedgerDisposedError=class extends LedgerError{},LedgerExecutionError=class extends LedgerError{},LedgerRollbackError=class extends LedgerError{},LedgerConfigError=class extends LedgerError{},StateStore=class{current;listeners=new Set;constructor(e){this.current=e}getSnapshot(){return this.current}set(e){this.current=e;for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function toMessage(e){return e instanceof Error?e.message:String(e)}function snapshotCommand(e){let{apply:t,label:n,meta:r,revert:i}=e;return{apply:t,entry:Object.freeze({label:n,meta:r}),revert:i}}function snapshotState(e){return Object.freeze({...e,redo:Object.freeze([...e.redo]),undo:Object.freeze([...e.undo])})}function operationError(e,t){return t?new LedgerDisposedError(`Cannot call ${e}() on a disposed ledger.`):new LedgerCancelledError(`${e}() was cancelled before it started.`)}function createLedger(e={}){let{maxHistory:t=100}=e;if(!Number.isSafeInteger(t)||t<0)throw new LedgerConfigError(`maxHistory must be a non-negative safe integer`);let n=new StateStore(snapshotState({queued:0,redo:[],running:0,undo:[]})),r=new WeakMap,i=new AbortController,a=new Set,o=new Set,s=!1,c=Promise.resolve();function updateState(e){if(n.set(snapshotState(e(n.getSnapshot()))),n.getSnapshot().queued===0&&n.getSnapshot().running===0){for(let e of a)e.resolve();a.clear()}}function settle(e,t){e.settled||(e.settled=!0,o.delete(e),e.cancel(),t===void 0?e.resolve():e.reject(t))}function enqueue(e,t,n){if(s)return Promise.reject(operationError(e,!0));let r=t?AbortSignal.any([t,i.signal]):i.signal;return new Promise((t,i)=>{let onAbort=()=>{a.started||a.settled||s||(updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,!1)))},a={cancel:()=>r.removeEventListener(`abort`,onAbort),reject:i,resolve:t,settled:!1,start:async()=>{if(!a.settled){if(s||r.aborted){updateState(e=>({...e,queued:e.queued-1})),settle(a,operationError(e,s));return}a.started=!0,updateState(e=>({...e,queued:e.queued-1,running:e.running+1}));try{await n({signal:r}),settle(a)}catch(e){settle(a,e)}finally{updateState(e=>({...e,running:e.running-1}))}}},started:!1};r.addEventListener(`abort`,onAbort,{once:!0}),o.add(a),updateState(e=>({...e,queued:e.queued+1})),c=c.then(a.start,a.start)})}async function runDo(e,n){try{await e.apply(n)}catch(e){throw n.signal.aborted?new LedgerCancelledError(`do() was cancelled while running.`,{cause:e}):new LedgerExecutionError(toMessage(e),{cause:e})}if(s||n.signal.aborted)throw new LedgerCancelledError(`do() was cancelled while running.`);updateState(n=>{if(t===0)return{...n,redo:[]};let r=[...n.undo,e.entry];return r.length>t&&r.shift(),{...n,redo:[],undo:r}}),r.set(e.entry,e)}async function runUndo(e){let t=n.getSnapshot().undo[n.getSnapshot().undo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Undo history is corrupted.`);try{await i.revert(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`undo() was cancelled while running.`,{cause:t}):new LedgerRollbackError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`undo() was cancelled while running.`);updateState(e=>({...e,redo:[...e.redo,t],undo:e.undo.slice(0,-1)}))}async function runRedo(e){let t=n.getSnapshot().redo[n.getSnapshot().redo.length-1];if(!t)return;let i=r.get(t);if(!i)throw new LedgerError(`Redo history is corrupted.`);try{await i.apply(e)}catch(t){throw e.signal.aborted?new LedgerCancelledError(`redo() was cancelled while running.`,{cause:t}):new LedgerExecutionError(toMessage(t),{cause:t})}if(s||e.signal.aborted)throw new LedgerCancelledError(`redo() was cancelled while running.`);updateState(e=>({...e,redo:e.redo.slice(0,-1),undo:[...e.undo,t]}))}return{clear(){return n.getSnapshot().undo.length===0&&n.getSnapshot().redo.length===0?s?Promise.reject(operationError(`clear`,!0)):Promise.resolve():enqueue(`clear`,void 0,async()=>{updateState(e=>({...e,redo:[],undo:[]}))})},get disposalSignal(){return i.signal},dispose(){if(s)return;s=!0,i.abort();let e=[...o].filter(e=>!e.started);for(let t of e)settle(t,operationError(`operation`,!0));for(let e of a)e.reject(operationError(`whenIdle`,!0));a.clear(),updateState(e=>({...e,queued:0,redo:[],undo:[]}))},get disposed(){return s},do(e,t){let n=snapshotCommand(e);return enqueue(`do`,t?.signal,e=>runDo(n,e))},record(e){if(s)throw new LedgerDisposedError(`Cannot call record() on a disposed ledger.`);let n=snapshotCommand(e);updateState(e=>{if(t===0)return{...e,redo:[]};let r=[...e.undo,n.entry];return r.length>t&&r.shift(),{...e,redo:[],undo:r}}),r.set(n.entry,n)},redo(e){return enqueue(`redo`,e?.signal,runRedo)},get state(){return n},[Symbol.dispose](){this.dispose()},undo(e){return enqueue(`undo`,e?.signal,runUndo)},whenIdle(){return n.getSnapshot().queued===0&&n.getSnapshot().running===0?s?Promise.reject(operationError(`whenIdle`,!0)):Promise.resolve():new Promise((e,t)=>a.add({reject:t,resolve:e}))}}}export{LedgerCancelledError,LedgerConfigError,LedgerDisposedError,LedgerError,LedgerExecutionError,LedgerRollbackError,compose,createLedger};
1
+ //#region src/compose.ts
2
+ function snapshotCommand$1(e) {
3
+ let { apply: t, label: n, meta: r, revert: i } = e;
4
+ return {
5
+ apply: t,
6
+ label: n,
7
+ meta: r,
8
+ revert: i
9
+ };
10
+ }
11
+ function compose(e, t) {
12
+ let n = e.map(snapshotCommand$1);
13
+ return {
14
+ apply: async (e) => {
15
+ let t = [];
16
+ try {
17
+ for (let r of n) await r.apply(e), t.push(r);
18
+ } catch (n) {
19
+ let r = [];
20
+ for (let n of [...t].reverse()) try {
21
+ await n.revert(e);
22
+ } catch (e) {
23
+ r.push(e);
24
+ }
25
+ throw r.length > 0 ? AggregateError([n, ...r], "Command application and compensation failed", { cause: n }) : n;
26
+ }
27
+ },
28
+ label: t,
29
+ revert: async (e) => {
30
+ let t = [];
31
+ for (let r of [...n].reverse()) try {
32
+ await r.revert(e);
33
+ } catch (e) {
34
+ t.push(e);
35
+ }
36
+ if (t.length > 0) throw AggregateError(t, "Command reversion failed");
37
+ }
38
+ };
39
+ }
40
+ //#endregion
41
+ //#region src/errors.ts
42
+ var LedgerError = class extends Error {
43
+ constructor(e, t) {
44
+ super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
45
+ }
46
+ }, LedgerCancelledError = class extends LedgerError {}, LedgerDisposedError = class extends LedgerError {}, LedgerExecutionError = class extends LedgerError {}, LedgerRollbackError = class extends LedgerError {}, LedgerConfigError = class extends LedgerError {}, StateStore = class {
47
+ current;
48
+ listeners = /* @__PURE__ */ new Set();
49
+ constructor(e) {
50
+ this.current = e;
51
+ }
52
+ getSnapshot() {
53
+ return this.current;
54
+ }
55
+ set(e) {
56
+ this.current = e;
57
+ for (let e of [...this.listeners]) try {
58
+ e();
59
+ } catch (e) {
60
+ queueMicrotask(() => {
61
+ throw e;
62
+ });
63
+ }
64
+ }
65
+ subscribe(e) {
66
+ return this.listeners.add(e), () => this.listeners.delete(e);
67
+ }
68
+ };
69
+ function toMessage(e) {
70
+ return e instanceof Error ? e.message : String(e);
71
+ }
72
+ function snapshotCommand(e) {
73
+ let { apply: t, label: n, meta: r, revert: i } = e;
74
+ return {
75
+ apply: t,
76
+ entry: Object.freeze({
77
+ label: n,
78
+ meta: r
79
+ }),
80
+ revert: i
81
+ };
82
+ }
83
+ function snapshotState(e) {
84
+ return Object.freeze({
85
+ ...e,
86
+ redo: Object.freeze([...e.redo]),
87
+ undo: Object.freeze([...e.undo])
88
+ });
89
+ }
90
+ function operationError(e, t) {
91
+ return t ? new LedgerDisposedError(`Cannot call ${e}() on a disposed ledger.`) : new LedgerCancelledError(`${e}() was cancelled before it started.`);
92
+ }
93
+ function createLedger(e = {}) {
94
+ let { maxHistory: t = 100 } = e;
95
+ if (!Number.isSafeInteger(t) || t < 0) throw new LedgerConfigError("maxHistory must be a non-negative safe integer");
96
+ let n = new StateStore(snapshotState({
97
+ queued: 0,
98
+ redo: [],
99
+ running: 0,
100
+ undo: []
101
+ })), r = /* @__PURE__ */ new WeakMap(), i = new AbortController(), a = /* @__PURE__ */ new Set(), o = /* @__PURE__ */ new Set(), s = !1, c = Promise.resolve();
102
+ function updateState(e) {
103
+ if (n.set(snapshotState(e(n.getSnapshot()))), n.getSnapshot().queued === 0 && n.getSnapshot().running === 0) {
104
+ for (let e of a) e.resolve();
105
+ a.clear();
106
+ }
107
+ }
108
+ function settle(e, t) {
109
+ e.settled || (e.settled = !0, o.delete(e), e.cancel(), t === void 0 ? e.resolve() : e.reject(t));
110
+ }
111
+ function enqueue(e, t, n) {
112
+ if (s) return Promise.reject(operationError(e, !0));
113
+ let r = t ? AbortSignal.any([t, i.signal]) : i.signal;
114
+ return new Promise((t, i) => {
115
+ let onAbort = () => {
116
+ a.started || a.settled || s || (updateState((e) => ({
117
+ ...e,
118
+ queued: e.queued - 1
119
+ })), settle(a, operationError(e, !1)));
120
+ }, a = {
121
+ cancel: () => r.removeEventListener("abort", onAbort),
122
+ reject: i,
123
+ resolve: t,
124
+ settled: !1,
125
+ start: async () => {
126
+ if (!a.settled) {
127
+ if (s || r.aborted) updateState((e) => ({
128
+ ...e,
129
+ queued: e.queued - 1
130
+ })), settle(a, operationError(e, s));
131
+ else {
132
+ a.started = !0, updateState((e) => ({
133
+ ...e,
134
+ queued: e.queued - 1,
135
+ running: e.running + 1
136
+ }));
137
+ try {
138
+ await n({ signal: r }), settle(a);
139
+ } catch (e) {
140
+ settle(a, e);
141
+ } finally {
142
+ updateState((e) => ({
143
+ ...e,
144
+ running: e.running - 1
145
+ }));
146
+ }
147
+ }
148
+ }
149
+ },
150
+ started: !1
151
+ };
152
+ r.addEventListener("abort", onAbort, { once: !0 }), o.add(a), updateState((e) => ({
153
+ ...e,
154
+ queued: e.queued + 1
155
+ })), c = c.then(a.start, a.start);
156
+ });
157
+ }
158
+ async function runDo(e, n) {
159
+ try {
160
+ await e.apply(n);
161
+ } catch (e) {
162
+ throw n.signal.aborted ? new LedgerCancelledError("do() was cancelled while running.", { cause: e }) : new LedgerExecutionError(toMessage(e), { cause: e });
163
+ }
164
+ if (s || n.signal.aborted) throw new LedgerCancelledError("do() was cancelled while running.");
165
+ updateState((n) => {
166
+ if (t === 0) return {
167
+ ...n,
168
+ redo: []
169
+ };
170
+ let r = [...n.undo, e.entry];
171
+ return r.length > t && r.shift(), {
172
+ ...n,
173
+ redo: [],
174
+ undo: r
175
+ };
176
+ }), r.set(e.entry, e);
177
+ }
178
+ async function runUndo(e) {
179
+ let t = n.getSnapshot().undo[n.getSnapshot().undo.length - 1];
180
+ if (!t) return;
181
+ let i = r.get(t);
182
+ if (!i) throw new LedgerError("Undo history is corrupted.");
183
+ try {
184
+ await i.revert(e);
185
+ } catch (t) {
186
+ throw e.signal.aborted ? new LedgerCancelledError("undo() was cancelled while running.", { cause: t }) : new LedgerRollbackError(toMessage(t), { cause: t });
187
+ }
188
+ if (s || e.signal.aborted) throw new LedgerCancelledError("undo() was cancelled while running.");
189
+ updateState((e) => ({
190
+ ...e,
191
+ redo: [...e.redo, t],
192
+ undo: e.undo.slice(0, -1)
193
+ }));
194
+ }
195
+ async function runRedo(e) {
196
+ let t = n.getSnapshot().redo[n.getSnapshot().redo.length - 1];
197
+ if (!t) return;
198
+ let i = r.get(t);
199
+ if (!i) throw new LedgerError("Redo history is corrupted.");
200
+ try {
201
+ await i.apply(e);
202
+ } catch (t) {
203
+ throw e.signal.aborted ? new LedgerCancelledError("redo() was cancelled while running.", { cause: t }) : new LedgerExecutionError(toMessage(t), { cause: t });
204
+ }
205
+ if (s || e.signal.aborted) throw new LedgerCancelledError("redo() was cancelled while running.");
206
+ updateState((e) => ({
207
+ ...e,
208
+ redo: e.redo.slice(0, -1),
209
+ undo: [...e.undo, t]
210
+ }));
211
+ }
212
+ return {
213
+ clear() {
214
+ return n.getSnapshot().undo.length === 0 && n.getSnapshot().redo.length === 0 ? s ? Promise.reject(operationError("clear", !0)) : Promise.resolve() : enqueue("clear", void 0, async () => {
215
+ updateState((e) => ({
216
+ ...e,
217
+ redo: [],
218
+ undo: []
219
+ }));
220
+ });
221
+ },
222
+ get disposalSignal() {
223
+ return i.signal;
224
+ },
225
+ dispose() {
226
+ if (s) return;
227
+ s = !0, i.abort();
228
+ let e = [...o].filter((e) => !e.started);
229
+ for (let t of e) settle(t, operationError("operation", !0));
230
+ for (let e of a) e.reject(operationError("whenIdle", !0));
231
+ a.clear(), updateState((e) => ({
232
+ ...e,
233
+ queued: 0,
234
+ redo: [],
235
+ undo: []
236
+ }));
237
+ },
238
+ get disposed() {
239
+ return s;
240
+ },
241
+ do(e, t) {
242
+ let n = snapshotCommand(e);
243
+ return enqueue("do", t?.signal, (e) => runDo(n, e));
244
+ },
245
+ record(e) {
246
+ if (s) throw new LedgerDisposedError("Cannot call record() on a disposed ledger.");
247
+ let n = snapshotCommand(e);
248
+ updateState((e) => {
249
+ if (t === 0) return {
250
+ ...e,
251
+ redo: []
252
+ };
253
+ let r = [...e.undo, n.entry];
254
+ return r.length > t && r.shift(), {
255
+ ...e,
256
+ redo: [],
257
+ undo: r
258
+ };
259
+ }), r.set(n.entry, n);
260
+ },
261
+ redo(e) {
262
+ return enqueue("redo", e?.signal, runRedo);
263
+ },
264
+ get state() {
265
+ return n;
266
+ },
267
+ [Symbol.dispose]() {
268
+ this.dispose();
269
+ },
270
+ undo(e) {
271
+ return enqueue("undo", e?.signal, runUndo);
272
+ },
273
+ whenIdle() {
274
+ return n.getSnapshot().queued === 0 && n.getSnapshot().running === 0 ? s ? Promise.reject(operationError("whenIdle", !0)) : Promise.resolve() : new Promise((e, t) => a.add({
275
+ reject: t,
276
+ resolve: e
277
+ }));
278
+ }
279
+ };
280
+ }
281
+ //#endregion
282
+ export { LedgerCancelledError, LedgerConfigError, LedgerDisposedError, LedgerError, LedgerExecutionError, LedgerRollbackError, compose, createLedger };
283
+
2
284
  //# sourceMappingURL=ledger.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ledger.js","names":[],"sources":["../src/compose.ts","../src/errors.ts","../src/ledger.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n","/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n","import {\n LedgerCancelledError,\n LedgerConfigError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';\nimport type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n Subscribable,\n Unsubscribe,\n} from './types';\n\ntype StoredCommand<TMeta> = {\n apply: (context: CommandContext) => Promise<void> | void;\n entry: HistoryEntry<TMeta>;\n revert: (context: CommandContext) => Promise<void> | void;\n};\n\ntype Operation = {\n cancel: () => void;\n reject: (reason?: unknown) => void;\n resolve: () => void;\n settled: boolean;\n start: () => Promise<void>;\n started: boolean;\n};\n\nclass StateStore<T> implements Subscribable<T> {\n private current: T;\n private readonly listeners = new Set<() => void>();\n\n constructor(initial: T) {\n this.current = initial;\n }\n\n getSnapshot(): T {\n return this.current;\n }\n\n set(next: T): void {\n this.current = next;\n\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n subscribe(listener: () => void): Unsubscribe {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n\nfunction toMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): StoredCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, entry: Object.freeze({ label, meta }), revert };\n}\n\nfunction snapshotState<TMeta>(state: LedgerState<TMeta>): LedgerState<TMeta> {\n return Object.freeze({\n ...state,\n redo: Object.freeze([...state.redo]),\n undo: Object.freeze([...state.undo]),\n });\n}\n\nfunction operationError(method: string, disposed: boolean): LedgerCancelledError | LedgerDisposedError {\n return disposed\n ? new LedgerDisposedError(`Cannot call ${method}() on a disposed ledger.`)\n : new LedgerCancelledError(`${method}() was cancelled before it started.`);\n}\n\n/**\n * Creates a serialized history of reversible commands.\n *\n * Commands are snapshotted when submitted. Queued commands cancelled before their queue turn do\n * not invoke user code. Active commands receive an abort signal and stop cooperatively.\n *\n * @example\n * const ledger = createLedger({ maxHistory: 50 });\n * await ledger.do({\n * apply: () => { item.name = next; },\n * revert: () => { item.name = previous; },\n * });\n * await ledger.undo();\n * await ledger.redo();\n * using ledger = createLedger();\n */\nexport function createLedger<TMeta = undefined>(options: LedgerOptions = {}): Ledger<TMeta> {\n const { maxHistory = 100 } = options;\n\n if (!Number.isSafeInteger(maxHistory) || maxHistory < 0) {\n throw new LedgerConfigError('maxHistory must be a non-negative safe integer');\n }\n\n const state = new StateStore<LedgerState<TMeta>>(snapshotState({ queued: 0, redo: [], running: 0, undo: [] }));\n const commandStore = new WeakMap<HistoryEntry<TMeta>, StoredCommand<TMeta>>();\n const disposalController = new AbortController();\n const idleWaiters = new Set<{ readonly reject: (reason?: unknown) => void; readonly resolve: () => void }>();\n const operations = new Set<Operation>();\n let disposed = false;\n let queue = Promise.resolve();\n\n function updateState(update: (current: LedgerState<TMeta>) => LedgerState<TMeta>): void {\n state.set(snapshotState(update(state.getSnapshot())));\n\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n for (const waiter of idleWaiters) waiter.resolve();\n idleWaiters.clear();\n }\n }\n\n function settle(operation: Operation, error?: unknown): void {\n if (operation.settled) return;\n\n operation.settled = true;\n operations.delete(operation);\n operation.cancel();\n\n if (error === undefined) operation.resolve();\n else operation.reject(error);\n }\n\n function enqueue(\n method: string,\n externalSignal: AbortSignal | undefined,\n task: (context: CommandContext) => Promise<void>,\n ): Promise<void> {\n if (disposed) return Promise.reject(operationError(method, true));\n\n const abortSignal = externalSignal\n ? AbortSignal.any([externalSignal, disposalController.signal])\n : disposalController.signal;\n\n return new Promise<void>((resolve, reject) => {\n const onAbort = (): void => {\n if (operation.started || operation.settled || disposed) return;\n\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, false));\n };\n\n const operation: Operation = {\n cancel: () => abortSignal.removeEventListener('abort', onAbort),\n reject,\n resolve,\n settled: false,\n start: async () => {\n if (operation.settled) return;\n\n if (disposed || abortSignal.aborted) {\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, disposed));\n\n return;\n }\n\n operation.started = true;\n updateState((current) => ({ ...current, queued: current.queued - 1, running: current.running + 1 }));\n\n try {\n await task({ signal: abortSignal });\n settle(operation);\n } catch (error) {\n settle(operation, error);\n } finally {\n updateState((current) => ({ ...current, running: current.running - 1 }));\n }\n },\n started: false,\n };\n\n abortSignal.addEventListener('abort', onAbort, { once: true });\n operations.add(operation);\n updateState((current) => ({ ...current, queued: current.queued + 1 }));\n queue = queue.then(operation.start, operation.start);\n });\n }\n\n async function runDo(command: StoredCommand<TMeta>, context: CommandContext): Promise<void> {\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('do() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('do() was cancelled while running.');\n }\n\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, command.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(command.entry, command);\n }\n\n async function runUndo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().undo[state.getSnapshot().undo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Undo history is corrupted.');\n\n try {\n await command.revert(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('undo() was cancelled while running.', { cause: error })\n : new LedgerRollbackError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('undo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: [...current.redo, entry], undo: current.undo.slice(0, -1) }));\n }\n\n async function runRedo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().redo[state.getSnapshot().redo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Redo history is corrupted.');\n\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('redo() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('redo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: current.redo.slice(0, -1), undo: [...current.undo, entry] }));\n }\n\n return {\n clear(): Promise<void> {\n if (state.getSnapshot().undo.length === 0 && state.getSnapshot().redo.length === 0) {\n return disposed ? Promise.reject(operationError('clear', true)) : Promise.resolve();\n }\n\n return enqueue('clear', undefined, async () => {\n updateState((current) => ({ ...current, redo: [], undo: [] }));\n });\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n const queued = [...operations].filter((operation) => !operation.started);\n\n for (const operation of queued) settle(operation, operationError('operation', true));\n\n for (const waiter of idleWaiters) waiter.reject(operationError('whenIdle', true));\n idleWaiters.clear();\n\n updateState((current) => ({ ...current, queued: 0, redo: [], undo: [] }));\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n do(command: ReversibleCommand<TMeta>, callOptions?: LedgerCallOptions): Promise<void> {\n const snapshot = snapshotCommand(command);\n\n return enqueue('do', callOptions?.signal, (context) => runDo(snapshot, context));\n },\n\n record(command: ReversibleCommand<TMeta>): void {\n if (disposed) throw new LedgerDisposedError('Cannot call record() on a disposed ledger.');\n\n const snapshot = snapshotCommand(command);\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, snapshot.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(snapshot.entry, snapshot);\n },\n\n redo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('redo', callOptions?.signal, runRedo);\n },\n\n get state() {\n return state;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n undo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('undo', callOptions?.signal, runUndo);\n },\n\n whenIdle(): Promise<void> {\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n return disposed ? Promise.reject(operationError('whenIdle', true)) : Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => idleWaiters.add({ reject, resolve }));\n },\n };\n}\n"],"mappings":"AAEA,SAAS,kBAAuB,EAA6D,CAC3F,GAAM,CAAE,QAAO,QAAO,OAAM,UAAW,EAEvC,MAAO,CAAE,QAAO,QAAO,OAAM,QAAO,CACtC,CAcA,SAAgB,QACd,EACA,EAC0B,CAC1B,IAAM,EAAQ,EAAS,IAAI,iBAAe,EAE1C,MAAO,CACL,MAAO,KAAO,IAA4B,CACxC,IAAM,EAAsC,CAAC,EAE7C,GAAI,CACF,IAAK,IAAM,KAAW,EACpB,MAAM,EAAQ,MAAM,CAAO,EAC3B,EAAQ,KAAK,CAAO,CAExB,OAAS,EAAO,CACd,IAAM,EAAkC,CAAC,EAEzC,IAAK,IAAM,IAAW,CAAC,GAAG,CAAO,CAAC,CAAC,QAAQ,EACzC,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAmB,CAC1B,EAAqB,KAAK,CAAiB,CAC7C,CASF,MANI,EAAqB,OAAS,EACtB,eAAe,CAAC,EAAO,GAAG,CAAoB,EAAG,8CAA+C,CACxG,MAAO,CACT,CAAC,EAGG,CACR,CACF,EACA,QACA,OAAQ,KAAO,IAA4B,CACzC,IAAM,EAAsB,CAAC,EAE7B,IAAK,IAAM,IAAW,CAAC,GAAG,CAAK,CAAC,CAAC,QAAQ,EACvC,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAO,CACd,EAAS,KAAK,CAAK,CACrB,CAGF,GAAI,EAAS,OAAS,EAAG,MAAU,eAAe,EAAU,0BAA0B,CACxF,CACF,CACF,CCrEA,IAAa,YAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,qBAAb,cAA0C,WAAY,CAAC,EAG1C,oBAAb,cAAyC,WAAY,CAAC,EAGzC,kBAAb,cAAuC,WAAY,CAAC,ECa9C,WAAN,KAA+C,CAC7C,QACA,UAA6B,IAAI,IAEjC,YAAY,EAAY,CACtB,KAAK,QAAU,CACjB,CAEA,aAAiB,CACf,OAAO,KAAK,OACd,CAEA,IAAI,EAAe,CACjB,KAAK,QAAU,EAEf,IAAK,IAAM,IAAY,CAAC,GAAG,KAAK,SAAS,EACvC,GAAI,CACF,EAAS,CACX,OAAS,EAAO,CACd,mBAAqB,CACnB,MAAM,CACR,CAAC,CACH,CAEJ,CAEA,UAAU,EAAmC,CAE3C,OADA,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,CAC7C,CACF,EAEA,SAAS,UAAU,EAAwB,CACzC,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D,CAEA,SAAS,gBAAuB,EAAyD,CACvF,GAAM,CAAE,QAAO,QAAO,OAAM,UAAW,EAEvC,MAAO,CAAE,QAAO,MAAO,OAAO,OAAO,CAAE,QAAO,MAAK,CAAC,EAAG,QAAO,CAChE,CAEA,SAAS,cAAqB,EAA+C,CAC3E,OAAO,OAAO,OAAO,CACnB,GAAG,EACH,KAAM,OAAO,OAAO,CAAC,GAAG,EAAM,IAAI,CAAC,EACnC,KAAM,OAAO,OAAO,CAAC,GAAG,EAAM,IAAI,CAAC,CACrC,CAAC,CACH,CAEA,SAAS,eAAe,EAAgB,EAA+D,CACrG,OAAO,EACH,IAAI,oBAAoB,eAAe,EAAO,yBAAyB,EACvE,IAAI,qBAAqB,GAAG,EAAO,oCAAoC,CAC7E,CAkBA,SAAgB,aAAgC,EAAyB,CAAC,EAAkB,CAC1F,GAAM,CAAE,aAAa,KAAQ,EAE7B,GAAI,CAAC,OAAO,cAAc,CAAU,GAAK,EAAa,EACpD,MAAM,IAAI,kBAAkB,gDAAgD,EAG9E,IAAM,EAAQ,IAAI,WAA+B,cAAc,CAAE,OAAQ,EAAG,KAAM,CAAC,EAAG,QAAS,EAAG,KAAM,CAAC,CAAE,CAAC,CAAC,EACvG,EAAe,IAAI,QACnB,EAAqB,IAAI,gBACzB,EAAc,IAAI,IAClB,EAAa,IAAI,IACnB,EAAW,GACX,EAAQ,QAAQ,QAAQ,EAE5B,SAAS,YAAY,EAAmE,CAGtF,GAFA,EAAM,IAAI,cAAc,EAAO,EAAM,YAAY,CAAC,CAAC,CAAC,EAEhD,EAAM,YAAY,CAAC,CAAC,SAAW,GAAK,EAAM,YAAY,CAAC,CAAC,UAAY,EAAG,CACzE,IAAK,IAAM,KAAU,EAAa,EAAO,QAAQ,EACjD,EAAY,MAAM,CACpB,CACF,CAEA,SAAS,OAAO,EAAsB,EAAuB,CACvD,EAAU,UAEd,EAAU,QAAU,GACpB,EAAW,OAAO,CAAS,EAC3B,EAAU,OAAO,EAEb,IAAU,IAAA,GAAW,EAAU,QAAQ,EACtC,EAAU,OAAO,CAAK,EAC7B,CAEA,SAAS,QACP,EACA,EACA,EACe,CACf,GAAI,EAAU,OAAO,QAAQ,OAAO,eAAe,EAAQ,EAAI,CAAC,EAEhE,IAAM,EAAc,EAChB,YAAY,IAAI,CAAC,EAAgB,EAAmB,MAAM,CAAC,EAC3D,EAAmB,OAEvB,OAAO,IAAI,SAAe,EAAS,IAAW,CAC5C,IAAM,YAAsB,CACtB,EAAU,SAAW,EAAU,SAAW,IAE9C,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAQ,OAAS,CAAE,EAAE,EACrE,OAAO,EAAW,eAAe,EAAQ,EAAK,CAAC,EACjD,EAEM,EAAuB,CAC3B,WAAc,EAAY,oBAAoB,QAAS,OAAO,EAC9D,SACA,UACA,QAAS,GACT,MAAO,SAAY,CACb,MAAU,QAEd,IAAI,GAAY,EAAY,QAAS,CACnC,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAQ,OAAS,CAAE,EAAE,EACrE,OAAO,EAAW,eAAe,EAAQ,CAAQ,CAAC,EAElD,MACF,CAEA,EAAU,QAAU,GACpB,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAQ,OAAS,EAAG,QAAS,EAAQ,QAAU,CAAE,EAAE,EAEnG,GAAI,CACF,MAAM,EAAK,CAAE,OAAQ,CAAY,CAAC,EAClC,OAAO,CAAS,CAClB,OAAS,EAAO,CACd,OAAO,EAAW,CAAK,CACzB,QAAU,CACR,YAAa,IAAa,CAAE,GAAG,EAAS,QAAS,EAAQ,QAAU,CAAE,EAAE,CACzE,CAZA,CAaF,EACA,QAAS,EACX,EAEA,EAAY,iBAAiB,QAAS,QAAS,CAAE,KAAM,EAAK,CAAC,EAC7D,EAAW,IAAI,CAAS,EACxB,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAQ,OAAS,CAAE,EAAE,EACrE,EAAQ,EAAM,KAAK,EAAU,MAAO,EAAU,KAAK,CACrD,CAAC,CACH,CAEA,eAAe,MAAM,EAA+B,EAAwC,CAC1F,GAAI,CACF,MAAM,EAAQ,MAAM,CAAO,CAC7B,OAAS,EAAO,CACd,MAAM,EAAQ,OAAO,QACjB,IAAI,qBAAqB,oCAAqC,CAAE,MAAO,CAAM,CAAC,EAC9E,IAAI,qBAAqB,UAAU,CAAK,EAAG,CAAE,MAAO,CAAM,CAAC,CACjE,CAEA,GAAI,GAAY,EAAQ,OAAO,QAC7B,MAAM,IAAI,qBAAqB,mCAAmC,EAGpE,YAAa,GAAY,CACvB,GAAI,IAAe,EAAG,MAAO,CAAE,GAAG,EAAS,KAAM,CAAC,CAAE,EAEpD,IAAM,EAAO,CAAC,GAAG,EAAQ,KAAM,EAAQ,KAAK,EAI5C,OAFI,EAAK,OAAS,GAAY,EAAK,MAAM,EAElC,CAAE,GAAG,EAAS,KAAM,CAAC,EAAG,MAAK,CACtC,CAAC,EACD,EAAa,IAAI,EAAQ,MAAO,CAAO,CACzC,CAEA,eAAe,QAAQ,EAAwC,CAC7D,IAAM,EAAQ,EAAM,YAAY,CAAC,CAAC,KAAK,EAAM,YAAY,CAAC,CAAC,KAAK,OAAS,GAEzE,GAAI,CAAC,EAAO,OAEZ,IAAM,EAAU,EAAa,IAAI,CAAK,EAEtC,GAAI,CAAC,EAAS,MAAM,IAAI,YAAY,4BAA4B,EAEhE,GAAI,CACF,MAAM,EAAQ,OAAO,CAAO,CAC9B,OAAS,EAAO,CACd,MAAM,EAAQ,OAAO,QACjB,IAAI,qBAAqB,sCAAuC,CAAE,MAAO,CAAM,CAAC,EAChF,IAAI,oBAAoB,UAAU,CAAK,EAAG,CAAE,MAAO,CAAM,CAAC,CAChE,CAEA,GAAI,GAAY,EAAQ,OAAO,QAC7B,MAAM,IAAI,qBAAqB,qCAAqC,EAGtE,YAAa,IAAa,CAAE,GAAG,EAAS,KAAM,CAAC,GAAG,EAAQ,KAAM,CAAK,EAAG,KAAM,EAAQ,KAAK,MAAM,EAAG,EAAE,CAAE,EAAE,CAC5G,CAEA,eAAe,QAAQ,EAAwC,CAC7D,IAAM,EAAQ,EAAM,YAAY,CAAC,CAAC,KAAK,EAAM,YAAY,CAAC,CAAC,KAAK,OAAS,GAEzE,GAAI,CAAC,EAAO,OAEZ,IAAM,EAAU,EAAa,IAAI,CAAK,EAEtC,GAAI,CAAC,EAAS,MAAM,IAAI,YAAY,4BAA4B,EAEhE,GAAI,CACF,MAAM,EAAQ,MAAM,CAAO,CAC7B,OAAS,EAAO,CACd,MAAM,EAAQ,OAAO,QACjB,IAAI,qBAAqB,sCAAuC,CAAE,MAAO,CAAM,CAAC,EAChF,IAAI,qBAAqB,UAAU,CAAK,EAAG,CAAE,MAAO,CAAM,CAAC,CACjE,CAEA,GAAI,GAAY,EAAQ,OAAO,QAC7B,MAAM,IAAI,qBAAqB,qCAAqC,EAGtE,YAAa,IAAa,CAAE,GAAG,EAAS,KAAM,EAAQ,KAAK,MAAM,EAAG,EAAE,EAAG,KAAM,CAAC,GAAG,EAAQ,KAAM,CAAK,CAAE,EAAE,CAC5G,CAEA,MAAO,CACL,OAAuB,CAKrB,OAJI,EAAM,YAAY,CAAC,CAAC,KAAK,SAAW,GAAK,EAAM,YAAY,CAAC,CAAC,KAAK,SAAW,EACxE,EAAW,QAAQ,OAAO,eAAe,QAAS,EAAI,CAAC,EAAI,QAAQ,QAAQ,EAG7E,QAAQ,QAAS,IAAA,GAAW,SAAY,CAC7C,YAAa,IAAa,CAAE,GAAG,EAAS,KAAM,CAAC,EAAG,KAAM,CAAC,CAAE,EAAE,CAC/D,CAAC,CACH,EAEA,IAAI,gBAA8B,CAChC,OAAO,EAAmB,MAC5B,EAEA,SAAgB,CACd,GAAI,EAAU,OAEd,EAAW,GACX,EAAmB,MAAM,EAEzB,IAAM,EAAS,CAAC,GAAG,CAAU,CAAC,CAAC,OAAQ,GAAc,CAAC,EAAU,OAAO,EAEvE,IAAK,IAAM,KAAa,EAAQ,OAAO,EAAW,eAAe,YAAa,EAAI,CAAC,EAEnF,IAAK,IAAM,KAAU,EAAa,EAAO,OAAO,eAAe,WAAY,EAAI,CAAC,EAChF,EAAY,MAAM,EAElB,YAAa,IAAa,CAAE,GAAG,EAAS,OAAQ,EAAG,KAAM,CAAC,EAAG,KAAM,CAAC,CAAE,EAAE,CAC1E,EAEA,IAAI,UAAoB,CACtB,OAAO,CACT,EAEA,GAAG,EAAmC,EAAgD,CACpF,IAAM,EAAW,gBAAgB,CAAO,EAExC,OAAO,QAAQ,KAAM,GAAa,OAAS,GAAY,MAAM,EAAU,CAAO,CAAC,CACjF,EAEA,OAAO,EAAyC,CAC9C,GAAI,EAAU,MAAM,IAAI,oBAAoB,4CAA4C,EAExF,IAAM,EAAW,gBAAgB,CAAO,EACxC,YAAa,GAAY,CACvB,GAAI,IAAe,EAAG,MAAO,CAAE,GAAG,EAAS,KAAM,CAAC,CAAE,EAEpD,IAAM,EAAO,CAAC,GAAG,EAAQ,KAAM,EAAS,KAAK,EAI7C,OAFI,EAAK,OAAS,GAAY,EAAK,MAAM,EAElC,CAAE,GAAG,EAAS,KAAM,CAAC,EAAG,MAAK,CACtC,CAAC,EACD,EAAa,IAAI,EAAS,MAAO,CAAQ,CAC3C,EAEA,KAAK,EAAgD,CACnD,OAAO,QAAQ,OAAQ,GAAa,OAAQ,OAAO,CACrD,EAEA,IAAI,OAAQ,CACV,OAAO,CACT,EAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,EAEA,KAAK,EAAgD,CACnD,OAAO,QAAQ,OAAQ,GAAa,OAAQ,OAAO,CACrD,EAEA,UAA0B,CAKxB,OAJI,EAAM,YAAY,CAAC,CAAC,SAAW,GAAK,EAAM,YAAY,CAAC,CAAC,UAAY,EAC/D,EAAW,QAAQ,OAAO,eAAe,WAAY,EAAI,CAAC,EAAI,QAAQ,QAAQ,EAGhF,IAAI,SAAe,EAAS,IAAW,EAAY,IAAI,CAAE,SAAQ,SAAQ,CAAC,CAAC,CACpF,CACF,CACF"}
1
+ {"version":3,"file":"ledger.js","names":["snapshotCommand","command","steps","commands","applied","context","error","compensationFailures","compensationError","failures","message","opts","initial","next","listener","state","disposed","method","options","maxHistory","commandStore","disposalController","idleWaiters","operations","queue","update","waiter","operation","abortSignal","externalSignal","current","task","undo","entry","queued","snapshot","callOptions"],"sources":["../src/compose.ts","../src/errors.ts","../src/ledger.ts"],"sourcesContent":["import type { CommandContext, ReversibleCommand } from './types';\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): ReversibleCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, label, meta, revert };\n}\n\n/**\n * Composes reversible commands into one reversible command.\n *\n * `apply` runs each child in order. `revert` runs children in reverse order.\n * A failed child application compensates completed children before rethrowing.\n *\n * @example\n * await ledger.do(compose([\n * { apply: () => { node.x = newX; }, revert: () => { node.x = oldX; } },\n * { apply: () => { node.y = newY; }, revert: () => { node.y = oldY; } },\n * ], 'Move node'));\n */\nexport function compose<TMeta = undefined>(\n commands: readonly ReversibleCommand<TMeta>[],\n label?: string,\n): ReversibleCommand<TMeta> {\n const steps = commands.map(snapshotCommand);\n\n return {\n apply: async (context: CommandContext) => {\n const applied: ReversibleCommand<TMeta>[] = [];\n\n try {\n for (const command of steps) {\n await command.apply(context);\n applied.push(command);\n }\n } catch (error) {\n const compensationFailures: unknown[] = [];\n\n for (const command of [...applied].reverse()) {\n try {\n await command.revert(context);\n } catch (compensationError) {\n compensationFailures.push(compensationError);\n }\n }\n\n if (compensationFailures.length > 0) {\n throw new AggregateError([error, ...compensationFailures], 'Command application and compensation failed', {\n cause: error,\n });\n }\n\n throw error;\n }\n },\n label,\n revert: async (context: CommandContext) => {\n const failures: unknown[] = [];\n\n for (const command of [...steps].reverse()) {\n try {\n await command.revert(context);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) throw new AggregateError(failures, 'Command reversion failed');\n },\n };\n}\n","/** Base class for all ledger errors. Use `instanceof LedgerError` to catch any ledger-originated error. */\nexport class LedgerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a queued operation is cancelled before user code starts, or an active operation cooperatively stops. */\nexport class LedgerCancelledError extends LedgerError {}\n\n/** Thrown when a method is called on a disposed ledger instance. */\nexport class LedgerDisposedError extends LedgerError {}\n\n/** Thrown when a command's `apply()` function throws. The original error is available via `.cause`. */\nexport class LedgerExecutionError extends LedgerError {}\n\n/** Thrown when a command's `revert()` function throws during an undo operation. The original error is available via `.cause`. */\nexport class LedgerRollbackError extends LedgerError {}\n\n/** Thrown when the ledger is constructed with invalid options. */\nexport class LedgerConfigError extends LedgerError {}\n","import {\n LedgerCancelledError,\n LedgerConfigError,\n LedgerDisposedError,\n LedgerError,\n LedgerExecutionError,\n LedgerRollbackError,\n} from './errors';\nimport type {\n CommandContext,\n HistoryEntry,\n Ledger,\n LedgerCallOptions,\n LedgerOptions,\n LedgerState,\n ReversibleCommand,\n Subscribable,\n Unsubscribe,\n} from './types';\n\ntype StoredCommand<TMeta> = {\n apply: (context: CommandContext) => Promise<void> | void;\n entry: HistoryEntry<TMeta>;\n revert: (context: CommandContext) => Promise<void> | void;\n};\n\ntype Operation = {\n cancel: () => void;\n reject: (reason?: unknown) => void;\n resolve: () => void;\n settled: boolean;\n start: () => Promise<void>;\n started: boolean;\n};\n\nclass StateStore<T> implements Subscribable<T> {\n private current: T;\n private readonly listeners = new Set<() => void>();\n\n constructor(initial: T) {\n this.current = initial;\n }\n\n getSnapshot(): T {\n return this.current;\n }\n\n set(next: T): void {\n this.current = next;\n\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n subscribe(listener: () => void): Unsubscribe {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n\nfunction toMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction snapshotCommand<TMeta>(command: ReversibleCommand<TMeta>): StoredCommand<TMeta> {\n const { apply, label, meta, revert } = command;\n\n return { apply, entry: Object.freeze({ label, meta }), revert };\n}\n\nfunction snapshotState<TMeta>(state: LedgerState<TMeta>): LedgerState<TMeta> {\n return Object.freeze({\n ...state,\n redo: Object.freeze([...state.redo]),\n undo: Object.freeze([...state.undo]),\n });\n}\n\nfunction operationError(method: string, disposed: boolean): LedgerCancelledError | LedgerDisposedError {\n return disposed\n ? new LedgerDisposedError(`Cannot call ${method}() on a disposed ledger.`)\n : new LedgerCancelledError(`${method}() was cancelled before it started.`);\n}\n\n/**\n * Creates a serialized history of reversible commands.\n *\n * Commands are snapshotted when submitted. Queued commands cancelled before their queue turn do\n * not invoke user code. Active commands receive an abort signal and stop cooperatively.\n *\n * @example\n * const ledger = createLedger({ maxHistory: 50 });\n * await ledger.do({\n * apply: () => { item.name = next; },\n * revert: () => { item.name = previous; },\n * });\n * await ledger.undo();\n * await ledger.redo();\n * using ledger = createLedger();\n */\nexport function createLedger<TMeta = undefined>(options: LedgerOptions = {}): Ledger<TMeta> {\n const { maxHistory = 100 } = options;\n\n if (!Number.isSafeInteger(maxHistory) || maxHistory < 0) {\n throw new LedgerConfigError('maxHistory must be a non-negative safe integer');\n }\n\n const state = new StateStore<LedgerState<TMeta>>(snapshotState({ queued: 0, redo: [], running: 0, undo: [] }));\n const commandStore = new WeakMap<HistoryEntry<TMeta>, StoredCommand<TMeta>>();\n const disposalController = new AbortController();\n const idleWaiters = new Set<{ readonly reject: (reason?: unknown) => void; readonly resolve: () => void }>();\n const operations = new Set<Operation>();\n let disposed = false;\n let queue = Promise.resolve();\n\n function updateState(update: (current: LedgerState<TMeta>) => LedgerState<TMeta>): void {\n state.set(snapshotState(update(state.getSnapshot())));\n\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n for (const waiter of idleWaiters) waiter.resolve();\n idleWaiters.clear();\n }\n }\n\n function settle(operation: Operation, error?: unknown): void {\n if (operation.settled) return;\n\n operation.settled = true;\n operations.delete(operation);\n operation.cancel();\n\n if (error === undefined) operation.resolve();\n else operation.reject(error);\n }\n\n function enqueue(\n method: string,\n externalSignal: AbortSignal | undefined,\n task: (context: CommandContext) => Promise<void>,\n ): Promise<void> {\n if (disposed) return Promise.reject(operationError(method, true));\n\n const abortSignal = externalSignal\n ? AbortSignal.any([externalSignal, disposalController.signal])\n : disposalController.signal;\n\n return new Promise<void>((resolve, reject) => {\n const onAbort = (): void => {\n if (operation.started || operation.settled || disposed) return;\n\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, false));\n };\n\n const operation: Operation = {\n cancel: () => abortSignal.removeEventListener('abort', onAbort),\n reject,\n resolve,\n settled: false,\n start: async () => {\n if (operation.settled) return;\n\n if (disposed || abortSignal.aborted) {\n updateState((current) => ({ ...current, queued: current.queued - 1 }));\n settle(operation, operationError(method, disposed));\n\n return;\n }\n\n operation.started = true;\n updateState((current) => ({ ...current, queued: current.queued - 1, running: current.running + 1 }));\n\n try {\n await task({ signal: abortSignal });\n settle(operation);\n } catch (error) {\n settle(operation, error);\n } finally {\n updateState((current) => ({ ...current, running: current.running - 1 }));\n }\n },\n started: false,\n };\n\n abortSignal.addEventListener('abort', onAbort, { once: true });\n operations.add(operation);\n updateState((current) => ({ ...current, queued: current.queued + 1 }));\n queue = queue.then(operation.start, operation.start);\n });\n }\n\n async function runDo(command: StoredCommand<TMeta>, context: CommandContext): Promise<void> {\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('do() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('do() was cancelled while running.');\n }\n\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, command.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(command.entry, command);\n }\n\n async function runUndo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().undo[state.getSnapshot().undo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Undo history is corrupted.');\n\n try {\n await command.revert(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('undo() was cancelled while running.', { cause: error })\n : new LedgerRollbackError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('undo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: [...current.redo, entry], undo: current.undo.slice(0, -1) }));\n }\n\n async function runRedo(context: CommandContext): Promise<void> {\n const entry = state.getSnapshot().redo[state.getSnapshot().redo.length - 1];\n\n if (!entry) return;\n\n const command = commandStore.get(entry);\n\n if (!command) throw new LedgerError('Redo history is corrupted.');\n\n try {\n await command.apply(context);\n } catch (error) {\n throw context.signal.aborted\n ? new LedgerCancelledError('redo() was cancelled while running.', { cause: error })\n : new LedgerExecutionError(toMessage(error), { cause: error });\n }\n\n if (disposed || context.signal.aborted) {\n throw new LedgerCancelledError('redo() was cancelled while running.');\n }\n\n updateState((current) => ({ ...current, redo: current.redo.slice(0, -1), undo: [...current.undo, entry] }));\n }\n\n return {\n clear(): Promise<void> {\n if (state.getSnapshot().undo.length === 0 && state.getSnapshot().redo.length === 0) {\n return disposed ? Promise.reject(operationError('clear', true)) : Promise.resolve();\n }\n\n return enqueue('clear', undefined, async () => {\n updateState((current) => ({ ...current, redo: [], undo: [] }));\n });\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n const queued = [...operations].filter((operation) => !operation.started);\n\n for (const operation of queued) settle(operation, operationError('operation', true));\n\n for (const waiter of idleWaiters) waiter.reject(operationError('whenIdle', true));\n idleWaiters.clear();\n\n updateState((current) => ({ ...current, queued: 0, redo: [], undo: [] }));\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n do(command: ReversibleCommand<TMeta>, callOptions?: LedgerCallOptions): Promise<void> {\n const snapshot = snapshotCommand(command);\n\n return enqueue('do', callOptions?.signal, (context) => runDo(snapshot, context));\n },\n\n record(command: ReversibleCommand<TMeta>): void {\n if (disposed) throw new LedgerDisposedError('Cannot call record() on a disposed ledger.');\n\n const snapshot = snapshotCommand(command);\n updateState((current) => {\n if (maxHistory === 0) return { ...current, redo: [] };\n\n const undo = [...current.undo, snapshot.entry];\n\n if (undo.length > maxHistory) undo.shift();\n\n return { ...current, redo: [], undo };\n });\n commandStore.set(snapshot.entry, snapshot);\n },\n\n redo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('redo', callOptions?.signal, runRedo);\n },\n\n get state() {\n return state;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n undo(callOptions?: LedgerCallOptions): Promise<void> {\n return enqueue('undo', callOptions?.signal, runUndo);\n },\n\n whenIdle(): Promise<void> {\n if (state.getSnapshot().queued === 0 && state.getSnapshot().running === 0) {\n return disposed ? Promise.reject(operationError('whenIdle', true)) : Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => idleWaiters.add({ reject, resolve }));\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/ledger",
3
- "version": "26.10.14",
3
+ "version": "26.10.16",
4
4
  "description": "Serialized reversible command history with atomic framework-neutral state and cancellation ownership",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,9 +39,9 @@
39
39
  "test": "vitest"
40
40
  },
41
41
  "devDependencies": {
42
- "@types/node": "^26.6.2",
42
+ "@types/node": "^26.6.5",
43
43
  "typescript": "^6.0.3",
44
- "vite": "^8.3.0",
45
- "vitest": "^5.0.1"
44
+ "vite": "^8.3.4",
45
+ "vitest": "^5.0.5"
46
46
  }
47
47
  }