@verdant-web/server 3.0.5 → 3.1.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.
Files changed (70) hide show
  1. package/dist/esm/ServerLibrary.test.js +10 -0
  2. package/dist/esm/ServerLibrary.test.js.map +1 -1
  3. package/dist/esm/storage/index.d.ts +1 -0
  4. package/dist/esm/storage/index.js +1 -0
  5. package/dist/esm/storage/index.js.map +1 -1
  6. package/dist/esm/storage/sql/database.d.ts +9 -0
  7. package/dist/esm/storage/sql/database.js +24 -0
  8. package/dist/esm/storage/sql/database.js.map +1 -0
  9. package/dist/esm/storage/sql/sqlStorage.d.ts +2 -1
  10. package/dist/esm/storage/sql/sqlStorage.js +5 -13
  11. package/dist/esm/storage/sql/sqlStorage.js.map +1 -1
  12. package/dist/esm/storage/sql/tables.d.ts +2 -0
  13. package/dist/esm/storage/sqlShard/Databases.d.ts +21 -0
  14. package/dist/esm/storage/sqlShard/Databases.js +64 -0
  15. package/dist/esm/storage/sqlShard/Databases.js.map +1 -0
  16. package/dist/esm/storage/sqlShard/SqlBaselines.d.ts +23 -0
  17. package/dist/esm/storage/sqlShard/SqlBaselines.js +98 -0
  18. package/dist/esm/storage/sqlShard/SqlBaselines.js.map +1 -0
  19. package/dist/esm/storage/sqlShard/SqlFileMetadata.d.ts +18 -0
  20. package/dist/esm/storage/sqlShard/SqlFileMetadata.js +70 -0
  21. package/dist/esm/storage/sqlShard/SqlFileMetadata.js.map +1 -0
  22. package/dist/esm/storage/sqlShard/SqlOperations.d.ts +18 -0
  23. package/dist/esm/storage/sqlShard/SqlOperations.js +104 -0
  24. package/dist/esm/storage/sqlShard/SqlOperations.js.map +1 -0
  25. package/dist/esm/storage/sqlShard/SqlReplicas.d.ts +32 -0
  26. package/dist/esm/storage/sqlShard/SqlReplicas.js +164 -0
  27. package/dist/esm/storage/sqlShard/SqlReplicas.js.map +1 -0
  28. package/dist/esm/storage/sqlShard/_testData/unifiedData.d.ts +30 -0
  29. package/dist/esm/storage/sqlShard/_testData/unifiedData.js +42 -0
  30. package/dist/esm/storage/sqlShard/_testData/unifiedData.js.map +1 -0
  31. package/dist/esm/storage/sqlShard/database.d.ts +6 -0
  32. package/dist/esm/storage/sqlShard/database.js +28 -0
  33. package/dist/esm/storage/sqlShard/database.js.map +1 -0
  34. package/dist/esm/storage/sqlShard/migrations/v1.d.ts +3 -0
  35. package/dist/esm/storage/sqlShard/migrations/v1.js +48 -0
  36. package/dist/esm/storage/sqlShard/migrations/v1.js.map +1 -0
  37. package/dist/esm/storage/sqlShard/migrations.d.ts +5 -0
  38. package/dist/esm/storage/sqlShard/migrations.js +3 -0
  39. package/dist/esm/storage/sqlShard/migrations.js.map +1 -0
  40. package/dist/esm/storage/sqlShard/sqlShardStorage.d.ts +7 -0
  41. package/dist/esm/storage/sqlShard/sqlShardStorage.js +55 -0
  42. package/dist/esm/storage/sqlShard/sqlShardStorage.js.map +1 -0
  43. package/dist/esm/storage/sqlShard/tables.d.ts +38 -0
  44. package/dist/esm/storage/sqlShard/tables.js +2 -0
  45. package/dist/esm/storage/sqlShard/tables.js.map +1 -0
  46. package/dist/esm/storage/sqlShard/transfer.d.ts +7 -0
  47. package/dist/esm/storage/sqlShard/transfer.js +90 -0
  48. package/dist/esm/storage/sqlShard/transfer.js.map +1 -0
  49. package/dist/esm/storage/sqlShard/transfer.test.d.ts +1 -0
  50. package/dist/esm/storage/sqlShard/transfer.test.js +109 -0
  51. package/dist/esm/storage/sqlShard/transfer.test.js.map +1 -0
  52. package/package.json +2 -1
  53. package/src/ServerLibrary.test.ts +10 -0
  54. package/src/storage/index.ts +1 -0
  55. package/src/storage/sql/database.ts +32 -0
  56. package/src/storage/sql/sqlStorage.ts +6 -8
  57. package/src/storage/sql/tables.ts +4 -0
  58. package/src/storage/sqlShard/Databases.ts +80 -0
  59. package/src/storage/sqlShard/SqlBaselines.ts +140 -0
  60. package/src/storage/sqlShard/SqlFileMetadata.ts +98 -0
  61. package/src/storage/sqlShard/SqlOperations.ts +146 -0
  62. package/src/storage/sqlShard/SqlReplicas.ts +237 -0
  63. package/src/storage/sqlShard/_testData/unifiedData.ts +45 -0
  64. package/src/storage/sqlShard/database.ts +37 -0
  65. package/src/storage/sqlShard/migrations/v1.ts +53 -0
  66. package/src/storage/sqlShard/migrations.ts +3 -0
  67. package/src/storage/sqlShard/sqlShardStorage.ts +81 -0
  68. package/src/storage/sqlShard/tables.ts +45 -0
  69. package/src/storage/sqlShard/transfer.test.ts +149 -0
  70. package/src/storage/sqlShard/transfer.ts +123 -0
@@ -0,0 +1,149 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import { openDatabase as openUnifiedDatabase } from '../sql/database.js';
3
+ import {
4
+ randomBaselines,
5
+ randomFileMetadata,
6
+ randomOperations,
7
+ randomReplicaInfo,
8
+ } from './_testData/unifiedData.js';
9
+ import { join, dirname } from 'path';
10
+ import { transferToShards } from './transfer.js';
11
+ import { rm, mkdir } from 'fs/promises';
12
+ import { fileURLToPath } from 'url';
13
+ import {
14
+ DocumentBaselineRow,
15
+ FileMetadataRow,
16
+ OperationHistoryRow,
17
+ ReplicaInfoRow,
18
+ } from '../sql/tables.js';
19
+ import { Kysely } from 'kysely';
20
+
21
+ const testTempDir = join(
22
+ dirname(fileURLToPath(import.meta.url)),
23
+ '_testData',
24
+ 'tmp',
25
+ );
26
+
27
+ describe('sql-shard storage transfer utility', () => {
28
+ const databases = new Array<Kysely<any>>();
29
+ beforeAll(async () => {
30
+ try {
31
+ await rm(testTempDir, { recursive: true });
32
+ } catch (err) {}
33
+ // create a temp dir
34
+ await mkdir(testTempDir, { recursive: true });
35
+ });
36
+ afterAll(async () => {
37
+ try {
38
+ await Promise.all(databases.map((db) => db.destroy()));
39
+ // empty the temp dir
40
+ await rm(testTempDir, { recursive: true });
41
+ } catch (err) {
42
+ // what can you do...
43
+ console.error(err);
44
+ }
45
+ });
46
+
47
+ it('should transfer data from unified database to shards', async () => {
48
+ const unifiedFile = join(testTempDir, 'unified.sqlite');
49
+ const { db: unifiedDb, ready } = openUnifiedDatabase(unifiedFile);
50
+ databases.push(unifiedDb);
51
+ await ready;
52
+ const libraryIds = ['library-1', 'library-2', 'library-3'];
53
+ const randomData = {} as Record<
54
+ string,
55
+ {
56
+ operations: OperationHistoryRow[];
57
+ baselines: DocumentBaselineRow[];
58
+ replicas: ReplicaInfoRow[];
59
+ fileMetadata: FileMetadataRow[];
60
+ }
61
+ >;
62
+ for (const libraryId of libraryIds) {
63
+ randomData[libraryId] = {
64
+ // test a lot of them - more than max sqlite var count.
65
+ // only doing operations here to reduce test time.
66
+ operations: randomOperations(libraryId, 6000),
67
+ baselines: randomBaselines(libraryId),
68
+ replicas: randomReplicaInfo(libraryId),
69
+ fileMetadata: randomFileMetadata(libraryId),
70
+ };
71
+
72
+ // have to batch these...
73
+ for (let i = 0; i < randomData[libraryId].operations.length; i += 1000) {
74
+ const toInsert = randomData[libraryId].operations.slice(i, i + 1000);
75
+ await unifiedDb
76
+ .insertInto('OperationHistory')
77
+ .values(toInsert)
78
+ .execute();
79
+ }
80
+ for (let i = 0; i < randomData[libraryId].baselines.length; i += 1000) {
81
+ const toInsert = randomData[libraryId].baselines.slice(i, i + 1000);
82
+ await unifiedDb
83
+ .insertInto('DocumentBaseline')
84
+ .values(toInsert)
85
+ .execute();
86
+ }
87
+ for (
88
+ let i = 0;
89
+ i < randomData[libraryId].fileMetadata.length;
90
+ i += 1000
91
+ ) {
92
+ const toInsert = randomData[libraryId].fileMetadata.slice(i, i + 1000);
93
+ await unifiedDb.insertInto('FileMetadata').values(toInsert).execute();
94
+ }
95
+ for (let i = 0; i < randomData[libraryId].replicas.length; i += 1000) {
96
+ const toInsert = randomData[libraryId].replicas.slice(i, i + 1000);
97
+ await unifiedDb.insertInto('ReplicaInfo').values(toInsert).execute();
98
+ }
99
+ }
100
+
101
+ const shards = await transferToShards({
102
+ directory: testTempDir,
103
+ file: unifiedFile,
104
+ });
105
+ Object.values(shards).map((db) => databases.push(db));
106
+
107
+ expect(Object.keys(shards).sort()).toEqual(libraryIds.sort());
108
+
109
+ for (const [libraryId, db] of Object.entries(shards)) {
110
+ const operations = await db
111
+ .selectFrom('OperationHistory')
112
+ .selectAll()
113
+ .execute();
114
+ const baselines = await db
115
+ .selectFrom('DocumentBaseline')
116
+ .selectAll()
117
+ .execute();
118
+ const replicas = await db.selectFrom('ReplicaInfo').selectAll().execute();
119
+ const fileMetadata = await db
120
+ .selectFrom('FileMetadata')
121
+ .selectAll()
122
+ .execute();
123
+
124
+ expect(operations.length).toBe(randomData[libraryId].operations.length);
125
+ expect(baselines.length).toBe(randomData[libraryId].baselines.length);
126
+ expect(replicas.length).toBe(randomData[libraryId].replicas.length);
127
+ expect(fileMetadata.length).toBe(
128
+ randomData[libraryId].fileMetadata.length,
129
+ );
130
+
131
+ for (const { oid } of randomData[libraryId].operations) {
132
+ expect(operations.find((op) => op.oid === oid)).toBeDefined();
133
+ }
134
+ for (const { oid } of randomData[libraryId].baselines) {
135
+ expect(
136
+ baselines.find((baseline) => baseline.oid === oid),
137
+ ).toBeDefined();
138
+ }
139
+ for (const { id } of randomData[libraryId].replicas) {
140
+ expect(replicas.find((replica) => replica.id === id)).toBeDefined();
141
+ }
142
+ for (const { fileId } of randomData[libraryId].fileMetadata) {
143
+ expect(
144
+ fileMetadata.find((file) => file.fileId === fileId),
145
+ ).toBeDefined();
146
+ }
147
+ }
148
+ });
149
+ });
@@ -0,0 +1,123 @@
1
+ import { Kysely } from 'kysely';
2
+ import { openDatabase as openUnifiedDatabase } from '../sql/database.js';
3
+ import { Database as UnifiedDatabase } from '../sql/tables.js';
4
+ import { Database } from './tables.js';
5
+ import { openDatabase } from './database.js';
6
+
7
+ const DEFAULT_BATCH_SIZE = 1000;
8
+
9
+ export async function transferToShards({
10
+ file,
11
+ directory,
12
+ batchSize = DEFAULT_BATCH_SIZE,
13
+ }: {
14
+ file: string;
15
+ directory: string;
16
+ batchSize?: number;
17
+ }) {
18
+ console.info(
19
+ `Transferring from unified database (${file}) to shards (${directory})...`,
20
+ );
21
+ const { db: unifiedDb, ready } = openUnifiedDatabase(file);
22
+ await ready;
23
+ const libraries = await unifiedDb
24
+ .selectFrom('ReplicaInfo')
25
+ .select('ReplicaInfo.libraryId')
26
+ .distinct()
27
+ .execute();
28
+
29
+ const dbs = await Promise.all(
30
+ libraries.map(async ({ libraryId }) => {
31
+ const db = await openDatabase(directory, libraryId);
32
+ await copyLibrary(libraryId, unifiedDb, db, batchSize);
33
+ return db;
34
+ }),
35
+ );
36
+
37
+ return libraries.reduce(
38
+ (acc, { libraryId }, i) => {
39
+ acc[libraryId] = dbs[i];
40
+ return acc;
41
+ },
42
+ {} as Record<string, Kysely<Database>>,
43
+ );
44
+ }
45
+
46
+ async function copyLibrary(
47
+ libraryId: string,
48
+ source: Kysely<UnifiedDatabase>,
49
+ dest: Kysely<Database>,
50
+ batchSize: number,
51
+ ) {
52
+ const operations = await source
53
+ .selectFrom('OperationHistory')
54
+ .where('libraryId', '=', libraryId)
55
+ .selectAll()
56
+ .execute();
57
+ const baselines = await source
58
+ .selectFrom('DocumentBaseline')
59
+ .where('libraryId', '=', libraryId)
60
+ .selectAll()
61
+ .execute();
62
+ const replicas = await source
63
+ .selectFrom('ReplicaInfo')
64
+ .where('libraryId', '=', libraryId)
65
+ .selectAll()
66
+ .execute();
67
+ const fileMetadata = await source
68
+ .selectFrom('FileMetadata')
69
+ .where('libraryId', '=', libraryId)
70
+ .selectAll()
71
+ .execute();
72
+
73
+ for (const operation of operations) {
74
+ // @ts-expect-error
75
+ delete operation.libraryId;
76
+ }
77
+ for (const baseline of baselines) {
78
+ // @ts-expect-error
79
+ delete baseline.libraryId;
80
+ }
81
+ for (const replica of replicas) {
82
+ // @ts-expect-error
83
+ delete replica.libraryId;
84
+ }
85
+ for (const metadata of fileMetadata) {
86
+ // @ts-expect-error
87
+ delete metadata.libraryId;
88
+ }
89
+
90
+ await dest.transaction().execute(async (trx) => {
91
+ const actions: Promise<any>[] = [];
92
+ // batch insert data to avoid maximum vars limits
93
+ if (operations.length) {
94
+ for (let i = 0; i < operations.length; i += batchSize) {
95
+ const toInsert = operations.slice(i, i + batchSize);
96
+ actions.push(
97
+ trx.insertInto('OperationHistory').values(toInsert).execute(),
98
+ );
99
+ }
100
+ }
101
+ if (baselines.length) {
102
+ for (let i = 0; i < baselines.length; i += batchSize) {
103
+ const toInsert = baselines.slice(i, i + batchSize);
104
+ actions.push(
105
+ trx.insertInto('DocumentBaseline').values(toInsert).execute(),
106
+ );
107
+ }
108
+ }
109
+ if (replicas.length) {
110
+ for (let i = 0; i < replicas.length; i += batchSize) {
111
+ const toInsert = replicas.slice(i, i + batchSize);
112
+ actions.push(trx.insertInto('ReplicaInfo').values(toInsert).execute());
113
+ }
114
+ }
115
+ if (fileMetadata.length) {
116
+ for (let i = 0; i < fileMetadata.length; i += batchSize) {
117
+ const toInsert = fileMetadata.slice(i, i + batchSize);
118
+ actions.push(trx.insertInto('FileMetadata').values(toInsert).execute());
119
+ }
120
+ }
121
+ await Promise.all(actions);
122
+ });
123
+ }