@kronos-ts/knex 0.1.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.
@@ -0,0 +1,3 @@
1
+ export { knexTransactionManager, type KnexInstanceLike, type KnexTransaction, } from "./knex-transaction-manager.js";
2
+ export { knexTokenStore, type KnexQueryable, } from "./knex-token-store.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EACL,cAAc,EACd,KAAK,aAAa,GACnB,MAAM,uBAAuB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { knexTransactionManager, } from "./knex-transaction-manager.js";
2
+ export { knexTokenStore, } from "./knex-token-store.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,GAGvB,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EACL,cAAc,GAEf,MAAM,uBAAuB,CAAA"}
@@ -0,0 +1,27 @@
1
+ import type { TokenStore } from "@kronos-ts/messaging";
2
+ /**
3
+ * A Knex instance or transaction with query builder methods.
4
+ */
5
+ export interface KnexQueryable {
6
+ (tableName: string): any;
7
+ raw(sql: string, ...bindings: any[]): any;
8
+ }
9
+ /**
10
+ * Creates a TokenStore backed by Knex.
11
+ *
12
+ * Participates in the active transaction via `getActiveTransaction()`.
13
+ *
14
+ * ```typescript
15
+ * import { knexTokenStore } from "@kronos-ts/knex"
16
+ *
17
+ * // tokenStore wiring to a kronos() App is pending a typed `tokenStore` slot
18
+ * // (Phase 9). For now, construct the store and pass it directly to the
19
+ * // tracking processor that owns it:
20
+ * const tokenStore = knexTokenStore(knex)
21
+ * ```
22
+ */
23
+ export declare function knexTokenStore(knex: KnexQueryable, options?: {
24
+ claimTimeoutMs?: number;
25
+ tableName?: string;
26
+ }): TokenStore;
27
+ //# sourceMappingURL=knex-token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knex-token-store.d.ts","sourceRoot":"","sources":["../src/knex-token-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,sBAAsB,CAAA;AAqBrE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,CAAA;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,CAAA;CAC1C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,aAAa,EACnB,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,UAAU,CA+GZ"}
@@ -0,0 +1,124 @@
1
+ import { getActiveTransaction, UnableToClaimTokenError, globalSequenceToken } from "@kronos-ts/messaging";
2
+ function serializeToken(token) {
3
+ return {
4
+ token_type: "GlobalSequenceToken",
5
+ token: JSON.stringify({ position: token.position().toString() }),
6
+ };
7
+ }
8
+ function deserializeToken(tokenType, token) {
9
+ if (!token || !tokenType)
10
+ return undefined;
11
+ const data = JSON.parse(token);
12
+ return globalSequenceToken(BigInt(data.position));
13
+ }
14
+ function nowIso() {
15
+ return new Date().toISOString();
16
+ }
17
+ /**
18
+ * Creates a TokenStore backed by Knex.
19
+ *
20
+ * Participates in the active transaction via `getActiveTransaction()`.
21
+ *
22
+ * ```typescript
23
+ * import { knexTokenStore } from "@kronos-ts/knex"
24
+ *
25
+ * // tokenStore wiring to a kronos() App is pending a typed `tokenStore` slot
26
+ * // (Phase 9). For now, construct the store and pass it directly to the
27
+ * // tracking processor that owns it:
28
+ * const tokenStore = knexTokenStore(knex)
29
+ * ```
30
+ */
31
+ export function knexTokenStore(knex, options) {
32
+ const claimTimeoutMs = options?.claimTimeoutMs ?? 10000;
33
+ const table = options?.tableName ?? "kronos_token_entries";
34
+ function getKnex() {
35
+ return getActiveTransaction() ?? knex;
36
+ }
37
+ return {
38
+ async store(processorName, segment, token) {
39
+ const k = getKnex();
40
+ const { token_type, token: tokenData } = serializeToken(token);
41
+ await k.raw(`INSERT INTO ?? (processor_name, segment, mask, token_type, token, timestamp, owner)
42
+ VALUES (?, ?, 0, ?, ?, ?, NULL)
43
+ ON CONFLICT (processor_name, segment) DO UPDATE SET token_type = ?, token = ?, timestamp = ?`, [table, processorName, segment, token_type, tokenData, nowIso(), token_type, tokenData, nowIso()]);
44
+ },
45
+ async get(processorName, segment) {
46
+ const k = getKnex();
47
+ const row = await k(table)
48
+ .where({ processor_name: processorName, segment })
49
+ .first();
50
+ if (!row)
51
+ return undefined;
52
+ return deserializeToken(row.token_type, row.token);
53
+ },
54
+ async initializeSegments(processorName, segmentCount) {
55
+ const k = getKnex();
56
+ for (let i = 0; i < segmentCount; i++) {
57
+ await k.raw(`INSERT INTO ?? (processor_name, segment, mask, token_type, token, timestamp, owner)
58
+ VALUES (?, ?, 0, NULL, NULL, NULL, NULL)
59
+ ON CONFLICT (processor_name, segment) DO NOTHING`, [table, processorName, i]);
60
+ }
61
+ },
62
+ async claimToken(processorName, segment, ownerId) {
63
+ const k = getKnex();
64
+ const row = await k(table)
65
+ .where({ processor_name: processorName, segment })
66
+ .first();
67
+ if (!row) {
68
+ await k(table).insert({
69
+ processor_name: processorName, segment, mask: 0,
70
+ token_type: null, token: null, timestamp: nowIso(), owner: ownerId,
71
+ });
72
+ return undefined;
73
+ }
74
+ const isExpired = !row.owner || !row.timestamp ||
75
+ (Date.now() - new Date(row.timestamp).getTime() > claimTimeoutMs);
76
+ if (row.owner === ownerId || isExpired) {
77
+ await k(table)
78
+ .where({ processor_name: processorName, segment })
79
+ .update({ owner: ownerId, timestamp: nowIso() });
80
+ return deserializeToken(row.token_type, row.token);
81
+ }
82
+ throw new UnableToClaimTokenError(processorName, segment);
83
+ },
84
+ async extendClaim(processorName, segment, ownerId) {
85
+ const k = getKnex();
86
+ await k(table)
87
+ .where({ processor_name: processorName, segment, owner: ownerId })
88
+ .update({ timestamp: nowIso() });
89
+ },
90
+ async releaseClaim(processorName, segment, ownerId) {
91
+ const k = getKnex();
92
+ await k(table)
93
+ .where({ processor_name: processorName, segment, owner: ownerId })
94
+ .update({ owner: null, timestamp: null });
95
+ },
96
+ async fetchSegments(processorName) {
97
+ const k = getKnex();
98
+ const rows = await k(table)
99
+ .where({ processor_name: processorName })
100
+ .select("segment")
101
+ .orderBy("segment", "asc");
102
+ return rows.map((r) => r.segment);
103
+ },
104
+ async fetchAvailableSegments(processorName) {
105
+ const k = getKnex();
106
+ const cutoff = new Date(Date.now() - claimTimeoutMs).toISOString();
107
+ const rows = await k(table)
108
+ .where({ processor_name: processorName })
109
+ .where(function () {
110
+ this.whereNull("owner").orWhere("timestamp", "<", cutoff);
111
+ })
112
+ .select("segment")
113
+ .orderBy("segment", "asc");
114
+ return rows.map((r) => r.segment);
115
+ },
116
+ async deleteToken(processorName, segment) {
117
+ const k = getKnex();
118
+ await k(table)
119
+ .where({ processor_name: processorName, segment })
120
+ .delete();
121
+ },
122
+ };
123
+ }
124
+ //# sourceMappingURL=knex-token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knex-token-store.js","sourceRoot":"","sources":["../src/knex-token-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { TransactionManager } from "@kronos-ts/messaging";
2
+ /**
3
+ * A Knex instance that supports transactions.
4
+ */
5
+ export interface KnexInstanceLike {
6
+ transaction<T>(fn: (trx: any) => Promise<T>): Promise<T>;
7
+ }
8
+ /**
9
+ * The Knex transaction object.
10
+ */
11
+ export type KnexTransaction = any;
12
+ /**
13
+ * Creates a TransactionManager for Knex.
14
+ *
15
+ * Bridges Knex's `knex.transaction(fn)` callback to the framework's
16
+ * `begin/commit/rollback` lifecycle.
17
+ *
18
+ * ```typescript
19
+ * import Knex from "knex"
20
+ * import { knexTransactionManager } from "@kronos-ts/knex"
21
+ *
22
+ * const knex = Knex({ client: "pg", connection: "..." })
23
+ *
24
+ * // transactionManager wiring to a kronos() App is pending a typed
25
+ * // `transactionManager` slot (Phase 9). For now, construct the manager
26
+ * // and pass it directly into the unitOfWorkFactory composition:
27
+ * const txManager = knexTransactionManager(knex)
28
+ * ```
29
+ */
30
+ export declare function knexTransactionManager(knex: KnexInstanceLike): TransactionManager<KnexTransaction>;
31
+ //# sourceMappingURL=knex-transaction-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knex-transaction-manager.d.ts","sourceRoot":"","sources":["../src/knex-transaction-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CACzD;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,GAAG,CAAA;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,gBAAgB,GACrB,kBAAkB,CAAC,eAAe,CAAC,CA2CrC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Creates a TransactionManager for Knex.
3
+ *
4
+ * Bridges Knex's `knex.transaction(fn)` callback to the framework's
5
+ * `begin/commit/rollback` lifecycle.
6
+ *
7
+ * ```typescript
8
+ * import Knex from "knex"
9
+ * import { knexTransactionManager } from "@kronos-ts/knex"
10
+ *
11
+ * const knex = Knex({ client: "pg", connection: "..." })
12
+ *
13
+ * // transactionManager wiring to a kronos() App is pending a typed
14
+ * // `transactionManager` slot (Phase 9). For now, construct the manager
15
+ * // and pass it directly into the unitOfWorkFactory composition:
16
+ * const txManager = knexTransactionManager(knex)
17
+ * ```
18
+ */
19
+ export function knexTransactionManager(knex) {
20
+ return {
21
+ async begin() {
22
+ let resolveTx;
23
+ let resolveCompletion;
24
+ let rejectCompletion;
25
+ const txReady = new Promise((resolve) => {
26
+ resolveTx = resolve;
27
+ });
28
+ const completionSignal = new Promise((resolve, reject) => {
29
+ resolveCompletion = resolve;
30
+ rejectCompletion = reject;
31
+ });
32
+ const txPromise = knex.transaction(async (trx) => {
33
+ resolveTx(trx);
34
+ await completionSignal;
35
+ });
36
+ const tx = await txReady;
37
+ tx.__kronos_commit = resolveCompletion;
38
+ tx.__kronos_rollback = rejectCompletion;
39
+ tx.__kronos_txPromise = txPromise;
40
+ return tx;
41
+ },
42
+ async commit(tx) {
43
+ const commit = tx.__kronos_commit;
44
+ const txPromise = tx.__kronos_txPromise;
45
+ commit();
46
+ await txPromise;
47
+ },
48
+ async rollback(tx) {
49
+ const rollback = tx.__kronos_rollback;
50
+ const txPromise = tx.__kronos_txPromise;
51
+ rollback(new Error("Transaction rolled back"));
52
+ try {
53
+ await txPromise;
54
+ }
55
+ catch { /* expected */ }
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=knex-transaction-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knex-transaction-manager.js","sourceRoot":"","sources":["../src/knex-transaction-manager.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAsB;IAEtB,OAAO;QACL,KAAK,CAAC,KAAK;YACT,IAAI,SAAyC,CAAA;YAC7C,IAAI,iBAA8B,CAAA;YAClC,IAAI,gBAA2C,CAAA;YAE/C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAkB,CAAC,OAAO,EAAE,EAAE;gBACvD,SAAS,GAAG,OAAO,CAAA;YACrB,CAAC,CAAC,CAAA;YAEF,MAAM,gBAAgB,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC7D,iBAAiB,GAAG,OAAO,CAAA;gBAC3B,gBAAgB,GAAG,MAAM,CAAA;YAC3B,CAAC,CAAC,CAAA;YAEF,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBAC/C,SAAS,CAAC,GAAG,CAAC,CAAA;gBACd,MAAM,gBAAgB,CAAA;YACxB,CAAC,CAAC,CAAA;YAEF,MAAM,EAAE,GAAG,MAAM,OAAO,CACvB;YAAC,EAAU,CAAC,eAAe,GAAG,iBAAiB,CAC/C;YAAC,EAAU,CAAC,iBAAiB,GAAG,gBAAgB,CAChD;YAAC,EAAU,CAAC,kBAAkB,GAAG,SAAS,CAAA;YAE3C,OAAO,EAAE,CAAA;QACX,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,EAAmB;YAC9B,MAAM,MAAM,GAAI,EAAU,CAAC,eAA6B,CAAA;YACxD,MAAM,SAAS,GAAI,EAAU,CAAC,kBAAmC,CAAA;YACjE,MAAM,EAAE,CAAA;YACR,MAAM,SAAS,CAAA;QACjB,CAAC;QAED,KAAK,CAAC,QAAQ,CAAC,EAAmB;YAChC,MAAM,QAAQ,GAAI,EAAU,CAAC,iBAA6C,CAAA;YAC1E,MAAM,SAAS,GAAI,EAAU,CAAC,kBAAmC,CAAA;YACjE,QAAQ,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAA;YAC9C,IAAI,CAAC;gBAAC,MAAM,SAAS,CAAA;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;QAClD,CAAC;KACF,CAAA;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@kronos-ts/knex",
3
+ "version": "0.1.0",
4
+ "description": "Knex extension for Kronos.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "author": "Theo Emanuelsson",
8
+ "homepage": "https://github.com/KronosDB/kronos-ts/tree/main/packages/extensions/knex#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/KronosDB/kronos-ts.git",
12
+ "directory": "packages/extensions/knex"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/KronosDB/kronos-ts/issues"
16
+ },
17
+ "keywords": [
18
+ "kronos",
19
+ "event-sourcing",
20
+ "cqrs",
21
+ "dcb",
22
+ "typescript",
23
+ "knex"
24
+ ],
25
+ "sideEffects": false,
26
+ "main": "src/index.ts",
27
+ "types": "src/index.ts",
28
+ "files": [
29
+ "dist",
30
+ "src",
31
+ "!src/**/__tests__",
32
+ "!src/**/*.test.ts",
33
+ "!src/**/*.bench.ts"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.json",
37
+ "clean": "rm -rf dist *.tsbuildinfo"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "main": "./dist/index.js",
42
+ "types": "./dist/index.d.ts",
43
+ "exports": {
44
+ ".": {
45
+ "types": "./dist/index.d.ts",
46
+ "default": "./dist/index.js"
47
+ }
48
+ }
49
+ },
50
+ "dependencies": {
51
+ "@kronos-ts/common": "workspace:*",
52
+ "@kronos-ts/messaging": "workspace:*"
53
+ },
54
+ "peerDependencies": {
55
+ "knex": ">=3.0.0"
56
+ }
57
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export {
2
+ knexTransactionManager,
3
+ type KnexInstanceLike,
4
+ type KnexTransaction,
5
+ } from "./knex-transaction-manager.js"
6
+
7
+ export {
8
+ knexTokenStore,
9
+ type KnexQueryable,
10
+ } from "./knex-token-store.js"
@@ -0,0 +1,158 @@
1
+ import type { TokenStore, TrackingToken } from "@kronos-ts/messaging"
2
+ import { getActiveTransaction, UnableToClaimTokenError, globalSequenceToken } from "@kronos-ts/messaging"
3
+ import type { KnexTransaction } from "./knex-transaction-manager.js"
4
+
5
+ function serializeToken(token: TrackingToken): { token_type: string; token: string } {
6
+ return {
7
+ token_type: "GlobalSequenceToken",
8
+ token: JSON.stringify({ position: token.position().toString() }),
9
+ }
10
+ }
11
+
12
+ function deserializeToken(tokenType: string | null, token: string | null): TrackingToken | undefined {
13
+ if (!token || !tokenType) return undefined
14
+ const data = JSON.parse(token)
15
+ return globalSequenceToken(BigInt(data.position))
16
+ }
17
+
18
+ function nowIso(): string {
19
+ return new Date().toISOString()
20
+ }
21
+
22
+ /**
23
+ * A Knex instance or transaction with query builder methods.
24
+ */
25
+ export interface KnexQueryable {
26
+ (tableName: string): any
27
+ raw(sql: string, ...bindings: any[]): any
28
+ }
29
+
30
+ /**
31
+ * Creates a TokenStore backed by Knex.
32
+ *
33
+ * Participates in the active transaction via `getActiveTransaction()`.
34
+ *
35
+ * ```typescript
36
+ * import { knexTokenStore } from "@kronos-ts/knex"
37
+ *
38
+ * // tokenStore wiring to a kronos() App is pending a typed `tokenStore` slot
39
+ * // (Phase 9). For now, construct the store and pass it directly to the
40
+ * // tracking processor that owns it:
41
+ * const tokenStore = knexTokenStore(knex)
42
+ * ```
43
+ */
44
+ export function knexTokenStore(
45
+ knex: KnexQueryable,
46
+ options?: { claimTimeoutMs?: number; tableName?: string },
47
+ ): TokenStore {
48
+ const claimTimeoutMs = options?.claimTimeoutMs ?? 10000
49
+ const table = options?.tableName ?? "kronos_token_entries"
50
+
51
+ function getKnex(): KnexQueryable {
52
+ return getActiveTransaction<KnexTransaction>() ?? knex
53
+ }
54
+
55
+ return {
56
+ async store(processorName, segment, token) {
57
+ const k = getKnex()
58
+ const { token_type, token: tokenData } = serializeToken(token)
59
+ await k.raw(
60
+ `INSERT INTO ?? (processor_name, segment, mask, token_type, token, timestamp, owner)
61
+ VALUES (?, ?, 0, ?, ?, ?, NULL)
62
+ ON CONFLICT (processor_name, segment) DO UPDATE SET token_type = ?, token = ?, timestamp = ?`,
63
+ [table, processorName, segment, token_type, tokenData, nowIso(), token_type, tokenData, nowIso()],
64
+ )
65
+ },
66
+
67
+ async get(processorName, segment) {
68
+ const k = getKnex()
69
+ const row = await k(table)
70
+ .where({ processor_name: processorName, segment })
71
+ .first()
72
+ if (!row) return undefined
73
+ return deserializeToken(row.token_type, row.token)
74
+ },
75
+
76
+ async initializeSegments(processorName, segmentCount) {
77
+ const k = getKnex()
78
+ for (let i = 0; i < segmentCount; i++) {
79
+ await k.raw(
80
+ `INSERT INTO ?? (processor_name, segment, mask, token_type, token, timestamp, owner)
81
+ VALUES (?, ?, 0, NULL, NULL, NULL, NULL)
82
+ ON CONFLICT (processor_name, segment) DO NOTHING`,
83
+ [table, processorName, i],
84
+ )
85
+ }
86
+ },
87
+
88
+ async claimToken(processorName, segment, ownerId) {
89
+ const k = getKnex()
90
+ const row = await k(table)
91
+ .where({ processor_name: processorName, segment })
92
+ .first()
93
+
94
+ if (!row) {
95
+ await k(table).insert({
96
+ processor_name: processorName, segment, mask: 0,
97
+ token_type: null, token: null, timestamp: nowIso(), owner: ownerId,
98
+ })
99
+ return undefined
100
+ }
101
+
102
+ const isExpired = !row.owner || !row.timestamp ||
103
+ (Date.now() - new Date(row.timestamp).getTime() > claimTimeoutMs)
104
+
105
+ if (row.owner === ownerId || isExpired) {
106
+ await k(table)
107
+ .where({ processor_name: processorName, segment })
108
+ .update({ owner: ownerId, timestamp: nowIso() })
109
+ return deserializeToken(row.token_type, row.token)
110
+ }
111
+
112
+ throw new UnableToClaimTokenError(processorName, segment)
113
+ },
114
+
115
+ async extendClaim(processorName, segment, ownerId) {
116
+ const k = getKnex()
117
+ await k(table)
118
+ .where({ processor_name: processorName, segment, owner: ownerId })
119
+ .update({ timestamp: nowIso() })
120
+ },
121
+
122
+ async releaseClaim(processorName, segment, ownerId) {
123
+ const k = getKnex()
124
+ await k(table)
125
+ .where({ processor_name: processorName, segment, owner: ownerId })
126
+ .update({ owner: null, timestamp: null })
127
+ },
128
+
129
+ async fetchSegments(processorName) {
130
+ const k = getKnex()
131
+ const rows = await k(table)
132
+ .where({ processor_name: processorName })
133
+ .select("segment")
134
+ .orderBy("segment", "asc")
135
+ return rows.map((r: any) => r.segment)
136
+ },
137
+
138
+ async fetchAvailableSegments(processorName) {
139
+ const k = getKnex()
140
+ const cutoff = new Date(Date.now() - claimTimeoutMs).toISOString()
141
+ const rows = await k(table)
142
+ .where({ processor_name: processorName })
143
+ .where(function (this: any) {
144
+ this.whereNull("owner").orWhere("timestamp", "<", cutoff)
145
+ })
146
+ .select("segment")
147
+ .orderBy("segment", "asc")
148
+ return rows.map((r: any) => r.segment)
149
+ },
150
+
151
+ async deleteToken(processorName, segment) {
152
+ const k = getKnex()
153
+ await k(table)
154
+ .where({ processor_name: processorName, segment })
155
+ .delete()
156
+ },
157
+ }
158
+ }
@@ -0,0 +1,78 @@
1
+ import type { TransactionManager } from "@kronos-ts/messaging"
2
+
3
+ /**
4
+ * A Knex instance that supports transactions.
5
+ */
6
+ export interface KnexInstanceLike {
7
+ transaction<T>(fn: (trx: any) => Promise<T>): Promise<T>
8
+ }
9
+
10
+ /**
11
+ * The Knex transaction object.
12
+ */
13
+ export type KnexTransaction = any
14
+
15
+ /**
16
+ * Creates a TransactionManager for Knex.
17
+ *
18
+ * Bridges Knex's `knex.transaction(fn)` callback to the framework's
19
+ * `begin/commit/rollback` lifecycle.
20
+ *
21
+ * ```typescript
22
+ * import Knex from "knex"
23
+ * import { knexTransactionManager } from "@kronos-ts/knex"
24
+ *
25
+ * const knex = Knex({ client: "pg", connection: "..." })
26
+ *
27
+ * // transactionManager wiring to a kronos() App is pending a typed
28
+ * // `transactionManager` slot (Phase 9). For now, construct the manager
29
+ * // and pass it directly into the unitOfWorkFactory composition:
30
+ * const txManager = knexTransactionManager(knex)
31
+ * ```
32
+ */
33
+ export function knexTransactionManager(
34
+ knex: KnexInstanceLike,
35
+ ): TransactionManager<KnexTransaction> {
36
+ return {
37
+ async begin(): Promise<KnexTransaction> {
38
+ let resolveTx!: (tx: KnexTransaction) => void
39
+ let resolveCompletion!: () => void
40
+ let rejectCompletion!: (error: unknown) => void
41
+
42
+ const txReady = new Promise<KnexTransaction>((resolve) => {
43
+ resolveTx = resolve
44
+ })
45
+
46
+ const completionSignal = new Promise<void>((resolve, reject) => {
47
+ resolveCompletion = resolve
48
+ rejectCompletion = reject
49
+ })
50
+
51
+ const txPromise = knex.transaction(async (trx) => {
52
+ resolveTx(trx)
53
+ await completionSignal
54
+ })
55
+
56
+ const tx = await txReady
57
+ ;(tx as any).__kronos_commit = resolveCompletion
58
+ ;(tx as any).__kronos_rollback = rejectCompletion
59
+ ;(tx as any).__kronos_txPromise = txPromise
60
+
61
+ return tx
62
+ },
63
+
64
+ async commit(tx: KnexTransaction): Promise<void> {
65
+ const commit = (tx as any).__kronos_commit as () => void
66
+ const txPromise = (tx as any).__kronos_txPromise as Promise<void>
67
+ commit()
68
+ await txPromise
69
+ },
70
+
71
+ async rollback(tx: KnexTransaction): Promise<void> {
72
+ const rollback = (tx as any).__kronos_rollback as (error: unknown) => void
73
+ const txPromise = (tx as any).__kronos_txPromise as Promise<void>
74
+ rollback(new Error("Transaction rolled back"))
75
+ try { await txPromise } catch { /* expected */ }
76
+ },
77
+ }
78
+ }