@optimystic/quereus-plugin-optimystic 0.3.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 (45) hide show
  1. package/README.md +358 -0
  2. package/dist/index.d.ts +96 -0
  3. package/dist/index.js +2973 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/plugin-B1lVAVv0.d.ts +596 -0
  6. package/dist/plugin.d.ts +4 -0
  7. package/dist/plugin.js +2873 -0
  8. package/dist/plugin.js.map +1 -0
  9. package/examples/README.md +232 -0
  10. package/examples/quoomb.config.absolute.json +22 -0
  11. package/examples/quoomb.config.dev.json +16 -0
  12. package/examples/quoomb.config.linked.json +25 -0
  13. package/examples/quoomb.config.local.json +22 -0
  14. package/examples/quoomb.config.node1.json +23 -0
  15. package/examples/quoomb.config.node2.env.json +28 -0
  16. package/examples/quoomb.config.node2.json +29 -0
  17. package/examples/quoomb.config.single-node.json +25 -0
  18. package/examples/quoomb.config.web.json +30 -0
  19. package/examples/start-mesh.ps1 +48 -0
  20. package/examples/start-mesh.sh +44 -0
  21. package/examples/test-comprehensive.sql +25 -0
  22. package/examples/test-manual-plugin.txt +23 -0
  23. package/examples/test-multi-insert.sql +11 -0
  24. package/examples/test-network.sql +20 -0
  25. package/examples/test-plugin.sql +20 -0
  26. package/examples/test-single-insert.sql +20 -0
  27. package/examples/test-single-node.sql +20 -0
  28. package/package.json +131 -0
  29. package/src/functions/transaction-id.ts +29 -0
  30. package/src/index.ts +42 -0
  31. package/src/optimystic-adapter/collection-factory.ts +337 -0
  32. package/src/optimystic-adapter/key-network.ts +106 -0
  33. package/src/optimystic-adapter/txn-bridge.ts +272 -0
  34. package/src/optimystic-adapter/vtab-connection.ts +76 -0
  35. package/src/optimystic-module.ts +1064 -0
  36. package/src/plugin.ts +64 -0
  37. package/src/schema/index-manager.ts +266 -0
  38. package/src/schema/row-codec.ts +312 -0
  39. package/src/schema/schema-manager.ts +217 -0
  40. package/src/schema/statistics-collector.ts +173 -0
  41. package/src/transaction/index.ts +16 -0
  42. package/src/transaction/quereus-engine.ts +174 -0
  43. package/src/types.ts +91 -0
  44. package/src/util/generate-transaction-id.ts +41 -0
  45. package/test/README.md +313 -0
@@ -0,0 +1,217 @@
1
+ /**
2
+ * SchemaManager - Manages table schemas in Optimystic trees
3
+ *
4
+ * Stores and retrieves table schema definitions from distributed Optimystic trees.
5
+ * Schema is stored in a dedicated tree at `tree://schema/{tableName}`.
6
+ */
7
+
8
+ import type { Tree } from '@optimystic/db-core';
9
+ import type { TableSchema, ColumnSchema } from '@quereus/quereus';
10
+ import type { ITransactor } from '@optimystic/db-core';
11
+
12
+ // IndexSchema type from TableSchema.indexes
13
+ type IndexSchema = NonNullable<TableSchema['indexes']>[number];
14
+
15
+ /**
16
+ * Serializable schema storage format
17
+ */
18
+ export interface StoredTableSchema {
19
+ name: string;
20
+ schemaName: string;
21
+ columns: StoredColumnSchema[];
22
+ primaryKeyDefinition: StoredPrimaryKeyColumn[];
23
+ indexes: StoredIndexSchema[];
24
+ vtabModuleName: string;
25
+ vtabArgs?: Record<string, any>;
26
+ isTemporary?: boolean;
27
+ estimatedRows?: number;
28
+ }
29
+
30
+ export interface StoredColumnSchema {
31
+ name: string;
32
+ affinity: string;
33
+ notNull: boolean;
34
+ primaryKey: boolean;
35
+ pkOrder: number;
36
+ defaultValue?: any;
37
+ collation: string;
38
+ generated: boolean;
39
+ pkDirection?: 'asc' | 'desc';
40
+ }
41
+
42
+ export interface StoredPrimaryKeyColumn {
43
+ index: number;
44
+ desc?: boolean;
45
+ autoIncrement?: boolean;
46
+ collation?: string;
47
+ }
48
+
49
+ export interface StoredIndexSchema {
50
+ name: string;
51
+ columns: StoredIndexColumn[];
52
+ unique?: boolean;
53
+ }
54
+
55
+ export interface StoredIndexColumn {
56
+ index: number;
57
+ desc?: boolean;
58
+ collation?: string;
59
+ }
60
+
61
+ /**
62
+ * Manages schema storage and retrieval in Optimystic trees
63
+ */
64
+ export class SchemaManager {
65
+ private schemaCache = new Map<string, StoredTableSchema>();
66
+
67
+ constructor(
68
+ private readonly getSchemaTree: (transactor?: ITransactor) => Promise<Tree<string, any>>
69
+ ) {}
70
+
71
+ /**
72
+ * Store a table schema
73
+ */
74
+ async storeSchema(schema: TableSchema, transactor?: ITransactor): Promise<void> {
75
+ const stored = this.tableSchemaToStored(schema);
76
+ this.schemaCache.set(schema.name, stored);
77
+
78
+ const tree = await this.getSchemaTree(transactor);
79
+ await tree.replace([[schema.name, stored]]);
80
+ }
81
+
82
+ /**
83
+ * Retrieve a table schema
84
+ */
85
+ async getSchema(tableName: string, transactor?: ITransactor): Promise<StoredTableSchema | undefined> {
86
+ // Check cache first
87
+ const cached = this.schemaCache.get(tableName);
88
+ if (cached) {
89
+ return cached;
90
+ }
91
+
92
+ // Load from tree
93
+ const tree = await this.getSchemaTree(transactor);
94
+ const path = await tree.find(tableName);
95
+ if (!tree.isValid(path)) {
96
+ return undefined;
97
+ }
98
+
99
+ const entry = tree.at(path) as [string, StoredTableSchema];
100
+ if (entry && entry.length >= 2) {
101
+ const stored = entry[1];
102
+ this.schemaCache.set(tableName, stored);
103
+ return stored;
104
+ }
105
+
106
+ return undefined;
107
+ }
108
+
109
+ /**
110
+ * Delete a table schema
111
+ */
112
+ async deleteSchema(tableName: string, transactor?: ITransactor): Promise<void> {
113
+ this.schemaCache.delete(tableName);
114
+
115
+ const tree = await this.getSchemaTree(transactor);
116
+ await tree.replace([[tableName, undefined]]);
117
+ }
118
+
119
+ /**
120
+ * List all table names
121
+ */
122
+ async listTables(transactor?: ITransactor): Promise<string[]> {
123
+ const tree = await this.getSchemaTree(transactor);
124
+ const tables: string[] = [];
125
+
126
+ for await (const path of tree.range({ isAscending: true } as any)) {
127
+ if (!tree.isValid(path)) {
128
+ continue;
129
+ }
130
+
131
+ const entry = tree.at(path) as [string, any];
132
+ if (entry && entry.length >= 1) {
133
+ tables.push(entry[0]);
134
+ }
135
+ }
136
+
137
+ return tables;
138
+ }
139
+
140
+ /**
141
+ * Clear the schema cache
142
+ */
143
+ clearCache(): void {
144
+ this.schemaCache.clear();
145
+ }
146
+
147
+ /**
148
+ * Convert TableSchema to storable format
149
+ */
150
+ private tableSchemaToStored(schema: TableSchema): StoredTableSchema {
151
+ return {
152
+ name: schema.name,
153
+ schemaName: schema.schemaName,
154
+ columns: schema.columns.map(col => this.columnSchemaToStored(col)),
155
+ primaryKeyDefinition: schema.primaryKeyDefinition.map(pk => ({
156
+ index: pk.index,
157
+ desc: pk.desc,
158
+ autoIncrement: pk.autoIncrement,
159
+ collation: pk.collation,
160
+ })),
161
+ indexes: (schema.indexes || []).map(idx => this.indexSchemaToStored(idx)),
162
+ vtabModuleName: schema.vtabModuleName,
163
+ vtabArgs: schema.vtabArgs as Record<string, any>,
164
+ isTemporary: schema.isTemporary,
165
+ estimatedRows: schema.estimatedRows,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Convert ColumnSchema to storable format
171
+ */
172
+ private columnSchemaToStored(col: ColumnSchema): StoredColumnSchema {
173
+ return {
174
+ name: col.name,
175
+ affinity: col.logicalType.name, // Use logicalType.name for storage
176
+ notNull: col.notNull,
177
+ primaryKey: col.primaryKey,
178
+ pkOrder: col.pkOrder,
179
+ defaultValue: col.defaultValue ? this.serializeExpression(col.defaultValue) : undefined,
180
+ collation: col.collation,
181
+ generated: col.generated,
182
+ pkDirection: col.pkDirection,
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Convert IndexSchema to storable format
188
+ */
189
+ private indexSchemaToStored(idx: IndexSchema): StoredIndexSchema {
190
+ return {
191
+ name: idx.name,
192
+ columns: idx.columns.map((col: { index: number; desc?: boolean; collation?: string }) => ({
193
+ index: col.index,
194
+ desc: col.desc,
195
+ collation: col.collation,
196
+ })),
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Serialize an expression for storage
202
+ * For now, we'll store a simplified representation
203
+ */
204
+ private serializeExpression(expr: any): any {
205
+ // TODO: Implement proper expression serialization
206
+ // For now, just store the expression as-is if it's a simple value
207
+ if (typeof expr === 'object' && expr !== null) {
208
+ if ('type' in expr && expr.type === 'literal') {
209
+ return { type: 'literal', value: expr.value };
210
+ }
211
+ // For complex expressions, we'll need to implement full serialization
212
+ return { type: 'complex', raw: JSON.stringify(expr) };
213
+ }
214
+ return expr;
215
+ }
216
+ }
217
+
@@ -0,0 +1,173 @@
1
+ /**
2
+ * StatisticsCollector - Collects and maintains table statistics for query optimization
3
+ *
4
+ * Tracks row counts, distinct values, and provides cost estimates for query planning.
5
+ */
6
+
7
+ import type { StoredTableSchema } from './schema-manager.js';
8
+
9
+ /**
10
+ * Statistics for a single column
11
+ */
12
+ export interface ColumnStatistics {
13
+ /** Approximate number of distinct values */
14
+ distinctCount: number;
15
+ /** Approximate number of NULL values */
16
+ nullCount: number;
17
+ /** Sample of values for histogram (optional) */
18
+ sampleValues?: unknown[];
19
+ }
20
+
21
+ /**
22
+ * Statistics for a table
23
+ */
24
+ export interface TableStatistics {
25
+ /** Total number of rows (approximate) */
26
+ rowCount: number;
27
+ /** Statistics per column */
28
+ columnStats: Map<number, ColumnStatistics>;
29
+ /** Last update timestamp */
30
+ lastUpdated: number;
31
+ }
32
+
33
+ /**
34
+ * Collects and maintains statistics for query optimization
35
+ */
36
+ export class StatisticsCollector {
37
+ private stats: TableStatistics;
38
+
39
+ constructor(private schema: StoredTableSchema) {
40
+ this.stats = {
41
+ rowCount: 0,
42
+ columnStats: new Map(),
43
+ lastUpdated: Date.now(),
44
+ };
45
+
46
+ // Initialize column stats
47
+ for (let i = 0; i < schema.columns.length; i++) {
48
+ this.stats.columnStats.set(i, {
49
+ distinctCount: 0,
50
+ nullCount: 0,
51
+ });
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Get current table statistics
57
+ */
58
+ getStatistics(): TableStatistics {
59
+ return this.stats;
60
+ }
61
+
62
+ /**
63
+ * Get estimated row count
64
+ */
65
+ getRowCount(): number {
66
+ return this.stats.rowCount;
67
+ }
68
+
69
+ /**
70
+ * Get estimated distinct count for a column
71
+ */
72
+ getDistinctCount(columnIndex: number): number {
73
+ const colStats = this.stats.columnStats.get(columnIndex);
74
+ return colStats?.distinctCount || 0;
75
+ }
76
+
77
+ /**
78
+ * Increment row count (called on INSERT)
79
+ */
80
+ incrementRowCount(): void {
81
+ this.stats.rowCount++;
82
+ this.stats.lastUpdated = Date.now();
83
+ }
84
+
85
+ /**
86
+ * Decrement row count (called on DELETE)
87
+ */
88
+ decrementRowCount(): void {
89
+ this.stats.rowCount = Math.max(0, this.stats.rowCount - 1);
90
+ this.stats.lastUpdated = Date.now();
91
+ }
92
+
93
+ /**
94
+ * Estimate selectivity of an equality constraint
95
+ * Returns a value between 0 and 1 representing the fraction of rows that match
96
+ */
97
+ estimateEqualitySelectivity(columnIndex: number): number {
98
+ const distinctCount = this.getDistinctCount(columnIndex);
99
+ if (distinctCount === 0) {
100
+ return 0.1; // Default estimate
101
+ }
102
+ return 1.0 / distinctCount;
103
+ }
104
+
105
+ /**
106
+ * Estimate selectivity of a range constraint
107
+ * Returns a value between 0 and 1 representing the fraction of rows that match
108
+ */
109
+ estimateRangeSelectivity(_columnIndex: number): number {
110
+ // Simple heuristic: assume range matches 25% of rows
111
+ return 0.25;
112
+ }
113
+
114
+ /**
115
+ * Estimate cost of a full table scan
116
+ */
117
+ estimateTableScanCost(): number {
118
+ // Cost is proportional to row count
119
+ // Base cost of 1.0 per row
120
+ return Math.max(1000, this.stats.rowCount);
121
+ }
122
+
123
+ /**
124
+ * Estimate cost of an index scan
125
+ */
126
+ estimateIndexScanCost(selectivity: number): number {
127
+ // Cost includes:
128
+ // 1. Index lookup cost (logarithmic)
129
+ // 2. Row fetch cost (proportional to selected rows)
130
+ const indexLookupCost = Math.log2(Math.max(1, this.stats.rowCount)) * 2;
131
+ const rowFetchCost = this.stats.rowCount * selectivity;
132
+ return indexLookupCost + rowFetchCost;
133
+ }
134
+
135
+ /**
136
+ * Estimate number of rows returned by a constraint
137
+ */
138
+ estimateRowsForConstraint(columnIndex: number, isEquality: boolean): number {
139
+ if (isEquality) {
140
+ const selectivity = this.estimateEqualitySelectivity(columnIndex);
141
+ return Math.max(1, Math.floor(this.stats.rowCount * selectivity));
142
+ } else {
143
+ const selectivity = this.estimateRangeSelectivity(columnIndex);
144
+ return Math.max(1, Math.floor(this.stats.rowCount * selectivity));
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Update statistics based on actual data (called periodically)
150
+ * This is a simplified version - a real implementation would sample the data
151
+ */
152
+ async updateStatistics(sampleSize = 1000): Promise<void> {
153
+ // TODO: Implement actual statistics collection by sampling the table
154
+ // For now, we just update the timestamp
155
+ this.stats.lastUpdated = Date.now();
156
+ }
157
+
158
+ /**
159
+ * Reset statistics
160
+ */
161
+ reset(): void {
162
+ this.stats.rowCount = 0;
163
+ this.stats.columnStats.clear();
164
+ for (let i = 0; i < this.schema.columns.length; i++) {
165
+ this.stats.columnStats.set(i, {
166
+ distinctCount: 0,
167
+ nullCount: 0,
168
+ });
169
+ }
170
+ this.stats.lastUpdated = Date.now();
171
+ }
172
+ }
173
+
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Transaction support for Quereus-Optimystic integration.
3
+ *
4
+ * This module provides the QuereusEngine for executing SQL transactions
5
+ * through the Optimystic distributed transaction system.
6
+ */
7
+
8
+ export {
9
+ QuereusEngine,
10
+ QUEREUS_ENGINE_ID,
11
+ createQuereusStatement,
12
+ createQuereusStatements
13
+ } from './quereus-engine.js';
14
+
15
+ export type { QuereusStatement } from './quereus-engine.js';
16
+
@@ -0,0 +1,174 @@
1
+ import type { Database, SqlParameters } from '@quereus/quereus';
2
+ import type {
3
+ ITransactionEngine,
4
+ Transaction,
5
+ ExecutionResult,
6
+ CollectionActions,
7
+ TransactionCoordinator
8
+ } from '@optimystic/db-core';
9
+ import { sha256 } from '@noble/hashes/sha256';
10
+
11
+ /**
12
+ * Engine ID for Quereus SQL transactions.
13
+ * Format: "quereus@{version}" where version matches the quereus package version.
14
+ */
15
+ export const QUEREUS_ENGINE_ID = 'quereus@0.5.3';
16
+
17
+ /**
18
+ * Statement format for Quereus transactions.
19
+ * Each statement is a SQL string with optional parameters.
20
+ */
21
+ export type QuereusStatement = {
22
+ /** The SQL statement to execute */
23
+ sql: string;
24
+ /** Optional parameters for the statement */
25
+ params?: SqlParameters;
26
+ };
27
+
28
+ /**
29
+ * Quereus-specific transaction engine for SQL execution.
30
+ *
31
+ * This engine:
32
+ * 1. Executes SQL statements through a Quereus database
33
+ * 2. Collects resulting actions from the virtual table module
34
+ * 3. Computes schema hash for validation
35
+ *
36
+ * Used for both initial execution (client creating transaction) and
37
+ * re-execution (validators verifying transaction).
38
+ */
39
+ export class QuereusEngine implements ITransactionEngine {
40
+ private schemaHashCache: string | undefined;
41
+ private schemaVersion: number = 0;
42
+
43
+ constructor(
44
+ private readonly db: Database,
45
+ private readonly coordinator: TransactionCoordinator
46
+ ) {}
47
+
48
+ /**
49
+ * Execute a transaction's statements and produce actions.
50
+ *
51
+ * For initial execution: Executes SQL through Quereus, which triggers
52
+ * the Optimystic virtual table module to apply actions.
53
+ *
54
+ * For validation: Re-executes the same SQL statements to verify
55
+ * they produce the same operations.
56
+ */
57
+ async execute(transaction: Transaction): Promise<ExecutionResult> {
58
+ try {
59
+ const allActions: CollectionActions[] = [];
60
+
61
+ for (const statementJson of transaction.statements) {
62
+ const statement = JSON.parse(statementJson) as QuereusStatement;
63
+
64
+ // Execute SQL through Quereus
65
+ // The Optimystic virtual table module will:
66
+ // 1. Translate SQL mutations to actions
67
+ // 2. Call coordinator.applyActions() with the stampId
68
+ await this.db.exec(statement.sql, statement.params);
69
+
70
+ // Note: Actions are collected by the coordinator's trackers,
71
+ // not returned directly from exec(). The coordinator tracks
72
+ // all actions applied during this transaction.
73
+ }
74
+
75
+ return {
76
+ success: true,
77
+ actions: allActions
78
+ };
79
+ } catch (error) {
80
+ return {
81
+ success: false,
82
+ error: `Failed to execute SQL transaction: ${error instanceof Error ? error.message : String(error)}`
83
+ };
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Get the schema hash for this engine.
89
+ *
90
+ * The schema hash is used for validation - all participants must have
91
+ * matching schema hashes for a transaction to be valid.
92
+ *
93
+ * Uses caching to avoid recomputing if schema hasn't changed.
94
+ */
95
+ async getSchemaHash(): Promise<string> {
96
+ // Check if we have a cached hash
97
+ if (this.schemaHashCache !== undefined) {
98
+ return this.schemaHashCache;
99
+ }
100
+
101
+ // Compute and cache the schema hash
102
+ this.schemaHashCache = await this.computeSchemaHash();
103
+ return this.schemaHashCache;
104
+ }
105
+
106
+ /**
107
+ * Invalidate the schema hash cache.
108
+ * Call this when the schema changes (e.g., after DDL statements).
109
+ */
110
+ invalidateSchemaCache(): void {
111
+ this.schemaHashCache = undefined;
112
+ this.schemaVersion++;
113
+ }
114
+
115
+ /**
116
+ * Get the current schema version number.
117
+ * Increments each time the schema cache is invalidated.
118
+ */
119
+ getSchemaVersion(): number {
120
+ return this.schemaVersion;
121
+ }
122
+
123
+ /**
124
+ * Compute the schema hash from the database catalog.
125
+ *
126
+ * Uses the schema() table-valued function to get schema information,
127
+ * then hashes the canonical representation using SHA-256.
128
+ */
129
+ private async computeSchemaHash(): Promise<string> {
130
+ // Query schema information from Quereus
131
+ const schemaInfo: Array<{ type: string; name: string; sql: string | null }> = [];
132
+
133
+ for await (const row of this.db.eval("select type, name, sql from schema() order by type, name")) {
134
+ schemaInfo.push({
135
+ type: row.type as string,
136
+ name: row.name as string,
137
+ sql: row.sql as string | null
138
+ });
139
+ }
140
+
141
+ // Serialize to canonical JSON
142
+ const catalogJson = JSON.stringify(schemaInfo);
143
+
144
+ // Compute SHA-256 hash using @noble/hashes
145
+ const hashBytes = sha256(new TextEncoder().encode(catalogJson));
146
+ // Use first 16 bytes encoded as base64url for compact representation
147
+ const hashBase64 = bytesToBase64url(hashBytes.slice(0, 16));
148
+ return `schema:${hashBase64}`;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Convert bytes to base64url encoding (URL-safe, no padding).
154
+ */
155
+ function bytesToBase64url(bytes: Uint8Array): string {
156
+ const base64 = btoa(String.fromCharCode(...bytes));
157
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
158
+ }
159
+
160
+ /**
161
+ * Helper to create Quereus statement JSON for a transaction.
162
+ */
163
+ export function createQuereusStatement(sql: string, params?: SqlParameters): string {
164
+ const statement: QuereusStatement = { sql, params };
165
+ return JSON.stringify(statement);
166
+ }
167
+
168
+ /**
169
+ * Helper to create an array of Quereus statements for a transaction.
170
+ */
171
+ export function createQuereusStatements(statements: Array<{ sql: string; params?: SqlParameters }>): string[] {
172
+ return statements.map(s => createQuereusStatement(s.sql, s.params));
173
+ }
174
+
package/src/types.ts ADDED
@@ -0,0 +1,91 @@
1
+ import type { Libp2p } from '@libp2p/interface';
2
+ import type { IKeyNetwork, ITransactor } from '@optimystic/db-core';
3
+
4
+ /**
5
+ * Configuration for the optimystic virtual table
6
+ */
7
+ export interface OptimysticOptions {
8
+ /** URI for the collection (e.g., 'tree://mydb/users') */
9
+ collectionUri: string;
10
+
11
+ /** Transactor type - 'network', 'test', or custom class name */
12
+ transactor?: 'network' | 'test' | string;
13
+
14
+ /** Key network type - 'libp2p', 'test', or custom class name */
15
+ keyNetwork?: 'libp2p' | 'test' | string;
16
+
17
+ /** Existing libp2p instance to use (optional) */
18
+ libp2p?: Libp2p;
19
+
20
+ /** Options for creating a new libp2p node */
21
+ libp2pOptions?: LibP2PNodeOptions;
22
+
23
+ /** Enable local snapshot cache */
24
+ cache?: boolean;
25
+
26
+ /** Row encoding format */
27
+ encoding?: 'json' | 'msgpack';
28
+ }
29
+
30
+ /**
31
+ * Options for creating a libp2p node
32
+ */
33
+ export interface LibP2PNodeOptions {
34
+ /** Network port to listen on */
35
+ port?: number;
36
+
37
+ /** Network name for protocol prefixes */
38
+ networkName?: string;
39
+
40
+ /** Bootstrap nodes for peer discovery */
41
+ bootstrapNodes?: string[];
42
+ }
43
+
44
+ /**
45
+ * Internal configuration after parsing and validation
46
+ */
47
+ export interface ParsedOptimysticOptions {
48
+ collectionUri: string;
49
+ transactor: 'network' | 'test' | string;
50
+ keyNetwork: 'libp2p' | 'test' | string;
51
+ libp2p?: Libp2p;
52
+ libp2pOptions: LibP2PNodeOptions;
53
+ cache: boolean;
54
+ encoding: 'json' | 'msgpack';
55
+ }
56
+
57
+ /**
58
+ * Registry for custom transactor and key network implementations
59
+ */
60
+ export interface CustomImplementationRegistry {
61
+ transactors: Map<string, new (...args: any[]) => ITransactor>;
62
+ keyNetworks: Map<string, new (...args: any[]) => IKeyNetwork>;
63
+ }
64
+
65
+ /**
66
+ * Column definition for the virtual table
67
+ */
68
+ export interface ColumnDefinition {
69
+ name: string;
70
+ type: 'TEXT' | 'INTEGER' | 'REAL' | 'BLOB' | 'NULL';
71
+ isPrimaryKey: boolean;
72
+ isNotNull: boolean;
73
+ }
74
+
75
+ /**
76
+ * Row data as stored in the tree
77
+ * Format: [primaryKey, encodedRow]
78
+ * - primaryKey: string representation of the primary key (composite keys are joined with \x00)
79
+ * - encodedRow: JSON-encoded row data
80
+ */
81
+ export type RowData = [string, string];
82
+
83
+ /**
84
+ * Transaction state for managing Optimystic transactions
85
+ */
86
+ export interface TransactionState {
87
+ transactor: ITransactor;
88
+ isActive: boolean;
89
+ collections: Map<string, any>; // Tree collections used in this transaction
90
+ transactionId: string; // Unique identifier for this transaction (stable within transaction, cycles between)
91
+ }
@@ -0,0 +1,41 @@
1
+ import { randomBytes } from '@libp2p/crypto';
2
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
3
+ import { sha256 } from '@noble/hashes/sha256';
4
+
5
+ /**
6
+ * Generates a unique transaction ID that includes a hash of the peer ID
7
+ * to reduce collision probability across distributed nodes.
8
+ *
9
+ * Format: {peerIdHash}-{randomBytes}
10
+ * - peerIdHash: First 16 bytes of SHA-256 hash of peer ID (base64url)
11
+ * - randomBytes: 16 random bytes (base64url)
12
+ * - Total: 32 bytes, base64url encoded
13
+ *
14
+ * @param peerId Optional peer ID to include in the hash. If not provided, only random bytes are used.
15
+ * @returns A unique transaction ID string
16
+ */
17
+ export function generateTransactionId(peerId?: string): string {
18
+ const randomPart = randomBytes(16);
19
+
20
+ if (peerId) {
21
+ // Hash the peer ID and take first 16 bytes
22
+ const peerIdBytes = new TextEncoder().encode(peerId);
23
+ const peerIdHash = sha256(peerIdBytes);
24
+ const peerIdHashPart = peerIdHash.slice(0, 16);
25
+
26
+ // Combine peer ID hash and random bytes
27
+ const combined = new Uint8Array(32);
28
+ combined.set(peerIdHashPart, 0);
29
+ combined.set(randomPart, 16);
30
+
31
+ return uint8ArrayToString(combined, 'base64url');
32
+ }
33
+
34
+ // Fallback: just use random bytes (padded to 32 bytes for consistency)
35
+ const fallback = new Uint8Array(32);
36
+ fallback.set(randomPart, 0);
37
+ fallback.set(randomBytes(16), 16);
38
+
39
+ return uint8ArrayToString(fallback, 'base64url');
40
+ }
41
+