@enbox/dwn-sql-store 0.0.1 → 0.0.3
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/README.md +36 -53
- package/dist/esm/src/data-store-sql.js +5 -5
- package/dist/esm/src/data-store-sql.js.map +1 -1
- package/dist/esm/src/dialect/bun-sqlite-adapter.js +46 -0
- package/dist/esm/src/dialect/bun-sqlite-adapter.js.map +1 -0
- package/dist/esm/src/dialect/mysql-dialect.js +1 -1
- package/dist/esm/src/dialect/mysql-dialect.js.map +1 -1
- package/dist/esm/src/dialect/postgres-dialect.js +1 -1
- package/dist/esm/src/dialect/postgres-dialect.js.map +1 -1
- package/dist/esm/src/dialect/sqlite-dialect.js +1 -1
- package/dist/esm/src/dialect/sqlite-dialect.js.map +1 -1
- package/dist/esm/src/main.js +3 -1
- package/dist/esm/src/main.js.map +1 -1
- package/dist/esm/src/message-store-sql.js +54 -25
- package/dist/esm/src/message-store-sql.js.map +1 -1
- package/dist/esm/src/resumable-task-store-sql.js +5 -6
- package/dist/esm/src/resumable-task-store-sql.js.map +1 -1
- package/dist/esm/src/smt-store-sql.js +151 -0
- package/dist/esm/src/smt-store-sql.js.map +1 -0
- package/dist/esm/src/state-index-sql.js +234 -0
- package/dist/esm/src/state-index-sql.js.map +1 -0
- package/dist/esm/src/utils/filter.js +3 -3
- package/dist/esm/src/utils/filter.js.map +1 -1
- package/dist/esm/src/utils/sanitize.js +7 -8
- package/dist/esm/src/utils/sanitize.js.map +1 -1
- package/dist/esm/src/utils/tags.js +3 -6
- package/dist/esm/src/utils/tags.js.map +1 -1
- package/dist/esm/src/utils/transaction.js +3 -21
- package/dist/esm/src/utils/transaction.js.map +1 -1
- package/dist/types/src/data-store-sql.d.ts +3 -4
- package/dist/types/src/data-store-sql.d.ts.map +1 -1
- package/dist/types/src/dialect/bun-sqlite-adapter.d.ts +33 -0
- package/dist/types/src/dialect/bun-sqlite-adapter.d.ts.map +1 -0
- package/dist/types/src/dialect/dialect.d.ts +1 -2
- package/dist/types/src/dialect/dialect.d.ts.map +1 -1
- package/dist/types/src/dialect/mysql-dialect.d.ts +3 -2
- package/dist/types/src/dialect/mysql-dialect.d.ts.map +1 -1
- package/dist/types/src/dialect/postgres-dialect.d.ts +3 -2
- package/dist/types/src/dialect/postgres-dialect.d.ts.map +1 -1
- package/dist/types/src/dialect/sqlite-dialect.d.ts +3 -2
- package/dist/types/src/dialect/sqlite-dialect.d.ts.map +1 -1
- package/dist/types/src/main.d.ts +3 -1
- package/dist/types/src/main.d.ts.map +1 -1
- package/dist/types/src/message-store-sql.d.ts +4 -3
- package/dist/types/src/message-store-sql.d.ts.map +1 -1
- package/dist/types/src/resumable-task-store-sql.d.ts +2 -2
- package/dist/types/src/resumable-task-store-sql.d.ts.map +1 -1
- package/dist/types/src/smt-store-sql.d.ts +37 -0
- package/dist/types/src/smt-store-sql.d.ts.map +1 -0
- package/dist/types/src/state-index-sql.d.ts +44 -0
- package/dist/types/src/state-index-sql.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +24 -42
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/utils/filter.d.ts +3 -3
- package/dist/types/src/utils/filter.d.ts.map +1 -1
- package/dist/types/src/utils/sanitize.d.ts +2 -2
- package/dist/types/src/utils/sanitize.d.ts.map +1 -1
- package/dist/types/src/utils/tags.d.ts +3 -5
- package/dist/types/src/utils/tags.d.ts.map +1 -1
- package/dist/types/src/utils/transaction.d.ts +4 -4
- package/dist/types/src/utils/transaction.d.ts.map +1 -1
- package/package.json +19 -31
- package/src/data-store-sql.ts +11 -9
- package/src/dialect/bun-sqlite-adapter.ts +82 -0
- package/src/dialect/dialect.ts +4 -5
- package/src/dialect/mysql-dialect.ts +8 -6
- package/src/dialect/postgres-dialect.ts +11 -6
- package/src/dialect/sqlite-dialect.ts +11 -6
- package/src/main.ts +4 -2
- package/src/message-store-sql.ts +90 -45
- package/src/resumable-task-store-sql.ts +9 -7
- package/src/smt-store-sql.ts +206 -0
- package/src/state-index-sql.ts +283 -0
- package/src/types.ts +32 -47
- package/src/utils/filter.ts +8 -6
- package/src/utils/sanitize.ts +19 -20
- package/src/utils/tags.ts +6 -7
- package/src/utils/transaction.ts +7 -23
- package/dist/cjs/main.js +0 -3784
- package/dist/cjs/package.json +0 -1
- package/dist/esm/src/event-log-sql.js +0 -169
- package/dist/esm/src/event-log-sql.js.map +0 -1
- package/dist/types/src/event-log-sql.d.ts +0 -24
- package/dist/types/src/event-log-sql.d.ts.map +0 -1
- package/src/event-log-sql.ts +0 -227
package/src/data-store-sql.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Dialect } from './dialect/dialect.js';
|
|
2
|
+
import type { DwnDatabaseType } from './types.js';
|
|
3
|
+
import type { DataStore, DataStoreGetResult, DataStorePutResult } from '@enbox/dwn-sdk-js';
|
|
4
|
+
|
|
5
|
+
import { DataStream } from '@enbox/dwn-sdk-js';
|
|
2
6
|
import { Kysely } from 'kysely';
|
|
3
|
-
import { Readable } from 'readable-stream';
|
|
4
|
-
import { DwnDatabaseType } from './types.js';
|
|
5
|
-
import { Dialect } from './dialect/dialect.js';
|
|
6
7
|
|
|
7
8
|
export class DataStoreSql implements DataStore {
|
|
8
9
|
#dialect: Dialect;
|
|
@@ -78,12 +79,13 @@ export class DataStoreSql implements DataStore {
|
|
|
78
79
|
return undefined;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
const dataBytes = new Uint8Array(result.data);
|
|
81
83
|
return {
|
|
82
84
|
dataSize : result.data.length,
|
|
83
|
-
dataStream : new
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
dataStream : new ReadableStream<Uint8Array>({
|
|
86
|
+
start(controller): void {
|
|
87
|
+
controller.enqueue(dataBytes);
|
|
88
|
+
controller.close();
|
|
87
89
|
}
|
|
88
90
|
}),
|
|
89
91
|
};
|
|
@@ -93,7 +95,7 @@ export class DataStoreSql implements DataStore {
|
|
|
93
95
|
tenant: string,
|
|
94
96
|
recordId: string,
|
|
95
97
|
dataCid: string,
|
|
96
|
-
dataStream:
|
|
98
|
+
dataStream: ReadableStream<Uint8Array>
|
|
97
99
|
): Promise<DataStorePutResult> {
|
|
98
100
|
if (!this.#db) {
|
|
99
101
|
throw new Error(
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter that wraps Bun's built-in SQLite (`bun:sqlite`) to conform to Kysely's
|
|
3
|
+
* `SqliteDatabase` / `SqliteStatement` interfaces, enabling it as a drop-in
|
|
4
|
+
* replacement for `better-sqlite3`.
|
|
5
|
+
*/
|
|
6
|
+
import { Database as BunDatabase } from 'bun:sqlite';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Matches Kysely's SqliteStatement interface.
|
|
10
|
+
*/
|
|
11
|
+
interface KyselySqliteStatement {
|
|
12
|
+
readonly reader: boolean;
|
|
13
|
+
all(parameters: ReadonlyArray<unknown>): unknown[];
|
|
14
|
+
run(parameters: ReadonlyArray<unknown>): {
|
|
15
|
+
changes: number | bigint;
|
|
16
|
+
lastInsertRowid: number | bigint;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Matches Kysely's SqliteDatabase interface.
|
|
22
|
+
*/
|
|
23
|
+
interface KyselySqliteDatabase {
|
|
24
|
+
close(): void;
|
|
25
|
+
prepare(sql: string): KyselySqliteStatement;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** SQL command prefixes that indicate a read/query operation. */
|
|
29
|
+
const READER_PREFIXES = /^\s*(SELECT|PRAGMA|EXPLAIN|WITH)\b/i;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Detects `RETURNING` clause in DML statements (INSERT/UPDATE/DELETE ... RETURNING).
|
|
33
|
+
* These produce rows like a SELECT, so the statement must use `all()` not `run()`.
|
|
34
|
+
*/
|
|
35
|
+
const HAS_RETURNING = /\bRETURNING\b/i;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a Kysely-compatible SQLite database backed by `bun:sqlite`.
|
|
39
|
+
*
|
|
40
|
+
* @param path - File path or `":memory:"` for in-memory database.
|
|
41
|
+
* @param options - Options forwarded to `bun:sqlite`'s Database constructor.
|
|
42
|
+
* - `readonly`: Open in read-only mode.
|
|
43
|
+
* - `create`: Create the file if it doesn't exist (default: true).
|
|
44
|
+
* @returns An object implementing Kysely's `SqliteDatabase` interface.
|
|
45
|
+
*/
|
|
46
|
+
export function createBunSqliteDatabase(
|
|
47
|
+
path: string,
|
|
48
|
+
options?: { readonly?: boolean; create?: boolean },
|
|
49
|
+
): KyselySqliteDatabase {
|
|
50
|
+
const db = new BunDatabase(path, options);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
close(): void {
|
|
54
|
+
db.close();
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
prepare(sql: string): KyselySqliteStatement {
|
|
58
|
+
const stmt = db.prepare(sql);
|
|
59
|
+
const isReader = READER_PREFIXES.test(sql) || HAS_RETURNING.test(sql);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
get reader(): boolean {
|
|
63
|
+
return isReader;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
all(parameters: ReadonlyArray<unknown>): unknown[] {
|
|
67
|
+
return stmt.all(...(parameters as any[]));
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
run(parameters: ReadonlyArray<unknown>): {
|
|
71
|
+
changes: number | bigint;
|
|
72
|
+
lastInsertRowid: number | bigint;
|
|
73
|
+
} {
|
|
74
|
+
return stmt.run(...(parameters as any[])) as {
|
|
75
|
+
changes: number | bigint;
|
|
76
|
+
lastInsertRowid: number | bigint;
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
package/src/dialect/dialect.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
2
|
ColumnBuilderCallback,
|
|
3
3
|
ColumnDataType,
|
|
4
4
|
CreateTableBuilder,
|
|
5
|
-
Dialect as KyselyDialect,
|
|
6
|
-
Kysely,
|
|
7
5
|
InsertObject,
|
|
8
6
|
InsertQueryBuilder,
|
|
9
|
-
|
|
7
|
+
Kysely,
|
|
8
|
+
Dialect as KyselyDialect,
|
|
10
9
|
SelectExpression,
|
|
10
|
+
Selection,
|
|
11
11
|
Transaction,
|
|
12
12
|
} from 'kysely';
|
|
13
13
|
|
|
@@ -64,7 +64,6 @@ export interface Dialect extends KyselyDialect {
|
|
|
64
64
|
*
|
|
65
65
|
* NOTE: the `returning` value must be formatted to return an insertId value.
|
|
66
66
|
* ex. if the generated key is `id` the string should be `id as insertId`.
|
|
67
|
-
* if the generated key is `watermark` the string should be `watermark as insertId`.
|
|
68
67
|
*
|
|
69
68
|
* @returns {InsertQueryBuilder} object to further modify the query or execute it.
|
|
70
69
|
*/
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { Dialect } from './dialect.js';
|
|
2
|
-
import {
|
|
1
|
+
import type { Dialect } from './dialect.js';
|
|
2
|
+
import type {
|
|
3
3
|
AnyColumn,
|
|
4
|
+
ColumnBuilderCallback,
|
|
4
5
|
ColumnDataType,
|
|
5
6
|
CreateTableBuilder,
|
|
6
|
-
ColumnBuilderCallback,
|
|
7
7
|
InsertObject,
|
|
8
8
|
InsertQueryBuilder,
|
|
9
|
+
Kysely,
|
|
9
10
|
SelectExpression,
|
|
10
11
|
Selection,
|
|
11
|
-
Transaction
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Transaction } from 'kysely';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
MysqlDialect as KyselyMysqlDialect
|
|
14
16
|
} from 'kysely';
|
|
15
17
|
|
|
16
18
|
export class MysqlDialect extends KyselyMysqlDialect implements Dialect {
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { Dialect } from './dialect.js';
|
|
2
|
-
import {
|
|
3
|
-
ColumnDataType,
|
|
1
|
+
import type { Dialect } from './dialect.js';
|
|
2
|
+
import type {
|
|
4
3
|
ColumnBuilderCallback,
|
|
4
|
+
ColumnDataType,
|
|
5
5
|
CreateTableBuilder,
|
|
6
6
|
InsertObject,
|
|
7
7
|
InsertQueryBuilder,
|
|
8
8
|
Kysely,
|
|
9
|
-
PostgresDialect as KyselyPostgresDialect,
|
|
10
9
|
SelectExpression,
|
|
11
10
|
Selection,
|
|
12
|
-
Transaction
|
|
11
|
+
Transaction } from 'kysely';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
PostgresDialect as KyselyPostgresDialect
|
|
13
15
|
} from 'kysely';
|
|
14
16
|
|
|
15
17
|
export class PostgresDialect extends KyselyPostgresDialect implements Dialect {
|
|
@@ -51,7 +53,10 @@ export class PostgresDialect extends KyselyPostgresDialect implements Dialect {
|
|
|
51
53
|
referenceColumnName: string,
|
|
52
54
|
onDeleteAction: 'cascade' | 'no action' | 'restrict' | 'set null' | 'set default',
|
|
53
55
|
): CreateTableBuilder<TB & string> {
|
|
54
|
-
return builder.addColumn(
|
|
56
|
+
return builder.addColumn(
|
|
57
|
+
columnName, columnType,
|
|
58
|
+
(col) => col.notNull().references(`${referenceTable}.${referenceColumnName}`).onDelete(onDeleteAction),
|
|
59
|
+
);
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
insertThenReturnId<DB, TB extends keyof DB = keyof DB, SE extends SelectExpression<DB, TB & string> = any>(
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { Dialect } from './dialect.js';
|
|
2
|
-
import {
|
|
1
|
+
import type { Dialect } from './dialect.js';
|
|
2
|
+
import type {
|
|
3
3
|
ColumnBuilderCallback,
|
|
4
4
|
ColumnDataType,
|
|
5
5
|
CreateTableBuilder,
|
|
6
|
-
Kysely,
|
|
7
6
|
InsertObject,
|
|
8
7
|
InsertQueryBuilder,
|
|
8
|
+
Kysely,
|
|
9
9
|
SelectExpression,
|
|
10
10
|
Selection,
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
Transaction } from 'kysely';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
SqliteDialect as KyselySqliteDialect
|
|
13
15
|
} from 'kysely';
|
|
14
16
|
|
|
15
17
|
export class SqliteDialect extends KyselySqliteDialect implements Dialect {
|
|
@@ -58,7 +60,10 @@ export class SqliteDialect extends KyselySqliteDialect implements Dialect {
|
|
|
58
60
|
referenceColumnName: string,
|
|
59
61
|
onDeleteAction: 'cascade' | 'no action' | 'restrict' | 'set null' | 'set default',
|
|
60
62
|
): CreateTableBuilder<TB & string> {
|
|
61
|
-
return builder.addColumn(
|
|
63
|
+
return builder.addColumn(
|
|
64
|
+
columnName, columnType,
|
|
65
|
+
(col) => col.notNull().references(`${referenceTable}.${referenceColumnName}`).onDelete(onDeleteAction),
|
|
66
|
+
);
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
insertThenReturnId<DB, TB extends keyof DB = keyof DB, SE extends SelectExpression<DB, TB & string> = any>(
|
package/src/main.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from './dialect/dialect.js';
|
|
2
|
+
export * from './dialect/bun-sqlite-adapter.js';
|
|
2
3
|
export * from './dialect/mysql-dialect.js';
|
|
3
4
|
export * from './dialect/postgres-dialect.js';
|
|
4
5
|
export * from './dialect/sqlite-dialect.js';
|
|
5
6
|
export * from './data-store-sql.js';
|
|
6
|
-
export * from './
|
|
7
|
+
export * from './state-index-sql.js';
|
|
7
8
|
export * from './message-store-sql.js';
|
|
8
|
-
export * from './resumable-task-store-sql.js';
|
|
9
|
+
export * from './resumable-task-store-sql.js';
|
|
10
|
+
export * from './smt-store-sql.js';
|
package/src/message-store-sql.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { Dialect } from './dialect/dialect.js';
|
|
2
|
+
import type { Transaction } from 'kysely';
|
|
3
|
+
import type { DwnDatabaseType, KeyValues } from './types.js';
|
|
4
|
+
import type {
|
|
5
5
|
Filter,
|
|
6
6
|
GenericMessage,
|
|
7
|
+
MessageSort,
|
|
7
8
|
MessageStore,
|
|
8
9
|
MessageStoreOptions,
|
|
9
|
-
MessageSort,
|
|
10
10
|
Pagination,
|
|
11
|
-
|
|
12
|
-
PaginationCursor,
|
|
13
|
-
} from '@enbox/dwn-sdk-js';
|
|
11
|
+
PaginationCursor } from '@enbox/dwn-sdk-js';
|
|
14
12
|
|
|
15
|
-
import { Kysely, Transaction } from 'kysely';
|
|
16
|
-
import { DwnDatabaseType, KeyValues } from './types.js';
|
|
17
13
|
import * as block from 'multiformats/block';
|
|
18
14
|
import * as cbor from '@ipld/dag-cbor';
|
|
19
|
-
import {
|
|
20
|
-
import { executeWithRetryIfDatabaseIsLocked } from './utils/transaction.js';
|
|
15
|
+
import { executeWithTransaction } from './utils/transaction.js';
|
|
21
16
|
import { extractTagsAndSanitizeIndexes } from './utils/sanitize.js';
|
|
22
17
|
import { filterSelectQuery } from './utils/filter.js';
|
|
23
18
|
import { sha256 } from 'multiformats/hashes/sha2';
|
|
24
19
|
import { TagTables } from './utils/tags.js';
|
|
20
|
+
import {
|
|
21
|
+
DwnInterfaceName,
|
|
22
|
+
DwnMethodName,
|
|
23
|
+
executeUnlessAborted,
|
|
24
|
+
SortDirection
|
|
25
|
+
} from '@enbox/dwn-sdk-js';
|
|
26
|
+
import { Kysely, sql } from 'kysely';
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
export class MessageStoreSql implements MessageStore {
|
|
@@ -31,7 +33,7 @@ export class MessageStoreSql implements MessageStore {
|
|
|
31
33
|
|
|
32
34
|
constructor(dialect: Dialect) {
|
|
33
35
|
this.#dialect = dialect;
|
|
34
|
-
this.#tags = new TagTables(dialect
|
|
36
|
+
this.#tags = new TagTables(dialect);
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
async open(): Promise<void> {
|
|
@@ -57,7 +59,7 @@ export class MessageStoreSql implements MessageStore {
|
|
|
57
59
|
.addColumn('parentId', 'varchar(60)')
|
|
58
60
|
.addColumn('protocol', 'varchar(200)')
|
|
59
61
|
.addColumn('protocolPath', 'varchar(200)')
|
|
60
|
-
.addColumn('contextId', 'varchar(
|
|
62
|
+
.addColumn('contextId', 'varchar(600)')
|
|
61
63
|
.addColumn('schema', 'varchar(200)')
|
|
62
64
|
.addColumn('author', 'varchar(255)')
|
|
63
65
|
.addColumn('recipient', 'varchar(255)')
|
|
@@ -72,32 +74,48 @@ export class MessageStoreSql implements MessageStore {
|
|
|
72
74
|
.addColumn('dataSize', 'integer')
|
|
73
75
|
.addColumn('encodedData', 'text') // we optionally store encoded data if it is below a threshold
|
|
74
76
|
.addColumn('attester', 'text')
|
|
75
|
-
.addColumn('permissionGrantId', 'varchar(60)')
|
|
76
|
-
.addColumn('latest', 'text'); // TODO: obsolete, remove once `dwn-sdk-js` tests are updated
|
|
77
|
+
.addColumn('permissionGrantId', 'varchar(60)');
|
|
77
78
|
|
|
78
79
|
// Add columns that have dialect-specific constraints
|
|
79
80
|
createMessagesTable = this.#dialect.addAutoIncrementingColumn(createMessagesTable, 'id', (col) => col.primaryKey());
|
|
80
81
|
createMessagesTable = this.#dialect.addBlobColumn(createMessagesTable, 'encodedMessageBytes', (col) => col.notNull());
|
|
81
82
|
await createMessagesTable.execute();
|
|
82
83
|
|
|
84
|
+
// add unique index for get() and delete() by messageCid — the most fundamental lookup path
|
|
85
|
+
await this.#db.schema
|
|
86
|
+
.createIndex('index_tenant_messageCid')
|
|
87
|
+
.on(messagesTableName)
|
|
88
|
+
.columns(['tenant', 'messageCid'])
|
|
89
|
+
.unique()
|
|
90
|
+
.execute();
|
|
91
|
+
|
|
83
92
|
// add indexes to the table
|
|
84
93
|
await this.createIndexes(this.#db, messagesTableName, [
|
|
85
|
-
['tenant'], // baseline protection to prevent full table scans across all tenants
|
|
86
94
|
['tenant', 'recordId'], // multiple uses, notably heavily depended by record chain construction for protocol authorization
|
|
95
|
+
['tenant', 'entryId'], // used by fetchInitialRecordsWriteMessage in RecordsRead, RecordsQuery, and RecordsDelete
|
|
87
96
|
['tenant', 'parentId'], // used to walk down hierarchy of records, use cases include purging of records
|
|
88
|
-
['tenant', 'protocol', 'published', 'messageTimestamp'], // index used for basically every external query
|
|
97
|
+
['tenant', 'protocol', 'published', 'messageTimestamp'], // index used for basically every external query
|
|
89
98
|
['tenant', 'interface'], // mainly for fast fetch of ProtocolsConfigure for authorization, not needed if protocol was a DWN Record
|
|
90
|
-
['tenant', '
|
|
91
|
-
['tenant', '
|
|
92
|
-
//
|
|
93
|
-
// ['tenant', 'author'],
|
|
94
|
-
// ['tenant', 'recipient'],
|
|
95
|
-
// ['tenant', 'schema', 'dataFormat'],
|
|
96
|
-
// ['tenant', 'dateCreated'],
|
|
97
|
-
// ['tenant', 'datePublished'],
|
|
98
|
-
// ['tenant', 'messageCid'],
|
|
99
|
-
// ['tenant', 'protocolPath'],
|
|
99
|
+
['tenant', 'permissionGrantId'], // for deleting grant-authorized messages though pending https://github.com/enboxorg/enbox/issues/716
|
|
100
|
+
['tenant', 'dateCreated'], // sort optimization for RecordsQuery with DateSort.CreatedAscending/Descending
|
|
101
|
+
['tenant', 'datePublished'], // sort optimization for RecordsQuery with DateSort.PublishedAscending/Descending
|
|
100
102
|
]);
|
|
103
|
+
|
|
104
|
+
// contextId index created separately because MySQL requires a prefix length to fit within
|
|
105
|
+
// the 3072-byte InnoDB index key limit. contextId is varchar(600) × 4 bytes (utf8mb4) = 2400 bytes,
|
|
106
|
+
// which combined with tenant (255 × 4 = 1020) and messageTimestamp (30 × 4 = 120) = 3540 bytes,
|
|
107
|
+
// exceeding the limit. A prefix of 480 chars (1920 bytes) brings the total to 3060 bytes.
|
|
108
|
+
// contextId values only contain ASCII chars [a-zA-Z0-9/], so a 480-char prefix is sufficient
|
|
109
|
+
// to distinguish most records (covers ~8 nesting levels of 59-char CID segments).
|
|
110
|
+
if (this.#dialect.name === 'MySQL') {
|
|
111
|
+
await sql`CREATE INDEX index_tenant_contextId_messageTimestamp
|
|
112
|
+
ON ${sql.table(messagesTableName)} (tenant, contextId(480), messageTimestamp)`
|
|
113
|
+
.execute(this.#db);
|
|
114
|
+
} else {
|
|
115
|
+
await this.createIndexes(this.#db, messagesTableName, [
|
|
116
|
+
['tenant', 'contextId', 'messageTimestamp'], // expected to be used for common query pattern
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
101
119
|
}
|
|
102
120
|
|
|
103
121
|
// create tags table
|
|
@@ -169,19 +187,19 @@ export class MessageStoreSql implements MessageStore {
|
|
|
169
187
|
const getEncodedData = (message: GenericMessage): { message: GenericMessage, encodedData: string|null} => {
|
|
170
188
|
let encodedData: string|null = null;
|
|
171
189
|
if (message.descriptor.interface === DwnInterfaceName.Records && message.descriptor.method === DwnMethodName.Write) {
|
|
172
|
-
const data =
|
|
173
|
-
if(data) {
|
|
174
|
-
delete
|
|
190
|
+
const data = message.encodedData;
|
|
191
|
+
if (data) {
|
|
192
|
+
delete message.encodedData;
|
|
175
193
|
encodedData = data;
|
|
176
194
|
}
|
|
177
195
|
}
|
|
178
196
|
return { message, encodedData };
|
|
179
197
|
};
|
|
180
198
|
|
|
181
|
-
const { message: messageToProcess, encodedData} = getEncodedData(message);
|
|
199
|
+
const { message: messageToProcess, encodedData } = getEncodedData(message);
|
|
182
200
|
|
|
183
201
|
const encodedMessageBlock = await executeUnlessAborted(
|
|
184
|
-
block.encode({ value: messageToProcess, codec: cbor, hasher: sha256}),
|
|
202
|
+
block.encode({ value: messageToProcess, codec: cbor, hasher: sha256 }),
|
|
185
203
|
options?.signal
|
|
186
204
|
);
|
|
187
205
|
|
|
@@ -192,7 +210,7 @@ export class MessageStoreSql implements MessageStore {
|
|
|
192
210
|
// if any of these inserts would throw, the whole transaction would be rolled back.
|
|
193
211
|
// otherwise it is committed.
|
|
194
212
|
const putMessageOperation = this.constructPutMessageOperation({ tenant, messageCid, encodedMessageBytes, encodedData, indexes });
|
|
195
|
-
await
|
|
213
|
+
await executeWithTransaction(this.#db, putMessageOperation);
|
|
196
214
|
}
|
|
197
215
|
|
|
198
216
|
/**
|
|
@@ -297,9 +315,9 @@ export class MessageStoreSql implements MessageStore {
|
|
|
297
315
|
// filter sanitization takes place within `filterSelectQuery`
|
|
298
316
|
query = filterSelectQuery(filters, query);
|
|
299
317
|
|
|
300
|
-
if(pagination?.cursor !== undefined) {
|
|
318
|
+
if (pagination?.cursor !== undefined) {
|
|
301
319
|
// currently the sort property is explicitly either `dateCreated` | `messageTimestamp` | `datePublished` which are all strings
|
|
302
|
-
// TODO: https://github.com/
|
|
320
|
+
// TODO: https://github.com/enboxorg/enbox/issues/664 to handle the edge case
|
|
303
321
|
const cursorValue = pagination.cursor.value as string;
|
|
304
322
|
const cursorMessageId = pagination.cursor.messageCid;
|
|
305
323
|
|
|
@@ -312,7 +330,7 @@ export class MessageStoreSql implements MessageStore {
|
|
|
312
330
|
|
|
313
331
|
const orderDirection = sortDirection === SortDirection.Ascending ? 'asc' : 'desc';
|
|
314
332
|
// sorting by the provided sort property, the tiebreak is always in ascending order regardless of sort
|
|
315
|
-
query =
|
|
333
|
+
query = query
|
|
316
334
|
.orderBy(sortProperty, orderDirection)
|
|
317
335
|
.orderBy('messageCid', orderDirection);
|
|
318
336
|
|
|
@@ -331,6 +349,33 @@ export class MessageStoreSql implements MessageStore {
|
|
|
331
349
|
return this.processPaginationResults(results, sortProperty, pagination?.limit, options);
|
|
332
350
|
}
|
|
333
351
|
|
|
352
|
+
async count(
|
|
353
|
+
tenant: string,
|
|
354
|
+
filters: Filter[],
|
|
355
|
+
messageSort?: MessageSort,
|
|
356
|
+
options?: MessageStoreOptions
|
|
357
|
+
): Promise<number> {
|
|
358
|
+
if (!this.#db) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
'Connection to database not open. Call `open` before using `count`.'
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
options?.signal?.throwIfAborted();
|
|
365
|
+
|
|
366
|
+
let query = this.#db
|
|
367
|
+
.selectFrom('messageStoreMessages')
|
|
368
|
+
.leftJoin('messageStoreRecordsTags', 'messageStoreRecordsTags.messageInsertId', 'messageStoreMessages.id')
|
|
369
|
+
.select(sql<number>`count(distinct ${sql.ref('messageStoreMessages.messageCid')})`.as('count'))
|
|
370
|
+
.where('tenant', '=', tenant);
|
|
371
|
+
|
|
372
|
+
query = filterSelectQuery(filters, query);
|
|
373
|
+
|
|
374
|
+
const result = await executeUnlessAborted(query.executeTakeFirstOrThrow(), options?.signal);
|
|
375
|
+
|
|
376
|
+
return Number(result.count);
|
|
377
|
+
}
|
|
378
|
+
|
|
334
379
|
async delete(
|
|
335
380
|
tenant: string,
|
|
336
381
|
cid: string,
|
|
@@ -382,9 +427,9 @@ export class MessageStoreSql implements MessageStore {
|
|
|
382
427
|
const message = decodedBlock.value as GenericMessage;
|
|
383
428
|
// If encodedData is stored within the MessageStore we include it in the response.
|
|
384
429
|
// We store encodedData when the data is below a certain threshold.
|
|
385
|
-
// https://github.com/
|
|
430
|
+
// https://github.com/enboxorg/enbox/pull/456
|
|
386
431
|
if (message !== undefined && encodedData !== undefined && encodedData !== null) {
|
|
387
|
-
|
|
432
|
+
message.encodedData = encodedData;
|
|
388
433
|
}
|
|
389
434
|
return message;
|
|
390
435
|
}
|
|
@@ -427,14 +472,14 @@ export class MessageStoreSql implements MessageStore {
|
|
|
427
472
|
private extractSortProperties(
|
|
428
473
|
messageSort?: MessageSort
|
|
429
474
|
):{ property: 'dateCreated' | 'datePublished' | 'messageTimestamp', direction: SortDirection } {
|
|
430
|
-
if(messageSort?.dateCreated !== undefined)
|
|
431
|
-
return
|
|
432
|
-
} else if(messageSort?.datePublished !== undefined) {
|
|
433
|
-
return
|
|
475
|
+
if (messageSort?.dateCreated !== undefined) {
|
|
476
|
+
return { property: 'dateCreated', direction: messageSort.dateCreated };
|
|
477
|
+
} else if (messageSort?.datePublished !== undefined) {
|
|
478
|
+
return { property: 'datePublished', direction: messageSort.datePublished };
|
|
434
479
|
} else if (messageSort?.messageTimestamp !== undefined) {
|
|
435
|
-
return
|
|
480
|
+
return { property: 'messageTimestamp', direction: messageSort.messageTimestamp };
|
|
436
481
|
} else {
|
|
437
|
-
return
|
|
482
|
+
return { property: 'messageTimestamp', direction: SortDirection.Ascending };
|
|
438
483
|
}
|
|
439
484
|
}
|
|
440
485
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import type { Dialect } from './dialect/dialect.js';
|
|
2
|
+
import type { DwnDatabaseType } from './types.js';
|
|
3
|
+
import type { ManagedResumableTask, ResumableTaskStore } from '@enbox/dwn-sdk-js';
|
|
4
|
+
|
|
5
|
+
import { Cid } from '@enbox/dwn-sdk-js';
|
|
6
|
+
import { executeWithTransaction } from './utils/transaction.js';
|
|
4
7
|
import { Kysely } from 'kysely';
|
|
5
|
-
import { Cid, ManagedResumableTask, ResumableTaskStore } from '@enbox/dwn-sdk-js';
|
|
6
8
|
|
|
7
9
|
export class ResumableTaskStoreSql implements ResumableTaskStore {
|
|
8
10
|
private static readonly taskTimeoutInSeconds = 60;
|
|
@@ -30,7 +32,7 @@ export class ResumableTaskStoreSql implements ResumableTaskStore {
|
|
|
30
32
|
|
|
31
33
|
// else create the table and corresponding indexes
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
const table = this.#db.schema
|
|
34
36
|
.createTable(tableName)
|
|
35
37
|
.ifNotExists() // kept to show supported by all dialects in contrast to ifNotExists() below, though not needed due to hasTable() check above
|
|
36
38
|
.addColumn('id', 'varchar(255)', (col) => col.primaryKey())
|
|
@@ -83,7 +85,7 @@ export class ResumableTaskStoreSql implements ResumableTaskStore {
|
|
|
83
85
|
|
|
84
86
|
let tasks: DwnDatabaseType['resumableTasks'][] = [];
|
|
85
87
|
|
|
86
|
-
const operation = async (transaction) => {
|
|
88
|
+
const operation = async (transaction): Promise<void> => {
|
|
87
89
|
tasks = await transaction
|
|
88
90
|
.selectFrom('resumableTasks')
|
|
89
91
|
.selectAll()
|
|
@@ -101,7 +103,7 @@ export class ResumableTaskStoreSql implements ResumableTaskStore {
|
|
|
101
103
|
}
|
|
102
104
|
};
|
|
103
105
|
|
|
104
|
-
await
|
|
106
|
+
await executeWithTransaction(this.#db, operation);
|
|
105
107
|
|
|
106
108
|
const tasksToReturn = tasks.map((task) => {
|
|
107
109
|
return {
|