@decaf-ts/core 0.26.5 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +87 -1
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.cjs.map +1 -1
  4. package/dist/core.js +1 -1
  5. package/dist/core.js.map +1 -1
  6. package/lib/cjs/index.cjs +3 -3
  7. package/lib/cjs/persistence/Adapter.cjs +9 -1
  8. package/lib/cjs/persistence/Adapter.cjs.map +1 -1
  9. package/lib/cjs/persistence/ContextLock.cjs +110 -61
  10. package/lib/cjs/persistence/ContextLock.cjs.map +1 -1
  11. package/lib/cjs/persistence/ObserverHandler.cjs +1 -1
  12. package/lib/cjs/persistence/ObserverHandler.cjs.map +1 -1
  13. package/lib/cjs/persistence/constants.cjs +1 -0
  14. package/lib/cjs/persistence/constants.cjs.map +1 -1
  15. package/lib/cjs/persistence/transactions.cjs +57 -24
  16. package/lib/cjs/persistence/transactions.cjs.map +1 -1
  17. package/lib/esm/index.js +3 -3
  18. package/lib/esm/persistence/Adapter.js +10 -2
  19. package/lib/esm/persistence/Adapter.js.map +1 -1
  20. package/lib/esm/persistence/ContextLock.js +108 -59
  21. package/lib/esm/persistence/ContextLock.js.map +1 -1
  22. package/lib/esm/persistence/ObserverHandler.js +1 -1
  23. package/lib/esm/persistence/ObserverHandler.js.map +1 -1
  24. package/lib/esm/persistence/constants.js +1 -0
  25. package/lib/esm/persistence/constants.js.map +1 -1
  26. package/lib/esm/persistence/transactions.js +55 -22
  27. package/lib/esm/persistence/transactions.js.map +1 -1
  28. package/lib/types/index.d.cts +3 -3
  29. package/lib/types/index.d.mts +3 -3
  30. package/lib/types/persistence/Adapter.d.cts +10 -2
  31. package/lib/types/persistence/Adapter.d.mts +10 -2
  32. package/lib/types/persistence/ContextLock.d.cts +72 -14
  33. package/lib/types/persistence/ContextLock.d.mts +72 -14
  34. package/lib/types/persistence/transactions.d.cts +20 -3
  35. package/lib/types/persistence/transactions.d.mts +20 -3
  36. package/lib/types/persistence/types.d.cts +9 -0
  37. package/lib/types/persistence/types.d.mts +9 -0
  38. package/package.json +1 -1
@@ -1,18 +1,76 @@
1
- import { Lock } from "@decaf-ts/transactional-decorators";
2
1
  import { type Adapter } from "./Adapter.d.cts";
3
- export declare class AdapterTransaction<A extends Adapter<any, any, any, any>> {
2
+ import { type Context } from "./Context.d.cts";
3
+ import { Lock } from "@decaf-ts/transactional-decorators";
4
+ /**
5
+ * @description Counting semaphore used by the default `ContextLock` to gate concurrent transactions
6
+ * @summary Simple FIFO semaphore: `acquire()` resolves immediately while permits remain, otherwise the
7
+ * caller is queued and resolved (without touching the permit count) the moment `release()` hands the
8
+ * permit directly to the next waiter. `SimpleConcurrencyLock.for(adapter, limit)` is the single,
9
+ * self-contained way to get the one gate shared by every transaction on that adapter - no extra state
10
+ * or methods live on `Adapter` itself for this.
11
+ * @class SimpleConcurrencyLock
12
+ */
13
+ export declare class SimpleConcurrencyLock extends Lock {
14
+ private static readonly registry;
15
+ /**
16
+ * @description Returns the one `SimpleConcurrencyLock` for this adapter, creating it on first use
17
+ * @summary `limit` only matters the first time it's called for a given adapter - the gate's capacity
18
+ * is fixed for the adapter's lifetime, the same way the adapter's own client/connection is.
19
+ */
20
+ static for(adapter: Adapter<any, any, any, any>, limit: number): SimpleConcurrencyLock;
21
+ private permits;
22
+ private readonly waiters;
23
+ private constructor();
24
+ acquire(ctx?: Context<any>): Promise<void>;
25
+ release(ctx?: Context<any>): void;
26
+ }
27
+ /**
28
+ * @description Per-adapter transaction lock
29
+ * @summary Default transaction boundary implementation stored on the Context by `@transactional`.
30
+ * Gated by the `maxConcurrentTransactions` flag (see `AdapterFlags`): `-1` (default) means no limit and
31
+ * `begin`/`commit`/`rollback` behave as a no-op; `0` disables transactions outright (every call throws);
32
+ * any positive number gates concurrent transactions through `SimpleConcurrencyLock.for(adapter, limit)`,
33
+ * the one counting semaphore shared by every transaction on that adapter, queuing callers until a slot
34
+ * frees up.
35
+ * Adapters with native transaction support (e.g. a SQL adapter wrapping BEGIN/COMMIT/ROLLBACK) override
36
+ * `Adapter.transactionLock()` to return a subclass with real `begin`/`commit`/`rollback` behavior - if that
37
+ * subclass does not call `super.begin()`/`super.commit()`/`super.rollback()`, `maxConcurrentTransactions`
38
+ * has no effect for it, since concurrency is then governed by the underlying database instead.
39
+ * `transactionLock()` always returns a *fresh* `ContextLock` per top-level transaction - it's the
40
+ * per-transaction handle (nesting `depth`, and for native adapters the actual exclusive connection/cursor),
41
+ * so it cannot be a singleton itself; only the concurrency gate it delegates to is shared.
42
+ * Nesting (reusing the same instance across nested `@transactional` calls, and deciding when to actually
43
+ * call `begin`/`commit`/`rollback`) is owned by the `@transactional` proxy via `depth`, not by this class.
44
+ * @class ContextLock
45
+ */
46
+ export declare class ContextLock<A extends Adapter<any, any, any, any> = Adapter<any, any, any, any>> {
4
47
  protected adapter: A;
48
+ /**
49
+ * @description Nesting depth, owned and mutated by the `@transactional` proxy
50
+ */
51
+ depth: number;
52
+ private semaphore?;
5
53
  constructor(adapter: A, ...args: any[]);
6
- begin(...args: any[]): Promise<void>;
7
- commit(...args: any[]): Promise<void>;
8
- rollback(...args: any[]): Promise<void>;
9
- }
10
- export declare class ContextLock extends Lock {
11
- protected adapterTransaction: AdapterTransaction<any>;
12
- private acquireCount;
13
- protected readonly lock: Lock;
14
- constructor(adapterTransaction: AdapterTransaction<any>);
15
- acquire(...args: any[]): Promise<void>;
16
- release(...args: any[]): Promise<void>;
17
- rollback(e: Error, ...args: any[]): Promise<void>;
54
+ /**
55
+ * @description Called once, by the outermost `@transactional` call
56
+ * @summary `context` already exists by the time this is called (the `@transactional` proxy always
57
+ * builds it before calling `begin`), so this routes it through `Adapter.logCtx()` with `allowCreate`
58
+ * left at its default `false` - there is nothing to create here, only the existing context (and its
59
+ * logger) to reuse. Passing `allowCreate: true` would be wrong: it skips the "reuse the context I was
60
+ * given" branch entirely and tries to build a new one through `Adapter.context()`, whose third
61
+ * positional parameter is reserved for a model constructor - the context would be misread as "model".
62
+ * @param {Context<any>} context - The context the transaction is starting under
63
+ */
64
+ begin(context: Context<any>): Promise<void>;
65
+ /**
66
+ * @description Called once, when the outermost `@transactional` call exits successfully
67
+ * @param {Context<any>} context - The context the transaction ran under
68
+ */
69
+ commit(context: Context<any>): Promise<void>;
70
+ /**
71
+ * @description Called once, by whichever call hits the error first. Ends the transaction outright
72
+ * @param {Error} err - The error that triggered the rollback
73
+ * @param {Context<any>} context - The context the transaction ran under
74
+ */
75
+ rollback(err: Error, context: Context<any>): Promise<void>;
18
76
  }
@@ -1,18 +1,76 @@
1
- import { Lock } from "@decaf-ts/transactional-decorators";
2
1
  import { type Adapter } from "./Adapter.d.mts";
3
- export declare class AdapterTransaction<A extends Adapter<any, any, any, any>> {
2
+ import { type Context } from "./Context.d.mts";
3
+ import { Lock } from "@decaf-ts/transactional-decorators";
4
+ /**
5
+ * @description Counting semaphore used by the default `ContextLock` to gate concurrent transactions
6
+ * @summary Simple FIFO semaphore: `acquire()` resolves immediately while permits remain, otherwise the
7
+ * caller is queued and resolved (without touching the permit count) the moment `release()` hands the
8
+ * permit directly to the next waiter. `SimpleConcurrencyLock.for(adapter, limit)` is the single,
9
+ * self-contained way to get the one gate shared by every transaction on that adapter - no extra state
10
+ * or methods live on `Adapter` itself for this.
11
+ * @class SimpleConcurrencyLock
12
+ */
13
+ export declare class SimpleConcurrencyLock extends Lock {
14
+ private static readonly registry;
15
+ /**
16
+ * @description Returns the one `SimpleConcurrencyLock` for this adapter, creating it on first use
17
+ * @summary `limit` only matters the first time it's called for a given adapter - the gate's capacity
18
+ * is fixed for the adapter's lifetime, the same way the adapter's own client/connection is.
19
+ */
20
+ static for(adapter: Adapter<any, any, any, any>, limit: number): SimpleConcurrencyLock;
21
+ private permits;
22
+ private readonly waiters;
23
+ private constructor();
24
+ acquire(ctx?: Context<any>): Promise<void>;
25
+ release(ctx?: Context<any>): void;
26
+ }
27
+ /**
28
+ * @description Per-adapter transaction lock
29
+ * @summary Default transaction boundary implementation stored on the Context by `@transactional`.
30
+ * Gated by the `maxConcurrentTransactions` flag (see `AdapterFlags`): `-1` (default) means no limit and
31
+ * `begin`/`commit`/`rollback` behave as a no-op; `0` disables transactions outright (every call throws);
32
+ * any positive number gates concurrent transactions through `SimpleConcurrencyLock.for(adapter, limit)`,
33
+ * the one counting semaphore shared by every transaction on that adapter, queuing callers until a slot
34
+ * frees up.
35
+ * Adapters with native transaction support (e.g. a SQL adapter wrapping BEGIN/COMMIT/ROLLBACK) override
36
+ * `Adapter.transactionLock()` to return a subclass with real `begin`/`commit`/`rollback` behavior - if that
37
+ * subclass does not call `super.begin()`/`super.commit()`/`super.rollback()`, `maxConcurrentTransactions`
38
+ * has no effect for it, since concurrency is then governed by the underlying database instead.
39
+ * `transactionLock()` always returns a *fresh* `ContextLock` per top-level transaction - it's the
40
+ * per-transaction handle (nesting `depth`, and for native adapters the actual exclusive connection/cursor),
41
+ * so it cannot be a singleton itself; only the concurrency gate it delegates to is shared.
42
+ * Nesting (reusing the same instance across nested `@transactional` calls, and deciding when to actually
43
+ * call `begin`/`commit`/`rollback`) is owned by the `@transactional` proxy via `depth`, not by this class.
44
+ * @class ContextLock
45
+ */
46
+ export declare class ContextLock<A extends Adapter<any, any, any, any> = Adapter<any, any, any, any>> {
4
47
  protected adapter: A;
48
+ /**
49
+ * @description Nesting depth, owned and mutated by the `@transactional` proxy
50
+ */
51
+ depth: number;
52
+ private semaphore?;
5
53
  constructor(adapter: A, ...args: any[]);
6
- begin(...args: any[]): Promise<void>;
7
- commit(...args: any[]): Promise<void>;
8
- rollback(...args: any[]): Promise<void>;
9
- }
10
- export declare class ContextLock extends Lock {
11
- protected adapterTransaction: AdapterTransaction<any>;
12
- private acquireCount;
13
- protected readonly lock: Lock;
14
- constructor(adapterTransaction: AdapterTransaction<any>);
15
- acquire(...args: any[]): Promise<void>;
16
- release(...args: any[]): Promise<void>;
17
- rollback(e: Error, ...args: any[]): Promise<void>;
54
+ /**
55
+ * @description Called once, by the outermost `@transactional` call
56
+ * @summary `context` already exists by the time this is called (the `@transactional` proxy always
57
+ * builds it before calling `begin`), so this routes it through `Adapter.logCtx()` with `allowCreate`
58
+ * left at its default `false` - there is nothing to create here, only the existing context (and its
59
+ * logger) to reuse. Passing `allowCreate: true` would be wrong: it skips the "reuse the context I was
60
+ * given" branch entirely and tries to build a new one through `Adapter.context()`, whose third
61
+ * positional parameter is reserved for a model constructor - the context would be misread as "model".
62
+ * @param {Context<any>} context - The context the transaction is starting under
63
+ */
64
+ begin(context: Context<any>): Promise<void>;
65
+ /**
66
+ * @description Called once, when the outermost `@transactional` call exits successfully
67
+ * @param {Context<any>} context - The context the transaction ran under
68
+ */
69
+ commit(context: Context<any>): Promise<void>;
70
+ /**
71
+ * @description Called once, by whichever call hits the error first. Ends the transaction outright
72
+ * @param {Error} err - The error that triggered the rollback
73
+ * @param {Context<any>} context - The context the transaction ran under
74
+ */
75
+ rollback(err: Error, context: Context<any>): Promise<void>;
18
76
  }
@@ -1,4 +1,21 @@
1
- import { Adapter } from "../persistence/Adapter.d.cts";
2
1
  import { ContextLock } from "../persistence/ContextLock.d.cts";
3
- export declare function getAdapterTransaction(obj: any, ...args: any[]): import(".").AdapterTransaction<Adapter<any, any, any, any>>;
4
- export declare function getContextLock(obj: any, ...args: any[]): ContextLock;
2
+ /**
3
+ * @description Resolves the transaction lock for a `@transactional`-decorated call
4
+ * @summary Finds the underlying adapter for the decorated object (Adapter, Repository, or
5
+ * ModelService) and asks it for a fresh `ContextLock` via `Adapter.transactionLock()`
6
+ */
7
+ export declare function resolveTransactionLock(obj: any, ...args: any[]): ContextLock;
8
+ /**
9
+ * @description Method decorator that wraps a method in core's transaction-lock mechanism
10
+ * @summary `@decaf-ts/transactional-decorators` exports its own `transactional()` factory, and that
11
+ * factory re-registers its own (base) decorator under the same Decoration key every time it is called
12
+ * — so importing core does not make core's implementation "stick" if anything also calls the base
13
+ * package's factory. Consumers that want core's `ContextLock`/per-adapter transaction-lock behavior
14
+ * MUST import `transactional` from `@decaf-ts/core` (this function), not from
15
+ * `@decaf-ts/transactional-decorators`. Whichever factory is called last determines the active
16
+ * implementation for the shared key/flavour going forward.
17
+ * @param {...any[]} data - Optional metadata available to the transaction-lock implementation
18
+ * @function transactional
19
+ * @category Decorators
20
+ */
21
+ export declare function transactional(...data: any[]): (target: any, propertyKey?: any, descriptor?: TypedPropertyDescriptor<any>) => any;
@@ -1,4 +1,21 @@
1
- import { Adapter } from "../persistence/Adapter.d.mts";
2
1
  import { ContextLock } from "../persistence/ContextLock.d.mts";
3
- export declare function getAdapterTransaction(obj: any, ...args: any[]): import(".").AdapterTransaction<Adapter<any, any, any, any>>;
4
- export declare function getContextLock(obj: any, ...args: any[]): ContextLock;
2
+ /**
3
+ * @description Resolves the transaction lock for a `@transactional`-decorated call
4
+ * @summary Finds the underlying adapter for the decorated object (Adapter, Repository, or
5
+ * ModelService) and asks it for a fresh `ContextLock` via `Adapter.transactionLock()`
6
+ */
7
+ export declare function resolveTransactionLock(obj: any, ...args: any[]): ContextLock;
8
+ /**
9
+ * @description Method decorator that wraps a method in core's transaction-lock mechanism
10
+ * @summary `@decaf-ts/transactional-decorators` exports its own `transactional()` factory, and that
11
+ * factory re-registers its own (base) decorator under the same Decoration key every time it is called
12
+ * — so importing core does not make core's implementation "stick" if anything also calls the base
13
+ * package's factory. Consumers that want core's `ContextLock`/per-adapter transaction-lock behavior
14
+ * MUST import `transactional` from `@decaf-ts/core` (this function), not from
15
+ * `@decaf-ts/transactional-decorators`. Whichever factory is called last determines the active
16
+ * implementation for the shared key/flavour going forward.
17
+ * @param {...any[]} data - Optional metadata available to the transaction-lock implementation
18
+ * @function transactional
19
+ * @category Decorators
20
+ */
21
+ export declare function transactional(...data: any[]): (target: any, propertyKey?: any, descriptor?: TypedPropertyDescriptor<any>) => any;
@@ -78,6 +78,15 @@ export type AdapterFlags<LOG extends Logger = Logger> = RepositoryFlags<LOG> & C
78
78
  paginateByBookmark: boolean;
79
79
  dryRun: boolean;
80
80
  lock?: ContextLock;
81
+ /**
82
+ * @description Maximum number of concurrent transactions the default `ContextLock` allows for this adapter
83
+ * @summary `-1` (default) means no limit - the default lock behaves as a no-op. `0` disables transactions
84
+ * entirely (every `@transactional()` call throws). Any positive number gates concurrent transactions through
85
+ * a counting semaphore, queuing callers until a slot frees up. Adapters with native transaction support
86
+ * (e.g. a SQL adapter) that fully override `ContextLock.begin()`/`commit()`/`rollback()` are not affected by
87
+ * this flag - concurrency there is governed by the underlying database instead.
88
+ */
89
+ maxConcurrentTransactions: number;
81
90
  };
82
91
  export type RawResult<R, D extends boolean> = D extends true ? R : {
83
92
  data: R;
@@ -78,6 +78,15 @@ export type AdapterFlags<LOG extends Logger = Logger> = RepositoryFlags<LOG> & C
78
78
  paginateByBookmark: boolean;
79
79
  dryRun: boolean;
80
80
  lock?: ContextLock;
81
+ /**
82
+ * @description Maximum number of concurrent transactions the default `ContextLock` allows for this adapter
83
+ * @summary `-1` (default) means no limit - the default lock behaves as a no-op. `0` disables transactions
84
+ * entirely (every `@transactional()` call throws). Any positive number gates concurrent transactions through
85
+ * a counting semaphore, queuing callers until a slot frees up. Adapters with native transaction support
86
+ * (e.g. a SQL adapter) that fully override `ContextLock.begin()`/`commit()`/`rollback()` are not affected by
87
+ * this flag - concurrency there is governed by the underlying database instead.
88
+ */
89
+ maxConcurrentTransactions: number;
81
90
  };
82
91
  export type RawResult<R, D extends boolean> = D extends true ? R : {
83
92
  data: R;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decaf-ts/core",
3
- "version": "0.26.5",
3
+ "version": "0.27.0",
4
4
  "description": "Core persistence module for the decaf framework",
5
5
  "type": "module",
6
6
  "exports": {