@affectively/dash 5.0.0 → 5.0.1

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.
@@ -30,5 +30,140 @@ export declare class DashEngine {
30
30
  maxZ: number;
31
31
  }): Promise<any[]>;
32
32
  close(): void;
33
+ /**
34
+ * Get information about all tables in the database
35
+ */
36
+ getAllTables(): TableInfo[];
37
+ /**
38
+ * Get detailed schema information for a specific table
39
+ */
40
+ getTableSchema(tableName: string): TableSchema | null;
41
+ /**
42
+ * Get the row count for a specific table
43
+ */
44
+ getTableRowCount(tableName: string): number;
45
+ /**
46
+ * Get statistics about the vector index
47
+ */
48
+ getVectorIndexStats(): VectorIndexStats;
49
+ /**
50
+ * Get all embeddings from the vector index
51
+ */
52
+ getAllEmbeddings(limit?: number, offset?: number): EmbeddingEntry[];
53
+ /**
54
+ * Search for similar vectors given a raw vector
55
+ */
56
+ searchSimilarByVector(vector: number[], k?: number): SimilarityResult[];
57
+ /**
58
+ * Get statistics about the spatial R-Tree index
59
+ */
60
+ getSpatialIndexStats(): SpatialIndexStats;
61
+ /**
62
+ * Get all spatial bounds from the R-Tree index
63
+ */
64
+ getAllSpatialBounds(limit?: number, offset?: number): SpatialBound[];
65
+ /**
66
+ * Get information about active table subscriptions
67
+ */
68
+ getActiveSubscriptions(): SubscriptionInfo[];
69
+ /**
70
+ * Get table listener counts as a map
71
+ */
72
+ getTableListenerCounts(): Record<string, number>;
73
+ /**
74
+ * Check if the database is ready
75
+ */
76
+ isReady(): boolean;
77
+ /**
78
+ * Get database metadata
79
+ */
80
+ getDatabaseInfo(): DatabaseInfo;
81
+ }
82
+ export interface TableInfo {
83
+ name: string;
84
+ type: 'table' | 'virtual';
85
+ rowCount: number;
86
+ isVirtual: boolean;
87
+ }
88
+ export interface TableSchema {
89
+ name: string;
90
+ columns: ColumnInfo[];
91
+ indexes: IndexInfo[];
92
+ foreignKeys: ForeignKeyInfo[];
93
+ rowCount: number;
94
+ }
95
+ export interface ColumnInfo {
96
+ cid: number;
97
+ name: string;
98
+ type: string;
99
+ notNull: boolean;
100
+ defaultValue: string | null;
101
+ isPrimaryKey: boolean;
102
+ }
103
+ export interface IndexInfo {
104
+ name: string;
105
+ unique: boolean;
106
+ origin: string;
107
+ }
108
+ export interface ForeignKeyInfo {
109
+ id: number;
110
+ table: string;
111
+ from: string;
112
+ to: string;
113
+ }
114
+ export interface VectorIndexStats {
115
+ totalEmbeddings: number;
116
+ dimensions: number;
117
+ indexExists: boolean;
118
+ tableName?: string;
119
+ error?: string;
120
+ }
121
+ export interface EmbeddingEntry {
122
+ id: string;
123
+ content: string | null;
124
+ embedding: number[] | null;
125
+ }
126
+ export interface SimilarityResult {
127
+ id: string;
128
+ content: string | null;
129
+ distance: number;
130
+ score: number;
131
+ }
132
+ export interface SpatialIndexStats {
133
+ totalEntries: number;
134
+ indexExists: boolean;
135
+ tableName?: string;
136
+ globalBounds?: {
137
+ minX: number;
138
+ maxX: number;
139
+ minY: number;
140
+ maxY: number;
141
+ minZ: number;
142
+ maxZ: number;
143
+ };
144
+ error?: string;
145
+ }
146
+ export interface SpatialBound {
147
+ id: string;
148
+ rowid: number;
149
+ bounds: {
150
+ minX: number;
151
+ maxX: number;
152
+ minY: number;
153
+ maxY: number;
154
+ minZ: number;
155
+ maxZ: number;
156
+ };
157
+ content: string | null;
158
+ }
159
+ export interface SubscriptionInfo {
160
+ table: string;
161
+ listenerCount: number;
162
+ }
163
+ export interface DatabaseInfo {
164
+ ready: boolean;
165
+ schemaVersion: number;
166
+ tableCount: number;
167
+ tables?: string[];
33
168
  }
34
169
  export declare const dash: DashEngine;
@@ -252,5 +252,303 @@ export class DashEngine {
252
252
  close() {
253
253
  this.db?.close();
254
254
  }
255
+ // ============================================
256
+ // INTROSPECTION METHODS FOR DASH-STUDIO
257
+ // ============================================
258
+ /**
259
+ * Get information about all tables in the database
260
+ */
261
+ getAllTables() {
262
+ if (!this.db)
263
+ return [];
264
+ const tables = this.execute(`
265
+ SELECT name, type
266
+ FROM sqlite_master
267
+ WHERE type IN ('table', 'virtual table')
268
+ AND name NOT LIKE 'sqlite_%'
269
+ ORDER BY name
270
+ `);
271
+ return tables.map((t) => {
272
+ const rowCount = this.getTableRowCount(t.name);
273
+ return {
274
+ name: t.name,
275
+ type: t.type === 'table' ? 'table' : 'virtual',
276
+ rowCount,
277
+ isVirtual: t.type !== 'table'
278
+ };
279
+ });
280
+ }
281
+ /**
282
+ * Get detailed schema information for a specific table
283
+ */
284
+ getTableSchema(tableName) {
285
+ if (!this.db)
286
+ return null;
287
+ try {
288
+ const columns = this.execute(`PRAGMA table_info('${tableName.replace(/'/g, "''")}')`);
289
+ // Get indexes for this table
290
+ const indexes = this.execute(`PRAGMA index_list('${tableName.replace(/'/g, "''")}')`);
291
+ // Get foreign keys
292
+ const foreignKeys = this.execute(`PRAGMA foreign_key_list('${tableName.replace(/'/g, "''")}')`);
293
+ return {
294
+ name: tableName,
295
+ columns: columns.map((col) => ({
296
+ cid: col.cid,
297
+ name: col.name,
298
+ type: col.type || 'ANY',
299
+ notNull: col.notnull === 1,
300
+ defaultValue: col.dflt_value,
301
+ isPrimaryKey: col.pk === 1
302
+ })),
303
+ indexes: indexes.map((idx) => ({
304
+ name: idx.name,
305
+ unique: idx.unique === 1,
306
+ origin: idx.origin
307
+ })),
308
+ foreignKeys: foreignKeys.map((fk) => ({
309
+ id: fk.id,
310
+ table: fk.table,
311
+ from: fk.from,
312
+ to: fk.to
313
+ })),
314
+ rowCount: this.getTableRowCount(tableName)
315
+ };
316
+ }
317
+ catch (e) {
318
+ console.warn(`Failed to get schema for table ${tableName}:`, e);
319
+ return null;
320
+ }
321
+ }
322
+ /**
323
+ * Get the row count for a specific table
324
+ */
325
+ getTableRowCount(tableName) {
326
+ if (!this.db)
327
+ return 0;
328
+ try {
329
+ const result = this.execute(`SELECT COUNT(*) as count FROM "${tableName.replace(/"/g, '""')}"`);
330
+ return result[0]?.count ?? 0;
331
+ }
332
+ catch (e) {
333
+ // Virtual tables may not support COUNT
334
+ return -1;
335
+ }
336
+ }
337
+ /**
338
+ * Get statistics about the vector index
339
+ */
340
+ getVectorIndexStats() {
341
+ if (!this.db) {
342
+ return { totalEmbeddings: 0, dimensions: 384, indexExists: false };
343
+ }
344
+ try {
345
+ const countResult = this.execute('SELECT COUNT(*) as count FROM dash_vec_idx');
346
+ const totalEmbeddings = countResult[0]?.count ?? 0;
347
+ return {
348
+ totalEmbeddings,
349
+ dimensions: 384, // Fixed dimension from schema
350
+ indexExists: true,
351
+ tableName: 'dash_vec_idx'
352
+ };
353
+ }
354
+ catch (e) {
355
+ return {
356
+ totalEmbeddings: 0,
357
+ dimensions: 384,
358
+ indexExists: false,
359
+ error: e instanceof Error ? e.message : 'Vector index not available'
360
+ };
361
+ }
362
+ }
363
+ /**
364
+ * Get all embeddings from the vector index
365
+ */
366
+ getAllEmbeddings(limit = 100, offset = 0) {
367
+ if (!this.db)
368
+ return [];
369
+ try {
370
+ const rows = this.execute(`
371
+ SELECT v.id, i.content, v.embedding
372
+ FROM dash_vec_idx v
373
+ LEFT JOIN dash_items i ON v.id = i.id
374
+ LIMIT ? OFFSET ?
375
+ `, [limit, offset]);
376
+ return rows.map((row) => ({
377
+ id: row.id,
378
+ content: row.content,
379
+ embedding: row.embedding ? Array.from(row.embedding) : null
380
+ }));
381
+ }
382
+ catch (e) {
383
+ console.warn('Failed to get embeddings:', e);
384
+ return [];
385
+ }
386
+ }
387
+ /**
388
+ * Search for similar vectors given a raw vector
389
+ */
390
+ searchSimilarByVector(vector, k = 5) {
391
+ if (!this.db)
392
+ return [];
393
+ try {
394
+ const rows = this.execute(`
395
+ SELECT
396
+ item.id,
397
+ item.content,
398
+ distance
399
+ FROM dash_vec_idx
400
+ JOIN dash_items AS item ON item.id = dash_vec_idx.id
401
+ WHERE embedding MATCH ?
402
+ ORDER BY distance
403
+ LIMIT ?
404
+ `, [new Float32Array(vector), k]);
405
+ return rows.map((row) => ({
406
+ id: row.id,
407
+ content: row.content,
408
+ distance: row.distance,
409
+ score: row.distance !== undefined ? 1 - row.distance : 0
410
+ }));
411
+ }
412
+ catch (e) {
413
+ console.warn('Vector search failed:', e);
414
+ return [];
415
+ }
416
+ }
417
+ /**
418
+ * Get statistics about the spatial R-Tree index
419
+ */
420
+ getSpatialIndexStats() {
421
+ if (!this.db) {
422
+ return { totalEntries: 0, indexExists: false };
423
+ }
424
+ try {
425
+ const countResult = this.execute('SELECT COUNT(*) as count FROM dash_spatial_idx');
426
+ const totalEntries = countResult[0]?.count ?? 0;
427
+ // Get bounding box of all entries
428
+ const boundsResult = this.execute(`
429
+ SELECT
430
+ MIN(minX) as minX, MAX(maxX) as maxX,
431
+ MIN(minY) as minY, MAX(maxY) as maxY,
432
+ MIN(minZ) as minZ, MAX(maxZ) as maxZ
433
+ FROM dash_spatial_idx
434
+ `);
435
+ const bounds = boundsResult[0] || {};
436
+ return {
437
+ totalEntries,
438
+ indexExists: true,
439
+ tableName: 'dash_spatial_idx',
440
+ globalBounds: totalEntries > 0 ? {
441
+ minX: bounds.minX,
442
+ maxX: bounds.maxX,
443
+ minY: bounds.minY,
444
+ maxY: bounds.maxY,
445
+ minZ: bounds.minZ,
446
+ maxZ: bounds.maxZ
447
+ } : undefined
448
+ };
449
+ }
450
+ catch (e) {
451
+ return {
452
+ totalEntries: 0,
453
+ indexExists: false,
454
+ error: e instanceof Error ? e.message : 'Spatial index not available'
455
+ };
456
+ }
457
+ }
458
+ /**
459
+ * Get all spatial bounds from the R-Tree index
460
+ */
461
+ getAllSpatialBounds(limit = 100, offset = 0) {
462
+ if (!this.db)
463
+ return [];
464
+ try {
465
+ const rows = this.execute(`
466
+ SELECT
467
+ idx.id as rowid,
468
+ map.item_id as id,
469
+ idx.minX, idx.maxX,
470
+ idx.minY, idx.maxY,
471
+ idx.minZ, idx.maxZ,
472
+ item.content
473
+ FROM dash_spatial_idx idx
474
+ JOIN dash_spatial_map map ON map.rowid = idx.id
475
+ LEFT JOIN dash_items item ON item.id = map.item_id
476
+ LIMIT ? OFFSET ?
477
+ `, [limit, offset]);
478
+ return rows.map((row) => ({
479
+ id: row.id,
480
+ rowid: row.rowid,
481
+ bounds: {
482
+ minX: row.minX,
483
+ maxX: row.maxX,
484
+ minY: row.minY,
485
+ maxY: row.maxY,
486
+ minZ: row.minZ,
487
+ maxZ: row.maxZ
488
+ },
489
+ content: row.content
490
+ }));
491
+ }
492
+ catch (e) {
493
+ console.warn('Failed to get spatial bounds:', e);
494
+ return [];
495
+ }
496
+ }
497
+ /**
498
+ * Get information about active table subscriptions
499
+ */
500
+ getActiveSubscriptions() {
501
+ const subscriptions = [];
502
+ this.tableListeners.forEach((listeners, tableName) => {
503
+ subscriptions.push({
504
+ table: tableName,
505
+ listenerCount: listeners.size
506
+ });
507
+ });
508
+ // Add global listeners
509
+ if (this.listeners.size > 0) {
510
+ subscriptions.push({
511
+ table: '*',
512
+ listenerCount: this.listeners.size
513
+ });
514
+ }
515
+ return subscriptions;
516
+ }
517
+ /**
518
+ * Get table listener counts as a map
519
+ */
520
+ getTableListenerCounts() {
521
+ const counts = {};
522
+ this.tableListeners.forEach((listeners, tableName) => {
523
+ counts[tableName] = listeners.size;
524
+ });
525
+ counts['*'] = this.listeners.size;
526
+ return counts;
527
+ }
528
+ /**
529
+ * Check if the database is ready
530
+ */
531
+ isReady() {
532
+ return this.db !== null;
533
+ }
534
+ /**
535
+ * Get database metadata
536
+ */
537
+ getDatabaseInfo() {
538
+ if (!this.db) {
539
+ return {
540
+ ready: false,
541
+ schemaVersion: this.currentSchemaVersion,
542
+ tableCount: 0
543
+ };
544
+ }
545
+ const tables = this.getAllTables();
546
+ return {
547
+ ready: true,
548
+ schemaVersion: this.currentSchemaVersion,
549
+ tableCount: tables.length,
550
+ tables: tables.map(t => t.name)
551
+ };
552
+ }
255
553
  }
256
554
  export const dash = new DashEngine();
@@ -25,4 +25,83 @@ export declare class HybridProvider extends Observable<string> {
25
25
  private onDocUpdate;
26
26
  private onAwarenessUpdate;
27
27
  destroy(): void;
28
+ /**
29
+ * Get comprehensive connection status
30
+ */
31
+ getConnectionStatus(): ConnectionStatus;
32
+ /**
33
+ * Get all awareness states from connected peers
34
+ */
35
+ getAwarenessStates(): AwarenessState[];
36
+ /**
37
+ * Get the local client ID
38
+ */
39
+ getLocalClientId(): number;
40
+ /**
41
+ * Get connected peer count (excluding local)
42
+ */
43
+ getPeerCount(): number;
44
+ /**
45
+ * Get document state information
46
+ */
47
+ getDocumentState(): DocumentState;
48
+ /**
49
+ * Get a snapshot of the Yjs document for inspection
50
+ */
51
+ getDocumentSnapshot(): DocumentSnapshot;
52
+ /**
53
+ * Set local awareness state for introspection (admin view)
54
+ */
55
+ setLocalAwareness(state: Record<string, unknown>): void;
56
+ /**
57
+ * Get full provider status for debugging
58
+ */
59
+ getProviderStatus(): ProviderStatus;
60
+ }
61
+ export interface ConnectionStatus {
62
+ connected: boolean;
63
+ roomName: string;
64
+ url: string;
65
+ websocket: {
66
+ state: 'connecting' | 'open' | 'closing' | 'closed';
67
+ connected: boolean;
68
+ };
69
+ webTransport: {
70
+ connected: boolean;
71
+ highFrequencyMode: boolean;
72
+ };
73
+ transport: 'WebSocket' | 'WebTransport';
74
+ }
75
+ export interface AwarenessState {
76
+ clientId: number;
77
+ state: Record<string, unknown>;
78
+ isLocal: boolean;
79
+ }
80
+ export interface SharedTypeInfo {
81
+ name: string;
82
+ type: 'YMap' | 'YArray' | 'YText' | 'YXmlFragment' | 'unknown';
83
+ size: number;
84
+ }
85
+ export interface DocumentState {
86
+ clientId: number;
87
+ guid: string;
88
+ stateVectorSize: number;
89
+ updateSize: number;
90
+ sharedTypes: SharedTypeInfo[];
91
+ transactionCount: number;
92
+ gcEnabled: boolean;
93
+ }
94
+ export interface DocumentSnapshot {
95
+ timestamp: string;
96
+ roomName: string;
97
+ content: Record<string, unknown>;
98
+ }
99
+ export interface ProviderStatus {
100
+ connection: ConnectionStatus;
101
+ awareness: {
102
+ localClientId: number;
103
+ peerCount: number;
104
+ states: AwarenessState[];
105
+ };
106
+ document: DocumentState;
28
107
  }
@@ -1,3 +1,4 @@
1
+ import * as Y from 'yjs';
1
2
  import { Observable } from 'lib0/observable';
2
3
  import * as encoding from 'lib0/encoding';
3
4
  import * as decoding from 'lib0/decoding';
@@ -191,4 +192,167 @@ export class HybridProvider extends Observable {
191
192
  this.connected = false;
192
193
  super.destroy();
193
194
  }
195
+ // ============================================
196
+ // INTROSPECTION METHODS FOR DASH-STUDIO
197
+ // ============================================
198
+ /**
199
+ * Get comprehensive connection status
200
+ */
201
+ getConnectionStatus() {
202
+ let wsState = 'closed';
203
+ if (this.ws) {
204
+ switch (this.ws.readyState) {
205
+ case WebSocket.CONNECTING:
206
+ wsState = 'connecting';
207
+ break;
208
+ case WebSocket.OPEN:
209
+ wsState = 'open';
210
+ break;
211
+ case WebSocket.CLOSING:
212
+ wsState = 'closing';
213
+ break;
214
+ case WebSocket.CLOSED:
215
+ wsState = 'closed';
216
+ break;
217
+ }
218
+ }
219
+ return {
220
+ connected: this.connected,
221
+ roomName: this.roomName,
222
+ url: this.url,
223
+ websocket: {
224
+ state: wsState,
225
+ connected: this.ws !== null && this.ws.readyState === WebSocket.OPEN
226
+ },
227
+ webTransport: {
228
+ connected: this.wt !== null,
229
+ highFrequencyMode: this.highFrequencyMode
230
+ },
231
+ transport: this.highFrequencyMode ? 'WebTransport' : 'WebSocket'
232
+ };
233
+ }
234
+ /**
235
+ * Get all awareness states from connected peers
236
+ */
237
+ getAwarenessStates() {
238
+ const states = [];
239
+ const awarenessStates = this.awareness.getStates();
240
+ awarenessStates.forEach((state, clientId) => {
241
+ states.push({
242
+ clientId,
243
+ state,
244
+ isLocal: clientId === this.awareness.clientID
245
+ });
246
+ });
247
+ return states;
248
+ }
249
+ /**
250
+ * Get the local client ID
251
+ */
252
+ getLocalClientId() {
253
+ return this.awareness.clientID;
254
+ }
255
+ /**
256
+ * Get connected peer count (excluding local)
257
+ */
258
+ getPeerCount() {
259
+ const states = this.awareness.getStates();
260
+ return Math.max(0, states.size - 1); // Exclude self
261
+ }
262
+ /**
263
+ * Get document state information
264
+ */
265
+ getDocumentState() {
266
+ const stateVector = Y.encodeStateVector(this.doc);
267
+ const update = Y.encodeStateAsUpdate(this.doc);
268
+ // Get shared types info
269
+ const sharedTypes = [];
270
+ this.doc.share.forEach((type, name) => {
271
+ let typeKind = 'unknown';
272
+ let size = 0;
273
+ if (type instanceof Y.Map) {
274
+ typeKind = 'YMap';
275
+ size = type.size;
276
+ }
277
+ else if (type instanceof Y.Array) {
278
+ typeKind = 'YArray';
279
+ size = type.length;
280
+ }
281
+ else if (type instanceof Y.Text) {
282
+ typeKind = 'YText';
283
+ size = type.length;
284
+ }
285
+ else if (type instanceof Y.XmlFragment) {
286
+ typeKind = 'YXmlFragment';
287
+ size = type.length;
288
+ }
289
+ sharedTypes.push({
290
+ name,
291
+ type: typeKind,
292
+ size
293
+ });
294
+ });
295
+ return {
296
+ clientId: this.doc.clientID,
297
+ guid: this.doc.guid,
298
+ stateVectorSize: stateVector.byteLength,
299
+ updateSize: update.byteLength,
300
+ sharedTypes,
301
+ transactionCount: 0, // Yjs doesn't expose this directly
302
+ gcEnabled: this.doc.gc
303
+ };
304
+ }
305
+ /**
306
+ * Get a snapshot of the Yjs document for inspection
307
+ */
308
+ getDocumentSnapshot() {
309
+ const content = {};
310
+ this.doc.share.forEach((type, name) => {
311
+ try {
312
+ if (type instanceof Y.Map) {
313
+ content[name] = type.toJSON();
314
+ }
315
+ else if (type instanceof Y.Array) {
316
+ content[name] = type.toJSON();
317
+ }
318
+ else if (type instanceof Y.Text) {
319
+ content[name] = type.toString();
320
+ }
321
+ else if (type instanceof Y.XmlFragment) {
322
+ content[name] = type.toString();
323
+ }
324
+ else {
325
+ content[name] = '[unsupported type]';
326
+ }
327
+ }
328
+ catch (e) {
329
+ content[name] = '[error reading content]';
330
+ }
331
+ });
332
+ return {
333
+ timestamp: new Date().toISOString(),
334
+ roomName: this.roomName,
335
+ content
336
+ };
337
+ }
338
+ /**
339
+ * Set local awareness state for introspection (admin view)
340
+ */
341
+ setLocalAwareness(state) {
342
+ this.awareness.setLocalState(state);
343
+ }
344
+ /**
345
+ * Get full provider status for debugging
346
+ */
347
+ getProviderStatus() {
348
+ return {
349
+ connection: this.getConnectionStatus(),
350
+ awareness: {
351
+ localClientId: this.awareness.clientID,
352
+ peerCount: this.getPeerCount(),
353
+ states: this.getAwarenessStates()
354
+ },
355
+ document: this.getDocumentState()
356
+ };
357
+ }
194
358
  }