@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.
- package/dist/src/auth/manager.d.ts +171 -1
- package/dist/src/auth/manager.js +557 -11
- package/dist/src/engine/sqlite.d.ts +135 -0
- package/dist/src/engine/sqlite.js +298 -0
- package/dist/src/sync/hybrid-provider.d.ts +79 -0
- package/dist/src/sync/hybrid-provider.js +164 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -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
|
}
|