@kronos-ts/drizzle 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,62 @@
1
+ import type { TokenStore } from "@kronos-ts/messaging";
2
+ import type { DrizzleDatabaseLike } from "./drizzle-transaction-manager.js";
3
+ /**
4
+ * Token table configuration. Users pass their Drizzle table reference
5
+ * and the `eq`, `and`, `or`, `lt` operators from drizzle-orm.
6
+ *
7
+ * The table must match the Kronos `TokenEntry` schema:
8
+ *
9
+ * ```typescript
10
+ * import { pgTable, varchar, integer, primaryKey } from "drizzle-orm/pg-core"
11
+ *
12
+ * export const kronosTokenEntries = pgTable("kronos_token_entries", {
13
+ * processorName: varchar("processor_name", { length: 255 }).notNull(),
14
+ * segment: integer("segment").notNull(),
15
+ * mask: integer("mask").notNull().default(0),
16
+ * tokenType: varchar("token_type", { length: 255 }),
17
+ * token: varchar("token", { length: 10000 }),
18
+ * timestamp: varchar("timestamp", { length: 255 }),
19
+ * owner: varchar("owner", { length: 255 }),
20
+ * }, (table) => ({
21
+ * pk: primaryKey({ columns: [table.processorName, table.segment] }),
22
+ * }))
23
+ * ```
24
+ */
25
+ export interface DrizzleTokenStoreConfig {
26
+ /** The Drizzle database instance. */
27
+ db: DrizzleDatabaseLike;
28
+ /** The Drizzle table reference for `kronos_token_entries`. */
29
+ table: any;
30
+ /** Drizzle `eq` operator. */
31
+ eq: (column: any, value: any) => any;
32
+ /** Drizzle `and` operator. */
33
+ and: (...conditions: any[]) => any;
34
+ /** Drizzle `or` operator. */
35
+ or: (...conditions: any[]) => any;
36
+ /** Drizzle `lt` operator. */
37
+ lt: (column: any, value: any) => any;
38
+ /** Drizzle `isNull` operator. */
39
+ isNull: (column: any) => any;
40
+ /** Claim timeout in ms. Default: 10000. */
41
+ claimTimeoutMs?: number;
42
+ }
43
+ /**
44
+ * Creates a TokenStore backed by Drizzle ORM.
45
+ *
46
+ * Participates in the active transaction via `getActiveTransaction()`.
47
+ *
48
+ * ```typescript
49
+ * import { eq, and, or, lt, isNull } from "drizzle-orm"
50
+ * import { drizzleTokenStore } from "@kronos-ts/drizzle"
51
+ * import { kronosTokenEntries } from "./schema"
52
+ *
53
+ * // tokenStore wiring to a kronos() App is pending a typed `tokenStore` slot
54
+ * // (Phase 9). For now, construct the store and pass it directly to the
55
+ * // tracking processor that owns it:
56
+ * const tokenStore = drizzleTokenStore({
57
+ * db, table: kronosTokenEntries, eq, and, or, lt, isNull,
58
+ * })
59
+ * ```
60
+ */
61
+ export declare function drizzleTokenStore(config: DrizzleTokenStoreConfig): TokenStore;
62
+ //# sourceMappingURL=drizzle-token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drizzle-token-store.d.ts","sourceRoot":"","sources":["../src/drizzle-token-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,sBAAsB,CAAA;AAErE,OAAO,KAAK,EAAE,mBAAmB,EAAsB,MAAM,kCAAkC,CAAA;AAE/F;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,uBAAuB;IACtC,qCAAqC;IACrC,EAAE,EAAE,mBAAmB,CAAA;IACvB,8DAA8D;IAC9D,KAAK,EAAE,GAAG,CAAA;IACV,6BAA6B;IAC7B,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,GAAG,CAAA;IACpC,8BAA8B;IAC9B,GAAG,EAAE,CAAC,GAAG,UAAU,EAAE,GAAG,EAAE,KAAK,GAAG,CAAA;IAClC,6BAA6B;IAC7B,EAAE,EAAE,CAAC,GAAG,UAAU,EAAE,GAAG,EAAE,KAAK,GAAG,CAAA;IACjC,6BAA6B;IAC7B,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,GAAG,CAAA;IACpC,iCAAiC;IACjC,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,GAAG,CAAA;IAC5B,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAmBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,UAAU,CAiH7E"}
@@ -0,0 +1,125 @@
1
+ import { getActiveTransaction, UnableToClaimTokenError, globalSequenceToken } from "@kronos-ts/messaging";
2
+ function serializeToken(token) {
3
+ return {
4
+ tokenType: "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 Drizzle ORM.
19
+ *
20
+ * Participates in the active transaction via `getActiveTransaction()`.
21
+ *
22
+ * ```typescript
23
+ * import { eq, and, or, lt, isNull } from "drizzle-orm"
24
+ * import { drizzleTokenStore } from "@kronos-ts/drizzle"
25
+ * import { kronosTokenEntries } from "./schema"
26
+ *
27
+ * // tokenStore wiring to a kronos() App is pending a typed `tokenStore` slot
28
+ * // (Phase 9). For now, construct the store and pass it directly to the
29
+ * // tracking processor that owns it:
30
+ * const tokenStore = drizzleTokenStore({
31
+ * db, table: kronosTokenEntries, eq, and, or, lt, isNull,
32
+ * })
33
+ * ```
34
+ */
35
+ export function drizzleTokenStore(config) {
36
+ const { table, eq, and, or, lt, isNull } = config;
37
+ const claimTimeoutMs = config.claimTimeoutMs ?? 10000;
38
+ function getDb() {
39
+ return getActiveTransaction() ?? config.db;
40
+ }
41
+ return {
42
+ async store(processorName, segment, token) {
43
+ const db = getDb();
44
+ const { tokenType, token: tokenData } = serializeToken(token);
45
+ await db.insert(table)
46
+ .values({ processorName, segment, mask: 0, tokenType, token: tokenData, timestamp: nowIso(), owner: null })
47
+ .onConflictDoUpdate({
48
+ target: [table.processorName, table.segment],
49
+ set: { tokenType, token: tokenData, timestamp: nowIso() },
50
+ });
51
+ },
52
+ async get(processorName, segment) {
53
+ const db = getDb();
54
+ const rows = await db.select().from(table)
55
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment)))
56
+ .limit(1);
57
+ if (rows.length === 0)
58
+ return undefined;
59
+ return deserializeToken(rows[0].tokenType, rows[0].token);
60
+ },
61
+ async initializeSegments(processorName, segmentCount) {
62
+ const db = getDb();
63
+ for (let i = 0; i < segmentCount; i++) {
64
+ await db.insert(table)
65
+ .values({ processorName, segment: i, mask: 0, tokenType: null, token: null, timestamp: null, owner: null })
66
+ .onConflictDoNothing();
67
+ }
68
+ },
69
+ async claimToken(processorName, segment, ownerId) {
70
+ const db = getDb();
71
+ const rows = await db.select().from(table)
72
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment)))
73
+ .limit(1);
74
+ if (rows.length === 0) {
75
+ await db.insert(table).values({
76
+ processorName, segment, mask: 0, tokenType: null, token: null, timestamp: nowIso(), owner: ownerId,
77
+ });
78
+ return undefined;
79
+ }
80
+ const row = rows[0];
81
+ const isExpired = !row.owner || !row.timestamp ||
82
+ (Date.now() - new Date(row.timestamp).getTime() > claimTimeoutMs);
83
+ if (row.owner === ownerId || isExpired) {
84
+ await db.update(table)
85
+ .set({ owner: ownerId, timestamp: nowIso() })
86
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment)));
87
+ return deserializeToken(row.tokenType, row.token);
88
+ }
89
+ throw new UnableToClaimTokenError(processorName, segment);
90
+ },
91
+ async extendClaim(processorName, segment, ownerId) {
92
+ const db = getDb();
93
+ await db.update(table)
94
+ .set({ timestamp: nowIso() })
95
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment), eq(table.owner, ownerId)));
96
+ },
97
+ async releaseClaim(processorName, segment, ownerId) {
98
+ const db = getDb();
99
+ await db.update(table)
100
+ .set({ owner: null, timestamp: null })
101
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment), eq(table.owner, ownerId)));
102
+ },
103
+ async fetchSegments(processorName) {
104
+ const db = getDb();
105
+ const rows = await db.select({ segment: table.segment }).from(table)
106
+ .where(eq(table.processorName, processorName))
107
+ .orderBy(table.segment);
108
+ return rows.map((r) => r.segment);
109
+ },
110
+ async fetchAvailableSegments(processorName) {
111
+ const db = getDb();
112
+ const cutoff = new Date(Date.now() - claimTimeoutMs).toISOString();
113
+ const rows = await db.select({ segment: table.segment }).from(table)
114
+ .where(and(eq(table.processorName, processorName), or(isNull(table.owner), lt(table.timestamp, cutoff))))
115
+ .orderBy(table.segment);
116
+ return rows.map((r) => r.segment);
117
+ },
118
+ async deleteToken(processorName, segment) {
119
+ const db = getDb();
120
+ await db.delete(table)
121
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment)));
122
+ },
123
+ };
124
+ }
125
+ //# sourceMappingURL=drizzle-token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drizzle-token-store.js","sourceRoot":"","sources":["../src/drizzle-token-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { TransactionManager } from "@kronos-ts/messaging";
2
+ /**
3
+ * A Drizzle database instance that supports transactions.
4
+ * Works with any Drizzle driver (postgres-js, node-postgres, better-sqlite3, etc.).
5
+ */
6
+ export interface DrizzleDatabaseLike {
7
+ transaction<T>(fn: (tx: any) => Promise<T>): Promise<T>;
8
+ }
9
+ /**
10
+ * The Drizzle transaction object — has the same query API as the database.
11
+ */
12
+ export type DrizzleTransaction = any;
13
+ /**
14
+ * Creates a TransactionManager for Drizzle ORM.
15
+ *
16
+ * Drizzle's `db.transaction()` uses a callback pattern. This manager
17
+ * bridges it to the framework's `begin/commit/rollback` lifecycle
18
+ * using deferred promises — the same pattern as the Prisma extension.
19
+ *
20
+ * The `tx` is made available via `getActiveTransaction()` so projections
21
+ * and token stores participate in the same transaction.
22
+ *
23
+ * ```typescript
24
+ * import { drizzle } from "drizzle-orm/postgres-js"
25
+ * import { drizzleTransactionManager } from "@kronos-ts/drizzle"
26
+ *
27
+ * const db = drizzle(sql)
28
+ *
29
+ * // transactionManager wiring to a kronos() App is pending a typed
30
+ * // `transactionManager` slot (Phase 9). For now, construct the manager
31
+ * // and pass it directly into the unitOfWorkFactory composition:
32
+ * const txManager = drizzleTransactionManager(db)
33
+ * ```
34
+ */
35
+ export declare function drizzleTransactionManager(db: DrizzleDatabaseLike): TransactionManager<DrizzleTransaction>;
36
+ //# sourceMappingURL=drizzle-transaction-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drizzle-transaction-manager.d.ts","sourceRoot":"","sources":["../src/drizzle-transaction-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CACxD;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,GAAG,CAAA;AAEpC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,mBAAmB,GACtB,kBAAkB,CAAC,kBAAkB,CAAC,CA2CxC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Creates a TransactionManager for Drizzle ORM.
3
+ *
4
+ * Drizzle's `db.transaction()` uses a callback pattern. This manager
5
+ * bridges it to the framework's `begin/commit/rollback` lifecycle
6
+ * using deferred promises — the same pattern as the Prisma extension.
7
+ *
8
+ * The `tx` is made available via `getActiveTransaction()` so projections
9
+ * and token stores participate in the same transaction.
10
+ *
11
+ * ```typescript
12
+ * import { drizzle } from "drizzle-orm/postgres-js"
13
+ * import { drizzleTransactionManager } from "@kronos-ts/drizzle"
14
+ *
15
+ * const db = drizzle(sql)
16
+ *
17
+ * // transactionManager wiring to a kronos() App is pending a typed
18
+ * // `transactionManager` slot (Phase 9). For now, construct the manager
19
+ * // and pass it directly into the unitOfWorkFactory composition:
20
+ * const txManager = drizzleTransactionManager(db)
21
+ * ```
22
+ */
23
+ export function drizzleTransactionManager(db) {
24
+ return {
25
+ async begin() {
26
+ let resolveTx;
27
+ let resolveCompletion;
28
+ let rejectCompletion;
29
+ const txReady = new Promise((resolve) => {
30
+ resolveTx = resolve;
31
+ });
32
+ const completionSignal = new Promise((resolve, reject) => {
33
+ resolveCompletion = resolve;
34
+ rejectCompletion = reject;
35
+ });
36
+ const txPromise = db.transaction(async (tx) => {
37
+ resolveTx(tx);
38
+ await completionSignal;
39
+ });
40
+ const tx = await txReady;
41
+ tx.__kronos_commit = resolveCompletion;
42
+ tx.__kronos_rollback = rejectCompletion;
43
+ tx.__kronos_txPromise = txPromise;
44
+ return tx;
45
+ },
46
+ async commit(tx) {
47
+ const commit = tx.__kronos_commit;
48
+ const txPromise = tx.__kronos_txPromise;
49
+ commit();
50
+ await txPromise;
51
+ },
52
+ async rollback(tx) {
53
+ const rollback = tx.__kronos_rollback;
54
+ const txPromise = tx.__kronos_txPromise;
55
+ rollback(new Error("Transaction rolled back"));
56
+ try {
57
+ await txPromise;
58
+ }
59
+ catch { /* expected */ }
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=drizzle-transaction-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drizzle-transaction-manager.js","sourceRoot":"","sources":["../src/drizzle-transaction-manager.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,yBAAyB,CACvC,EAAuB;IAEvB,OAAO;QACL,KAAK,CAAC,KAAK;YACT,IAAI,SAA4C,CAAA;YAChD,IAAI,iBAA8B,CAAA;YAClC,IAAI,gBAA2C,CAAA;YAE/C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAqB,CAAC,OAAO,EAAE,EAAE;gBAC1D,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,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;gBAC5C,SAAS,CAAC,EAAE,CAAC,CAAA;gBACb,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,EAAsB;YACjC,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,EAAsB;YACnC,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"}
@@ -0,0 +1,3 @@
1
+ export { drizzleTransactionManager, type DrizzleDatabaseLike, type DrizzleTransaction, } from "./drizzle-transaction-manager.js";
2
+ export { drizzleTokenStore, type DrizzleTokenStoreConfig, } from "./drizzle-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,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,EACL,iBAAiB,EACjB,KAAK,uBAAuB,GAC7B,MAAM,0BAA0B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { drizzleTransactionManager, } from "./drizzle-transaction-manager.js";
2
+ export { drizzleTokenStore, } from "./drizzle-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,yBAAyB,GAG1B,MAAM,kCAAkC,CAAA;AAEzC,OAAO,EACL,iBAAiB,GAElB,MAAM,0BAA0B,CAAA"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@kronos-ts/drizzle",
3
+ "version": "0.1.0",
4
+ "description": "Drizzle ORM 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/drizzle#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/KronosDB/kronos-ts.git",
12
+ "directory": "packages/extensions/drizzle"
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
+ "drizzle",
24
+ "orm"
25
+ ],
26
+ "sideEffects": false,
27
+ "main": "src/index.ts",
28
+ "types": "src/index.ts",
29
+ "files": [
30
+ "dist",
31
+ "src",
32
+ "!src/**/__tests__",
33
+ "!src/**/*.test.ts",
34
+ "!src/**/*.bench.ts"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsc -p tsconfig.json",
38
+ "clean": "rm -rf dist *.tsbuildinfo"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "main": "./dist/index.js",
43
+ "types": "./dist/index.d.ts",
44
+ "exports": {
45
+ ".": {
46
+ "types": "./dist/index.d.ts",
47
+ "default": "./dist/index.js"
48
+ }
49
+ }
50
+ },
51
+ "dependencies": {
52
+ "@kronos-ts/common": "workspace:*",
53
+ "@kronos-ts/messaging": "workspace:*"
54
+ },
55
+ "peerDependencies": {
56
+ "drizzle-orm": ">=0.30.0"
57
+ }
58
+ }
@@ -0,0 +1,194 @@
1
+ import type { TokenStore, TrackingToken } from "@kronos-ts/messaging"
2
+ import { getActiveTransaction, UnableToClaimTokenError, globalSequenceToken } from "@kronos-ts/messaging"
3
+ import type { DrizzleDatabaseLike, DrizzleTransaction } from "./drizzle-transaction-manager.js"
4
+
5
+ /**
6
+ * Token table configuration. Users pass their Drizzle table reference
7
+ * and the `eq`, `and`, `or`, `lt` operators from drizzle-orm.
8
+ *
9
+ * The table must match the Kronos `TokenEntry` schema:
10
+ *
11
+ * ```typescript
12
+ * import { pgTable, varchar, integer, primaryKey } from "drizzle-orm/pg-core"
13
+ *
14
+ * export const kronosTokenEntries = pgTable("kronos_token_entries", {
15
+ * processorName: varchar("processor_name", { length: 255 }).notNull(),
16
+ * segment: integer("segment").notNull(),
17
+ * mask: integer("mask").notNull().default(0),
18
+ * tokenType: varchar("token_type", { length: 255 }),
19
+ * token: varchar("token", { length: 10000 }),
20
+ * timestamp: varchar("timestamp", { length: 255 }),
21
+ * owner: varchar("owner", { length: 255 }),
22
+ * }, (table) => ({
23
+ * pk: primaryKey({ columns: [table.processorName, table.segment] }),
24
+ * }))
25
+ * ```
26
+ */
27
+ export interface DrizzleTokenStoreConfig {
28
+ /** The Drizzle database instance. */
29
+ db: DrizzleDatabaseLike
30
+ /** The Drizzle table reference for `kronos_token_entries`. */
31
+ table: any
32
+ /** Drizzle `eq` operator. */
33
+ eq: (column: any, value: any) => any
34
+ /** Drizzle `and` operator. */
35
+ and: (...conditions: any[]) => any
36
+ /** Drizzle `or` operator. */
37
+ or: (...conditions: any[]) => any
38
+ /** Drizzle `lt` operator. */
39
+ lt: (column: any, value: any) => any
40
+ /** Drizzle `isNull` operator. */
41
+ isNull: (column: any) => any
42
+ /** Claim timeout in ms. Default: 10000. */
43
+ claimTimeoutMs?: number
44
+ }
45
+
46
+ function serializeToken(token: TrackingToken): { tokenType: string; token: string } {
47
+ return {
48
+ tokenType: "GlobalSequenceToken",
49
+ token: JSON.stringify({ position: token.position().toString() }),
50
+ }
51
+ }
52
+
53
+ function deserializeToken(tokenType: string | null, token: string | null): TrackingToken | undefined {
54
+ if (!token || !tokenType) return undefined
55
+ const data = JSON.parse(token)
56
+ return globalSequenceToken(BigInt(data.position))
57
+ }
58
+
59
+ function nowIso(): string {
60
+ return new Date().toISOString()
61
+ }
62
+
63
+ /**
64
+ * Creates a TokenStore backed by Drizzle ORM.
65
+ *
66
+ * Participates in the active transaction via `getActiveTransaction()`.
67
+ *
68
+ * ```typescript
69
+ * import { eq, and, or, lt, isNull } from "drizzle-orm"
70
+ * import { drizzleTokenStore } from "@kronos-ts/drizzle"
71
+ * import { kronosTokenEntries } from "./schema"
72
+ *
73
+ * // tokenStore wiring to a kronos() App is pending a typed `tokenStore` slot
74
+ * // (Phase 9). For now, construct the store and pass it directly to the
75
+ * // tracking processor that owns it:
76
+ * const tokenStore = drizzleTokenStore({
77
+ * db, table: kronosTokenEntries, eq, and, or, lt, isNull,
78
+ * })
79
+ * ```
80
+ */
81
+ export function drizzleTokenStore(config: DrizzleTokenStoreConfig): TokenStore {
82
+ const { table, eq, and, or, lt, isNull } = config
83
+ const claimTimeoutMs = config.claimTimeoutMs ?? 10000
84
+
85
+ function getDb(): any {
86
+ return getActiveTransaction<DrizzleTransaction>() ?? config.db
87
+ }
88
+
89
+ return {
90
+ async store(processorName, segment, token) {
91
+ const db = getDb()
92
+ const { tokenType, token: tokenData } = serializeToken(token)
93
+ await db.insert(table)
94
+ .values({ processorName, segment, mask: 0, tokenType, token: tokenData, timestamp: nowIso(), owner: null })
95
+ .onConflictDoUpdate({
96
+ target: [table.processorName, table.segment],
97
+ set: { tokenType, token: tokenData, timestamp: nowIso() },
98
+ })
99
+ },
100
+
101
+ async get(processorName, segment) {
102
+ const db = getDb()
103
+ const rows = await db.select().from(table)
104
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment)))
105
+ .limit(1)
106
+ if (rows.length === 0) return undefined
107
+ return deserializeToken(rows[0].tokenType, rows[0].token)
108
+ },
109
+
110
+ async initializeSegments(processorName, segmentCount) {
111
+ const db = getDb()
112
+ for (let i = 0; i < segmentCount; i++) {
113
+ await db.insert(table)
114
+ .values({ processorName, segment: i, mask: 0, tokenType: null, token: null, timestamp: null, owner: null })
115
+ .onConflictDoNothing()
116
+ }
117
+ },
118
+
119
+ async claimToken(processorName, segment, ownerId) {
120
+ const db = getDb()
121
+ const rows = await db.select().from(table)
122
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment)))
123
+ .limit(1)
124
+
125
+ if (rows.length === 0) {
126
+ await db.insert(table).values({
127
+ processorName, segment, mask: 0, tokenType: null, token: null, timestamp: nowIso(), owner: ownerId,
128
+ })
129
+ return undefined
130
+ }
131
+
132
+ const row = rows[0]
133
+ const isExpired = !row.owner || !row.timestamp ||
134
+ (Date.now() - new Date(row.timestamp).getTime() > claimTimeoutMs)
135
+
136
+ if (row.owner === ownerId || isExpired) {
137
+ await db.update(table)
138
+ .set({ owner: ownerId, timestamp: nowIso() })
139
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment)))
140
+ return deserializeToken(row.tokenType, row.token)
141
+ }
142
+
143
+ throw new UnableToClaimTokenError(processorName, segment)
144
+ },
145
+
146
+ async extendClaim(processorName, segment, ownerId) {
147
+ const db = getDb()
148
+ await db.update(table)
149
+ .set({ timestamp: nowIso() })
150
+ .where(and(
151
+ eq(table.processorName, processorName),
152
+ eq(table.segment, segment),
153
+ eq(table.owner, ownerId),
154
+ ))
155
+ },
156
+
157
+ async releaseClaim(processorName, segment, ownerId) {
158
+ const db = getDb()
159
+ await db.update(table)
160
+ .set({ owner: null, timestamp: null })
161
+ .where(and(
162
+ eq(table.processorName, processorName),
163
+ eq(table.segment, segment),
164
+ eq(table.owner, ownerId),
165
+ ))
166
+ },
167
+
168
+ async fetchSegments(processorName) {
169
+ const db = getDb()
170
+ const rows = await db.select({ segment: table.segment }).from(table)
171
+ .where(eq(table.processorName, processorName))
172
+ .orderBy(table.segment)
173
+ return rows.map((r: any) => r.segment)
174
+ },
175
+
176
+ async fetchAvailableSegments(processorName) {
177
+ const db = getDb()
178
+ const cutoff = new Date(Date.now() - claimTimeoutMs).toISOString()
179
+ const rows = await db.select({ segment: table.segment }).from(table)
180
+ .where(and(
181
+ eq(table.processorName, processorName),
182
+ or(isNull(table.owner), lt(table.timestamp, cutoff)),
183
+ ))
184
+ .orderBy(table.segment)
185
+ return rows.map((r: any) => r.segment)
186
+ },
187
+
188
+ async deleteToken(processorName, segment) {
189
+ const db = getDb()
190
+ await db.delete(table)
191
+ .where(and(eq(table.processorName, processorName), eq(table.segment, segment)))
192
+ },
193
+ }
194
+ }
@@ -0,0 +1,83 @@
1
+ import type { TransactionManager } from "@kronos-ts/messaging"
2
+
3
+ /**
4
+ * A Drizzle database instance that supports transactions.
5
+ * Works with any Drizzle driver (postgres-js, node-postgres, better-sqlite3, etc.).
6
+ */
7
+ export interface DrizzleDatabaseLike {
8
+ transaction<T>(fn: (tx: any) => Promise<T>): Promise<T>
9
+ }
10
+
11
+ /**
12
+ * The Drizzle transaction object — has the same query API as the database.
13
+ */
14
+ export type DrizzleTransaction = any
15
+
16
+ /**
17
+ * Creates a TransactionManager for Drizzle ORM.
18
+ *
19
+ * Drizzle's `db.transaction()` uses a callback pattern. This manager
20
+ * bridges it to the framework's `begin/commit/rollback` lifecycle
21
+ * using deferred promises — the same pattern as the Prisma extension.
22
+ *
23
+ * The `tx` is made available via `getActiveTransaction()` so projections
24
+ * and token stores participate in the same transaction.
25
+ *
26
+ * ```typescript
27
+ * import { drizzle } from "drizzle-orm/postgres-js"
28
+ * import { drizzleTransactionManager } from "@kronos-ts/drizzle"
29
+ *
30
+ * const db = drizzle(sql)
31
+ *
32
+ * // transactionManager wiring to a kronos() App is pending a typed
33
+ * // `transactionManager` slot (Phase 9). For now, construct the manager
34
+ * // and pass it directly into the unitOfWorkFactory composition:
35
+ * const txManager = drizzleTransactionManager(db)
36
+ * ```
37
+ */
38
+ export function drizzleTransactionManager(
39
+ db: DrizzleDatabaseLike,
40
+ ): TransactionManager<DrizzleTransaction> {
41
+ return {
42
+ async begin(): Promise<DrizzleTransaction> {
43
+ let resolveTx!: (tx: DrizzleTransaction) => void
44
+ let resolveCompletion!: () => void
45
+ let rejectCompletion!: (error: unknown) => void
46
+
47
+ const txReady = new Promise<DrizzleTransaction>((resolve) => {
48
+ resolveTx = resolve
49
+ })
50
+
51
+ const completionSignal = new Promise<void>((resolve, reject) => {
52
+ resolveCompletion = resolve
53
+ rejectCompletion = reject
54
+ })
55
+
56
+ const txPromise = db.transaction(async (tx) => {
57
+ resolveTx(tx)
58
+ await completionSignal
59
+ })
60
+
61
+ const tx = await txReady
62
+ ;(tx as any).__kronos_commit = resolveCompletion
63
+ ;(tx as any).__kronos_rollback = rejectCompletion
64
+ ;(tx as any).__kronos_txPromise = txPromise
65
+
66
+ return tx
67
+ },
68
+
69
+ async commit(tx: DrizzleTransaction): Promise<void> {
70
+ const commit = (tx as any).__kronos_commit as () => void
71
+ const txPromise = (tx as any).__kronos_txPromise as Promise<void>
72
+ commit()
73
+ await txPromise
74
+ },
75
+
76
+ async rollback(tx: DrizzleTransaction): Promise<void> {
77
+ const rollback = (tx as any).__kronos_rollback as (error: unknown) => void
78
+ const txPromise = (tx as any).__kronos_txPromise as Promise<void>
79
+ rollback(new Error("Transaction rolled back"))
80
+ try { await txPromise } catch { /* expected */ }
81
+ },
82
+ }
83
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export {
2
+ drizzleTransactionManager,
3
+ type DrizzleDatabaseLike,
4
+ type DrizzleTransaction,
5
+ } from "./drizzle-transaction-manager.js"
6
+
7
+ export {
8
+ drizzleTokenStore,
9
+ type DrizzleTokenStoreConfig,
10
+ } from "./drizzle-token-store.js"