@kronos-ts/kysely 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 { kyselyTransactionManager, type KyselyDatabaseLike, type KyselyTransaction, } from "./kysely-transaction-manager.js";
2
+ export { kyselyTokenStore, type KyselyDbLike, } from "./kysely-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,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,yBAAyB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { kyselyTransactionManager, } from "./kysely-transaction-manager.js";
2
+ export { kyselyTokenStore, } from "./kysely-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,wBAAwB,GAGzB,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,gBAAgB,GAEjB,MAAM,yBAAyB,CAAA"}
@@ -0,0 +1,30 @@
1
+ import type { TokenStore } from "@kronos-ts/messaging";
2
+ /**
3
+ * A Kysely database instance (or transaction) with query methods.
4
+ */
5
+ export interface KyselyDbLike {
6
+ selectFrom(table: string): any;
7
+ insertInto(table: string): any;
8
+ updateTable(table: string): any;
9
+ deleteFrom(table: string): any;
10
+ }
11
+ /**
12
+ * Creates a TokenStore backed by Kysely.
13
+ *
14
+ * Participates in the active transaction via `getActiveTransaction()`.
15
+ * Uses the `kronos_token_entries` table with snake_case column names.
16
+ *
17
+ * ```typescript
18
+ * import { kyselyTokenStore } from "@kronos-ts/kysely"
19
+ *
20
+ * // tokenStore wiring to a kronos() App is pending a typed `tokenStore` slot
21
+ * // (Phase 9). For now, construct the store and pass it directly to the
22
+ * // tracking processor that owns it:
23
+ * const tokenStore = kyselyTokenStore(db)
24
+ * ```
25
+ */
26
+ export declare function kyselyTokenStore(db: KyselyDbLike, options?: {
27
+ claimTimeoutMs?: number;
28
+ tableName?: string;
29
+ }): TokenStore;
30
+ //# sourceMappingURL=kysely-token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-token-store.d.ts","sourceRoot":"","sources":["../src/kysely-token-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,sBAAsB,CAAA;AAwCrE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAA;IAC9B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAA;IAC9B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAA;IAC/B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAA;CAC/B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,YAAY,EAChB,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,UAAU,CA2HZ"}
@@ -0,0 +1,161 @@
1
+ import { getActiveTransaction, UnableToClaimTokenError, globalSequenceToken } from "@kronos-ts/messaging";
2
+ /**
3
+ * Token table interface for Kysely. Users must define this table in their
4
+ * Kysely database interface:
5
+ *
6
+ * ```typescript
7
+ * interface Database {
8
+ * kronos_token_entries: {
9
+ * processor_name: string
10
+ * segment: number
11
+ * mask: number
12
+ * token_type: string | null
13
+ * token: string | null
14
+ * timestamp: string | null
15
+ * owner: string | null
16
+ * }
17
+ * }
18
+ * ```
19
+ */
20
+ function serializeToken(token) {
21
+ return {
22
+ token_type: "GlobalSequenceToken",
23
+ token: JSON.stringify({ position: token.position().toString() }),
24
+ };
25
+ }
26
+ function deserializeToken(tokenType, token) {
27
+ if (!token || !tokenType)
28
+ return undefined;
29
+ const data = JSON.parse(token);
30
+ return globalSequenceToken(BigInt(data.position));
31
+ }
32
+ function nowIso() {
33
+ return new Date().toISOString();
34
+ }
35
+ /**
36
+ * Creates a TokenStore backed by Kysely.
37
+ *
38
+ * Participates in the active transaction via `getActiveTransaction()`.
39
+ * Uses the `kronos_token_entries` table with snake_case column names.
40
+ *
41
+ * ```typescript
42
+ * import { kyselyTokenStore } from "@kronos-ts/kysely"
43
+ *
44
+ * // tokenStore wiring to a kronos() App is pending a typed `tokenStore` slot
45
+ * // (Phase 9). For now, construct the store and pass it directly to the
46
+ * // tracking processor that owns it:
47
+ * const tokenStore = kyselyTokenStore(db)
48
+ * ```
49
+ */
50
+ export function kyselyTokenStore(db, options) {
51
+ const claimTimeoutMs = options?.claimTimeoutMs ?? 10000;
52
+ const table = options?.tableName ?? "kronos_token_entries";
53
+ function getDb() {
54
+ return getActiveTransaction() ?? db;
55
+ }
56
+ return {
57
+ async store(processorName, segment, token) {
58
+ const d = getDb();
59
+ const { token_type, token: tokenData } = serializeToken(token);
60
+ // Upsert via onConflict
61
+ await d.insertInto(table)
62
+ .values({ processor_name: processorName, segment, mask: 0, token_type, token: tokenData, timestamp: nowIso(), owner: null })
63
+ .onConflict((oc) => oc.columns(["processor_name", "segment"]).doUpdateSet({ token_type, token: tokenData, timestamp: nowIso() }))
64
+ .execute();
65
+ },
66
+ async get(processorName, segment) {
67
+ const d = getDb();
68
+ const row = await d.selectFrom(table)
69
+ .selectAll()
70
+ .where("processor_name", "=", processorName)
71
+ .where("segment", "=", segment)
72
+ .executeTakeFirst();
73
+ if (!row)
74
+ return undefined;
75
+ return deserializeToken(row.token_type, row.token);
76
+ },
77
+ async initializeSegments(processorName, segmentCount) {
78
+ const d = getDb();
79
+ for (let i = 0; i < segmentCount; i++) {
80
+ await d.insertInto(table)
81
+ .values({ processor_name: processorName, segment: i, mask: 0, token_type: null, token: null, timestamp: null, owner: null })
82
+ .onConflict((oc) => oc.columns(["processor_name", "segment"]).doNothing())
83
+ .execute();
84
+ }
85
+ },
86
+ async claimToken(processorName, segment, ownerId) {
87
+ const d = getDb();
88
+ const row = await d.selectFrom(table)
89
+ .selectAll()
90
+ .where("processor_name", "=", processorName)
91
+ .where("segment", "=", segment)
92
+ .executeTakeFirst();
93
+ if (!row) {
94
+ await d.insertInto(table)
95
+ .values({ processor_name: processorName, segment, mask: 0, token_type: null, token: null, timestamp: nowIso(), owner: ownerId })
96
+ .execute();
97
+ return undefined;
98
+ }
99
+ const isExpired = !row.owner || !row.timestamp ||
100
+ (Date.now() - new Date(row.timestamp).getTime() > claimTimeoutMs);
101
+ if (row.owner === ownerId || isExpired) {
102
+ await d.updateTable(table)
103
+ .set({ owner: ownerId, timestamp: nowIso() })
104
+ .where("processor_name", "=", processorName)
105
+ .where("segment", "=", segment)
106
+ .execute();
107
+ return deserializeToken(row.token_type, row.token);
108
+ }
109
+ throw new UnableToClaimTokenError(processorName, segment);
110
+ },
111
+ async extendClaim(processorName, segment, ownerId) {
112
+ const d = getDb();
113
+ await d.updateTable(table)
114
+ .set({ timestamp: nowIso() })
115
+ .where("processor_name", "=", processorName)
116
+ .where("segment", "=", segment)
117
+ .where("owner", "=", ownerId)
118
+ .execute();
119
+ },
120
+ async releaseClaim(processorName, segment, ownerId) {
121
+ const d = getDb();
122
+ await d.updateTable(table)
123
+ .set({ owner: null, timestamp: null })
124
+ .where("processor_name", "=", processorName)
125
+ .where("segment", "=", segment)
126
+ .where("owner", "=", ownerId)
127
+ .execute();
128
+ },
129
+ async fetchSegments(processorName) {
130
+ const d = getDb();
131
+ const rows = await d.selectFrom(table)
132
+ .select("segment")
133
+ .where("processor_name", "=", processorName)
134
+ .orderBy("segment", "asc")
135
+ .execute();
136
+ return rows.map((r) => r.segment);
137
+ },
138
+ async fetchAvailableSegments(processorName) {
139
+ const d = getDb();
140
+ const cutoff = new Date(Date.now() - claimTimeoutMs).toISOString();
141
+ const rows = await d.selectFrom(table)
142
+ .select("segment")
143
+ .where("processor_name", "=", processorName)
144
+ .where((eb) => eb.or([
145
+ eb("owner", "is", null),
146
+ eb("timestamp", "<", cutoff),
147
+ ]))
148
+ .orderBy("segment", "asc")
149
+ .execute();
150
+ return rows.map((r) => r.segment);
151
+ },
152
+ async deleteToken(processorName, segment) {
153
+ const d = getDb();
154
+ await d.deleteFrom(table)
155
+ .where("processor_name", "=", processorName)
156
+ .where("segment", "=", segment)
157
+ .execute();
158
+ },
159
+ };
160
+ }
161
+ //# sourceMappingURL=kysely-token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-token-store.js","sourceRoot":"","sources":["../src/kysely-token-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { TransactionManager } from "@kronos-ts/messaging";
2
+ /**
3
+ * A Kysely database instance that supports transactions.
4
+ */
5
+ export interface KyselyDatabaseLike {
6
+ transaction(): {
7
+ execute<T>(fn: (trx: any) => Promise<T>): Promise<T>;
8
+ };
9
+ }
10
+ /**
11
+ * The Kysely transaction object.
12
+ */
13
+ export type KyselyTransaction = any;
14
+ /**
15
+ * Creates a TransactionManager for Kysely.
16
+ *
17
+ * Bridges Kysely's `db.transaction().execute(fn)` callback pattern
18
+ * to the framework's `begin/commit/rollback` lifecycle.
19
+ *
20
+ * ```typescript
21
+ * import { Kysely } from "kysely"
22
+ * import { kyselyTransactionManager } from "@kronos-ts/kysely"
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 = kyselyTransactionManager(db)
28
+ * ```
29
+ */
30
+ export declare function kyselyTransactionManager(db: KyselyDatabaseLike): TransactionManager<KyselyTransaction>;
31
+ //# sourceMappingURL=kysely-transaction-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-transaction-manager.d.ts","sourceRoot":"","sources":["../src/kysely-transaction-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,WAAW,IAAI;QAAE,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;KAAE,CAAA;CACxE;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAA;AAEnC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,kBAAkB,GACrB,kBAAkB,CAAC,iBAAiB,CAAC,CA2CvC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Creates a TransactionManager for Kysely.
3
+ *
4
+ * Bridges Kysely's `db.transaction().execute(fn)` callback pattern
5
+ * to the framework's `begin/commit/rollback` lifecycle.
6
+ *
7
+ * ```typescript
8
+ * import { Kysely } from "kysely"
9
+ * import { kyselyTransactionManager } from "@kronos-ts/kysely"
10
+ *
11
+ * // transactionManager wiring to a kronos() App is pending a typed
12
+ * // `transactionManager` slot (Phase 9). For now, construct the manager
13
+ * // and pass it directly into the unitOfWorkFactory composition:
14
+ * const txManager = kyselyTransactionManager(db)
15
+ * ```
16
+ */
17
+ export function kyselyTransactionManager(db) {
18
+ return {
19
+ async begin() {
20
+ let resolveTx;
21
+ let resolveCompletion;
22
+ let rejectCompletion;
23
+ const txReady = new Promise((resolve) => {
24
+ resolveTx = resolve;
25
+ });
26
+ const completionSignal = new Promise((resolve, reject) => {
27
+ resolveCompletion = resolve;
28
+ rejectCompletion = reject;
29
+ });
30
+ const txPromise = db.transaction().execute(async (trx) => {
31
+ resolveTx(trx);
32
+ await completionSignal;
33
+ });
34
+ const tx = await txReady;
35
+ tx.__kronos_commit = resolveCompletion;
36
+ tx.__kronos_rollback = rejectCompletion;
37
+ tx.__kronos_txPromise = txPromise;
38
+ return tx;
39
+ },
40
+ async commit(tx) {
41
+ const commit = tx.__kronos_commit;
42
+ const txPromise = tx.__kronos_txPromise;
43
+ commit();
44
+ await txPromise;
45
+ },
46
+ async rollback(tx) {
47
+ const rollback = tx.__kronos_rollback;
48
+ const txPromise = tx.__kronos_txPromise;
49
+ rollback(new Error("Transaction rolled back"));
50
+ try {
51
+ await txPromise;
52
+ }
53
+ catch { /* expected */ }
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=kysely-transaction-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-transaction-manager.js","sourceRoot":"","sources":["../src/kysely-transaction-manager.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,wBAAwB,CACtC,EAAsB;IAEtB,OAAO;QACL,KAAK,CAAC,KAAK;YACT,IAAI,SAA2C,CAAA;YAC/C,IAAI,iBAA8B,CAAA;YAClC,IAAI,gBAA2C,CAAA;YAE/C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,EAAE;gBACzD,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,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBACvD,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,EAAqB;YAChC,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,EAAqB;YAClC,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/kysely",
3
+ "version": "0.1.0",
4
+ "description": "Kysely 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/kysely#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/KronosDB/kronos-ts.git",
12
+ "directory": "packages/extensions/kysely"
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
+ "kysely"
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
+ "kysely": ">=0.27.0"
56
+ }
57
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export {
2
+ kyselyTransactionManager,
3
+ type KyselyDatabaseLike,
4
+ type KyselyTransaction,
5
+ } from "./kysely-transaction-manager.js"
6
+
7
+ export {
8
+ kyselyTokenStore,
9
+ type KyselyDbLike,
10
+ } from "./kysely-token-store.js"
@@ -0,0 +1,192 @@
1
+ import type { TokenStore, TrackingToken } from "@kronos-ts/messaging"
2
+ import { getActiveTransaction, UnableToClaimTokenError, globalSequenceToken } from "@kronos-ts/messaging"
3
+ import type { KyselyTransaction } from "./kysely-transaction-manager.js"
4
+
5
+ /**
6
+ * Token table interface for Kysely. Users must define this table in their
7
+ * Kysely database interface:
8
+ *
9
+ * ```typescript
10
+ * interface Database {
11
+ * kronos_token_entries: {
12
+ * processor_name: string
13
+ * segment: number
14
+ * mask: number
15
+ * token_type: string | null
16
+ * token: string | null
17
+ * timestamp: string | null
18
+ * owner: string | null
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+
24
+ function serializeToken(token: TrackingToken): { token_type: string; token: string } {
25
+ return {
26
+ token_type: "GlobalSequenceToken",
27
+ token: JSON.stringify({ position: token.position().toString() }),
28
+ }
29
+ }
30
+
31
+ function deserializeToken(tokenType: string | null, token: string | null): TrackingToken | undefined {
32
+ if (!token || !tokenType) return undefined
33
+ const data = JSON.parse(token)
34
+ return globalSequenceToken(BigInt(data.position))
35
+ }
36
+
37
+ function nowIso(): string {
38
+ return new Date().toISOString()
39
+ }
40
+
41
+ /**
42
+ * A Kysely database instance (or transaction) with query methods.
43
+ */
44
+ export interface KyselyDbLike {
45
+ selectFrom(table: string): any
46
+ insertInto(table: string): any
47
+ updateTable(table: string): any
48
+ deleteFrom(table: string): any
49
+ }
50
+
51
+ /**
52
+ * Creates a TokenStore backed by Kysely.
53
+ *
54
+ * Participates in the active transaction via `getActiveTransaction()`.
55
+ * Uses the `kronos_token_entries` table with snake_case column names.
56
+ *
57
+ * ```typescript
58
+ * import { kyselyTokenStore } from "@kronos-ts/kysely"
59
+ *
60
+ * // tokenStore wiring to a kronos() App is pending a typed `tokenStore` slot
61
+ * // (Phase 9). For now, construct the store and pass it directly to the
62
+ * // tracking processor that owns it:
63
+ * const tokenStore = kyselyTokenStore(db)
64
+ * ```
65
+ */
66
+ export function kyselyTokenStore(
67
+ db: KyselyDbLike,
68
+ options?: { claimTimeoutMs?: number; tableName?: string },
69
+ ): TokenStore {
70
+ const claimTimeoutMs = options?.claimTimeoutMs ?? 10000
71
+ const table = options?.tableName ?? "kronos_token_entries"
72
+
73
+ function getDb(): KyselyDbLike {
74
+ return getActiveTransaction<KyselyTransaction>() ?? db
75
+ }
76
+
77
+ return {
78
+ async store(processorName, segment, token) {
79
+ const d = getDb()
80
+ const { token_type, token: tokenData } = serializeToken(token)
81
+ // Upsert via onConflict
82
+ await d.insertInto(table)
83
+ .values({ processor_name: processorName, segment, mask: 0, token_type, token: tokenData, timestamp: nowIso(), owner: null })
84
+ .onConflict((oc: any) => oc.columns(["processor_name", "segment"]).doUpdateSet({ token_type, token: tokenData, timestamp: nowIso() }))
85
+ .execute()
86
+ },
87
+
88
+ async get(processorName, segment) {
89
+ const d = getDb()
90
+ const row = await d.selectFrom(table)
91
+ .selectAll()
92
+ .where("processor_name", "=", processorName)
93
+ .where("segment", "=", segment)
94
+ .executeTakeFirst()
95
+ if (!row) return undefined
96
+ return deserializeToken(row.token_type, row.token)
97
+ },
98
+
99
+ async initializeSegments(processorName, segmentCount) {
100
+ const d = getDb()
101
+ for (let i = 0; i < segmentCount; i++) {
102
+ await d.insertInto(table)
103
+ .values({ processor_name: processorName, segment: i, mask: 0, token_type: null, token: null, timestamp: null, owner: null })
104
+ .onConflict((oc: any) => oc.columns(["processor_name", "segment"]).doNothing())
105
+ .execute()
106
+ }
107
+ },
108
+
109
+ async claimToken(processorName, segment, ownerId) {
110
+ const d = getDb()
111
+ const row = await d.selectFrom(table)
112
+ .selectAll()
113
+ .where("processor_name", "=", processorName)
114
+ .where("segment", "=", segment)
115
+ .executeTakeFirst()
116
+
117
+ if (!row) {
118
+ await d.insertInto(table)
119
+ .values({ processor_name: processorName, segment, mask: 0, token_type: null, token: null, timestamp: nowIso(), owner: ownerId })
120
+ .execute()
121
+ return undefined
122
+ }
123
+
124
+ const isExpired = !row.owner || !row.timestamp ||
125
+ (Date.now() - new Date(row.timestamp).getTime() > claimTimeoutMs)
126
+
127
+ if (row.owner === ownerId || isExpired) {
128
+ await d.updateTable(table)
129
+ .set({ owner: ownerId, timestamp: nowIso() })
130
+ .where("processor_name", "=", processorName)
131
+ .where("segment", "=", segment)
132
+ .execute()
133
+ return deserializeToken(row.token_type, row.token)
134
+ }
135
+
136
+ throw new UnableToClaimTokenError(processorName, segment)
137
+ },
138
+
139
+ async extendClaim(processorName, segment, ownerId) {
140
+ const d = getDb()
141
+ await d.updateTable(table)
142
+ .set({ timestamp: nowIso() })
143
+ .where("processor_name", "=", processorName)
144
+ .where("segment", "=", segment)
145
+ .where("owner", "=", ownerId)
146
+ .execute()
147
+ },
148
+
149
+ async releaseClaim(processorName, segment, ownerId) {
150
+ const d = getDb()
151
+ await d.updateTable(table)
152
+ .set({ owner: null, timestamp: null })
153
+ .where("processor_name", "=", processorName)
154
+ .where("segment", "=", segment)
155
+ .where("owner", "=", ownerId)
156
+ .execute()
157
+ },
158
+
159
+ async fetchSegments(processorName) {
160
+ const d = getDb()
161
+ const rows = await d.selectFrom(table)
162
+ .select("segment")
163
+ .where("processor_name", "=", processorName)
164
+ .orderBy("segment", "asc")
165
+ .execute()
166
+ return rows.map((r: any) => r.segment)
167
+ },
168
+
169
+ async fetchAvailableSegments(processorName) {
170
+ const d = getDb()
171
+ const cutoff = new Date(Date.now() - claimTimeoutMs).toISOString()
172
+ const rows = await d.selectFrom(table)
173
+ .select("segment")
174
+ .where("processor_name", "=", processorName)
175
+ .where((eb: any) => eb.or([
176
+ eb("owner", "is", null),
177
+ eb("timestamp", "<", cutoff),
178
+ ]))
179
+ .orderBy("segment", "asc")
180
+ .execute()
181
+ return rows.map((r: any) => r.segment)
182
+ },
183
+
184
+ async deleteToken(processorName, segment) {
185
+ const d = getDb()
186
+ await d.deleteFrom(table)
187
+ .where("processor_name", "=", processorName)
188
+ .where("segment", "=", segment)
189
+ .execute()
190
+ },
191
+ }
192
+ }
@@ -0,0 +1,76 @@
1
+ import type { TransactionManager } from "@kronos-ts/messaging"
2
+
3
+ /**
4
+ * A Kysely database instance that supports transactions.
5
+ */
6
+ export interface KyselyDatabaseLike {
7
+ transaction(): { execute<T>(fn: (trx: any) => Promise<T>): Promise<T> }
8
+ }
9
+
10
+ /**
11
+ * The Kysely transaction object.
12
+ */
13
+ export type KyselyTransaction = any
14
+
15
+ /**
16
+ * Creates a TransactionManager for Kysely.
17
+ *
18
+ * Bridges Kysely's `db.transaction().execute(fn)` callback pattern
19
+ * to the framework's `begin/commit/rollback` lifecycle.
20
+ *
21
+ * ```typescript
22
+ * import { Kysely } from "kysely"
23
+ * import { kyselyTransactionManager } from "@kronos-ts/kysely"
24
+ *
25
+ * // transactionManager wiring to a kronos() App is pending a typed
26
+ * // `transactionManager` slot (Phase 9). For now, construct the manager
27
+ * // and pass it directly into the unitOfWorkFactory composition:
28
+ * const txManager = kyselyTransactionManager(db)
29
+ * ```
30
+ */
31
+ export function kyselyTransactionManager(
32
+ db: KyselyDatabaseLike,
33
+ ): TransactionManager<KyselyTransaction> {
34
+ return {
35
+ async begin(): Promise<KyselyTransaction> {
36
+ let resolveTx!: (tx: KyselyTransaction) => void
37
+ let resolveCompletion!: () => void
38
+ let rejectCompletion!: (error: unknown) => void
39
+
40
+ const txReady = new Promise<KyselyTransaction>((resolve) => {
41
+ resolveTx = resolve
42
+ })
43
+
44
+ const completionSignal = new Promise<void>((resolve, reject) => {
45
+ resolveCompletion = resolve
46
+ rejectCompletion = reject
47
+ })
48
+
49
+ const txPromise = db.transaction().execute(async (trx) => {
50
+ resolveTx(trx)
51
+ await completionSignal
52
+ })
53
+
54
+ const tx = await txReady
55
+ ;(tx as any).__kronos_commit = resolveCompletion
56
+ ;(tx as any).__kronos_rollback = rejectCompletion
57
+ ;(tx as any).__kronos_txPromise = txPromise
58
+
59
+ return tx
60
+ },
61
+
62
+ async commit(tx: KyselyTransaction): Promise<void> {
63
+ const commit = (tx as any).__kronos_commit as () => void
64
+ const txPromise = (tx as any).__kronos_txPromise as Promise<void>
65
+ commit()
66
+ await txPromise
67
+ },
68
+
69
+ async rollback(tx: KyselyTransaction): Promise<void> {
70
+ const rollback = (tx as any).__kronos_rollback as (error: unknown) => void
71
+ const txPromise = (tx as any).__kronos_txPromise as Promise<void>
72
+ rollback(new Error("Transaction rolled back"))
73
+ try { await txPromise } catch { /* expected */ }
74
+ },
75
+ }
76
+ }