@aerogel/plugin-local-first 0.0.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.
- package/dist/InruptAuthenticator.lazy-BvpQLGoj-BvpQLGoj.js +9 -0
- package/dist/InruptAuthenticator.lazy-BvpQLGoj-BvpQLGoj.js.map +1 -0
- package/dist/LegacyAuthenticator.lazy-ChFFqP14-ChFFqP14.js +5 -0
- package/dist/LegacyAuthenticator.lazy-ChFFqP14-ChFFqP14.js.map +1 -0
- package/dist/aerogel-plugin-local-first.d.ts +269 -0
- package/dist/aerogel-plugin-local-first.js +1686 -0
- package/dist/aerogel-plugin-local-first.js.map +1 -0
- package/package.json +50 -0
- package/src/components/AGCloudStatus.vue +31 -0
- package/src/components/index.ts +3 -0
- package/src/index.ts +29 -0
- package/src/jobs/Backup.ts +98 -0
- package/src/jobs/Migrate.test.ts +217 -0
- package/src/jobs/Migrate.ts +179 -0
- package/src/jobs/Sync.test.ts +982 -0
- package/src/jobs/Sync.ts +590 -0
- package/src/jobs/mixins/LoadsChildren.ts +210 -0
- package/src/jobs/mixins/LoadsTypeIndex.ts +23 -0
- package/src/jobs/mixins/TracksLocalModels.ts +32 -0
- package/src/lib/SyncQueue.ts +94 -0
- package/src/lib/inference.ts +124 -0
- package/src/lib/mirroring.ts +327 -0
- package/src/lib/utils.ts +21 -0
- package/src/services/Cloud.state.ts +111 -0
- package/src/services/Cloud.ts +450 -0
- package/src/services/DocumentsCache.ts +35 -0
- package/src/testing/cloud.ts +193 -0
- package/src/testing/mocks/Solid.mock.ts +34 -0
- package/src/testing/setup.ts +32 -0
- package/src/testing/stubs/LegacyTask.schema.ts +15 -0
- package/src/testing/stubs/Movie.schema.ts +15 -0
- package/src/testing/stubs/Movie.ts +3 -0
- package/src/testing/stubs/MoviesContainer.ts +20 -0
- package/src/testing/stubs/Task.schema.ts +12 -0
- package/src/testing/stubs/Task.ts +3 -0
- package/src/testing/stubs/TasksList.ts +20 -0
- package/src/testing/stubs/Workspace.ts +15 -0
- package/src/types/shims.d.ts +7 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Job } from '@aerogel/core';
|
|
2
|
+
import { requireEngine } from 'soukai';
|
|
3
|
+
import { requireUrlParentDirectory } from '@noeldemartin/utils';
|
|
4
|
+
import { ignoreModelsCollection, trackModelsCollection } from '@aerogel/plugin-soukai';
|
|
5
|
+
import type { EngineDocument, IndexedDBEngine } from 'soukai';
|
|
6
|
+
import type { SolidModelConstructor } from 'soukai-solid';
|
|
7
|
+
|
|
8
|
+
interface Migration {
|
|
9
|
+
modelClass: SolidModelConstructor;
|
|
10
|
+
local: string;
|
|
11
|
+
remote: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default class Backup extends Job {
|
|
15
|
+
|
|
16
|
+
private urlMigrations: Migration[] = [];
|
|
17
|
+
private collectionMigrations: Migration[] = [];
|
|
18
|
+
|
|
19
|
+
public migrateUrl(modelClass: SolidModelConstructor, local: string, remote: string): void {
|
|
20
|
+
this.urlMigrations.push({
|
|
21
|
+
modelClass,
|
|
22
|
+
local,
|
|
23
|
+
remote,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public migrateCollection(modelClass: SolidModelConstructor, local: string, remote: string): void {
|
|
28
|
+
this.collectionMigrations.push({
|
|
29
|
+
modelClass,
|
|
30
|
+
local,
|
|
31
|
+
remote,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected async run(): Promise<void> {
|
|
36
|
+
const engine = requireEngine<IndexedDBEngine>();
|
|
37
|
+
const collections = await engine.getCollections();
|
|
38
|
+
|
|
39
|
+
for (const collection of collections) {
|
|
40
|
+
const documents = await engine.readMany(collection);
|
|
41
|
+
|
|
42
|
+
for (const [id, document] of Object.entries(documents)) {
|
|
43
|
+
const url = this.replaceUrl(id);
|
|
44
|
+
|
|
45
|
+
await engine.create(requireUrlParentDirectory(url), this.replaceDocumentUrls(document), url);
|
|
46
|
+
await engine.delete(collection, id);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const { modelClass, remote } of this.urlMigrations) {
|
|
51
|
+
trackModelsCollection(modelClass, requireUrlParentDirectory(remote), { refresh: false });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const { modelClass, local, remote } of this.collectionMigrations) {
|
|
55
|
+
ignoreModelsCollection(modelClass, local);
|
|
56
|
+
trackModelsCollection(modelClass, remote, { refresh: false });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected replaceDocumentUrls(document: EngineDocument): EngineDocument {
|
|
61
|
+
for (const [key, value] of Object.entries(document)) {
|
|
62
|
+
if (key === '@id' && typeof value === 'string') {
|
|
63
|
+
document[key] = this.replaceUrl(value);
|
|
64
|
+
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof value !== 'object' || value === null) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
document[key] = this.replaceDocumentUrls(value as EngineDocument);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return document;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected replaceUrl(url: string): string {
|
|
79
|
+
for (const { local, remote } of this.urlMigrations) {
|
|
80
|
+
if (!url.startsWith(local)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return url.replace(local, remote);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const { local, remote } of this.collectionMigrations) {
|
|
88
|
+
if (!url.startsWith(local)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return url.replace(local, remote);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return url;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { Events } from '@aerogel/core';
|
|
3
|
+
import { FakeServer, fakeContainerUrl, fakeDocumentUrl } from '@noeldemartin/testing';
|
|
4
|
+
import { range } from '@noeldemartin/utils';
|
|
5
|
+
import { Solid } from '@aerogel/plugin-solid';
|
|
6
|
+
|
|
7
|
+
import Cloud from '@aerogel/plugin-local-first/services/Cloud';
|
|
8
|
+
import { getRemoteClass } from '@aerogel/plugin-local-first/lib/mirroring';
|
|
9
|
+
|
|
10
|
+
import Workspace from '@aerogel/plugin-local-first/testing/stubs/Workspace';
|
|
11
|
+
import SolidMock from '@aerogel/plugin-local-first/testing/mocks/Solid.mock';
|
|
12
|
+
import LegacyTaskSchema from '@aerogel/plugin-local-first/testing/stubs/LegacyTask.schema';
|
|
13
|
+
import TaskSchema from '@aerogel/plugin-local-first/testing/stubs/Task.schema';
|
|
14
|
+
import Task from '@aerogel/plugin-local-first/testing/stubs/Task';
|
|
15
|
+
import { legacyTaskResponse, setupCloudTests, typeIndexResponse } from '@aerogel/plugin-local-first/testing/cloud';
|
|
16
|
+
|
|
17
|
+
import Migrate from './Migrate';
|
|
18
|
+
|
|
19
|
+
describe('Migrate', () => {
|
|
20
|
+
|
|
21
|
+
beforeEach(setupCloudTests);
|
|
22
|
+
|
|
23
|
+
it('Migrates containers', async () => {
|
|
24
|
+
// Arrange
|
|
25
|
+
const updates: number[] = [];
|
|
26
|
+
const {
|
|
27
|
+
workspace,
|
|
28
|
+
typeIndexUrl,
|
|
29
|
+
documentUrls: [documentUrl],
|
|
30
|
+
} = await setupMigration(1);
|
|
31
|
+
|
|
32
|
+
// Act
|
|
33
|
+
await Cloud.migrate({ onUpdated: (progress) => updates.push(progress) });
|
|
34
|
+
|
|
35
|
+
// Assert - Progress
|
|
36
|
+
expect(updates).toEqual([0, 1]);
|
|
37
|
+
|
|
38
|
+
// Assert - Local models
|
|
39
|
+
expect(Task.rdfsClasses).toEqual(['https://schema.org/Action']);
|
|
40
|
+
|
|
41
|
+
const freshWorkspace = await workspace.fresh();
|
|
42
|
+
|
|
43
|
+
await freshWorkspace.loadRelation('tasks');
|
|
44
|
+
|
|
45
|
+
expect(freshWorkspace.tasks).toHaveLength(1);
|
|
46
|
+
|
|
47
|
+
// Assert - Remote models
|
|
48
|
+
expect(getRemoteClass(Task).rdfsClasses).toEqual(['https://schema.org/Action']);
|
|
49
|
+
|
|
50
|
+
expect(FakeServer.getRequests(documentUrl)).toHaveLength(3);
|
|
51
|
+
expect(FakeServer.getRequests(typeIndexUrl)).toHaveLength(3);
|
|
52
|
+
expect(FakeServer.getRequests()).toHaveLength(6);
|
|
53
|
+
|
|
54
|
+
expect(FakeServer.getRequest('PATCH', documentUrl)?.body).toEqualSparql(`
|
|
55
|
+
DELETE DATA {
|
|
56
|
+
@prefix ical: <http://www.w3.org/2002/12/cal/ical#>.
|
|
57
|
+
|
|
58
|
+
<#it>
|
|
59
|
+
a ical:Vtodo ;
|
|
60
|
+
ical:summary "Migrate schemas" .
|
|
61
|
+
} ;
|
|
62
|
+
|
|
63
|
+
INSERT DATA {
|
|
64
|
+
@prefix schema: <https://schema.org/>.
|
|
65
|
+
|
|
66
|
+
<#it>
|
|
67
|
+
a schema:Action ;
|
|
68
|
+
schema:name "Migrate schemas" .
|
|
69
|
+
} .
|
|
70
|
+
`);
|
|
71
|
+
|
|
72
|
+
expect(FakeServer.getRequest('PATCH', typeIndexUrl)?.body).toEqualSparql(`
|
|
73
|
+
DELETE DATA {
|
|
74
|
+
@prefix solid: <http://www.w3.org/ns/solid/terms#>.
|
|
75
|
+
@prefix ical: <http://www.w3.org/2002/12/cal/ical#>.
|
|
76
|
+
|
|
77
|
+
<#[[registration][.*]]> solid:forClass ical:Vtodo .
|
|
78
|
+
} ;
|
|
79
|
+
|
|
80
|
+
INSERT DATA {
|
|
81
|
+
@prefix solid: <http://www.w3.org/ns/solid/terms#>.
|
|
82
|
+
@prefix schema: <https://schema.org/>.
|
|
83
|
+
|
|
84
|
+
<#[[registration][.*]]> solid:forClass schema:Action .
|
|
85
|
+
} .
|
|
86
|
+
`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('Tracks progress', async () => {
|
|
90
|
+
// Arrange
|
|
91
|
+
const updates: number[] = [];
|
|
92
|
+
|
|
93
|
+
await setupMigration(5);
|
|
94
|
+
|
|
95
|
+
// Act
|
|
96
|
+
await Cloud.migrate({ onUpdated: (progress) => updates.push(progress) });
|
|
97
|
+
|
|
98
|
+
// Assert
|
|
99
|
+
expect(updates).toEqual([0, 0.2, 0.4, 0.6, 0.8, 1]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('Resumes after cancellation', async () => {
|
|
103
|
+
// Arrange
|
|
104
|
+
const updates: number[] = [];
|
|
105
|
+
|
|
106
|
+
await setupMigration(5);
|
|
107
|
+
|
|
108
|
+
// Act - Cancel
|
|
109
|
+
await Cloud.migrate({
|
|
110
|
+
onUpdated: (progress) => {
|
|
111
|
+
updates.push(progress);
|
|
112
|
+
|
|
113
|
+
if (progress > 0.5) {
|
|
114
|
+
Cloud.migrationJob?.cancel();
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Assert - Cancel
|
|
120
|
+
const job = Cloud.migrationJob;
|
|
121
|
+
|
|
122
|
+
expect(job?.cancelled).toBe(true);
|
|
123
|
+
expect(updates).toEqual([0, 0.2, 0.4, 0.6]);
|
|
124
|
+
|
|
125
|
+
// Act - Resume
|
|
126
|
+
await Cloud.migrate({ onUpdated: (progress) => updates.push(progress) });
|
|
127
|
+
|
|
128
|
+
// Assert - Resume
|
|
129
|
+
expect(job?.cancelled).toBe(false);
|
|
130
|
+
expect(Cloud.migrationJob).toBe(null);
|
|
131
|
+
expect(updates).toEqual([0, 0.2, 0.4, 0.6, 0.6, 0.8, 1]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('Resumes after going through serialization', async () => {
|
|
135
|
+
// Arrange
|
|
136
|
+
const updates: number[] = [];
|
|
137
|
+
|
|
138
|
+
await setupMigration(5);
|
|
139
|
+
|
|
140
|
+
// Act - Cancel and serialize
|
|
141
|
+
await Cloud.migrate({
|
|
142
|
+
onUpdated: (progress) => {
|
|
143
|
+
updates.push(progress);
|
|
144
|
+
|
|
145
|
+
if (progress > 0.5) {
|
|
146
|
+
Cloud.migrationJob?.cancel();
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
Cloud.migrationJob = Migrate.restore(JSON.parse(JSON.stringify(Cloud.migrationJob?.serialize())));
|
|
152
|
+
|
|
153
|
+
// Assert - Cancel and serialize
|
|
154
|
+
expect(Cloud.migrationJob?.models).toBeUndefined();
|
|
155
|
+
expect(updates).toEqual([0, 0.2, 0.4, 0.6]);
|
|
156
|
+
|
|
157
|
+
// Act - Resume
|
|
158
|
+
await Cloud.migrate({
|
|
159
|
+
onUpdated: (progress) => updates.push(progress),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Assert - Resume
|
|
163
|
+
expect(Cloud.migrationJob).toBe(null);
|
|
164
|
+
expect(updates).toEqual([0, 0.2, 0.4, 0.6, 0.6, 0.8, 1]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
async function setupMigration(count: number = 1) {
|
|
170
|
+
// Arrange - Mint urls
|
|
171
|
+
const parentContainerUrl = Solid.requireUser().storageUrls[0];
|
|
172
|
+
const containerUrl = fakeContainerUrl({ baseUrl: parentContainerUrl });
|
|
173
|
+
const documentUrls = range(count).map(() => fakeDocumentUrl({ containerUrl })) as [string, ...string[]];
|
|
174
|
+
|
|
175
|
+
// Arrange - Prepare models
|
|
176
|
+
await Task.updateSchema(LegacyTaskSchema);
|
|
177
|
+
|
|
178
|
+
const workspace = await Workspace.at(parentContainerUrl).create({
|
|
179
|
+
url: containerUrl,
|
|
180
|
+
name: 'Tasks',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
for (const documentUrl of documentUrls) {
|
|
184
|
+
await workspace.relatedTasks.create({
|
|
185
|
+
url: `${documentUrl}#it`,
|
|
186
|
+
name: 'Migrate schemas',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Arrange - Prepare responses
|
|
191
|
+
const typeIndexUrl = SolidMock.requireUser().privateTypeIndexUrl ?? '';
|
|
192
|
+
const typeIndex = typeIndexResponse({ [containerUrl]: '<http://www.w3.org/2002/12/cal/ical#Vtodo>' });
|
|
193
|
+
|
|
194
|
+
FakeServer.respondOnce(typeIndexUrl, typeIndex);
|
|
195
|
+
documentUrls.forEach((documentUrl) => {
|
|
196
|
+
FakeServer.respondOnce(documentUrl, legacyTaskResponse('Migrate schemas'));
|
|
197
|
+
FakeServer.respondOnce(documentUrl, legacyTaskResponse('Migrate schemas'));
|
|
198
|
+
FakeServer.respondOnce(documentUrl);
|
|
199
|
+
});
|
|
200
|
+
FakeServer.respondOnce(typeIndexUrl, typeIndex);
|
|
201
|
+
FakeServer.respondOnce(typeIndexUrl);
|
|
202
|
+
|
|
203
|
+
// Arrange - Prepare service
|
|
204
|
+
Cloud.ready = true;
|
|
205
|
+
|
|
206
|
+
Cloud.registerSchemaMigration(Task, TaskSchema);
|
|
207
|
+
|
|
208
|
+
await Cloud.launch();
|
|
209
|
+
await Cloud.register(Workspace);
|
|
210
|
+
await Events.emit('application-ready');
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
workspace,
|
|
214
|
+
typeIndexUrl,
|
|
215
|
+
documentUrls,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { PropertyOperation, isContainer } from 'soukai-solid';
|
|
2
|
+
import { Job } from '@aerogel/core';
|
|
3
|
+
import { isInstanceOf, map, mixed, objectMap, tap } from '@noeldemartin/utils';
|
|
4
|
+
import type { JobListener, JobStatus } from '@aerogel/core';
|
|
5
|
+
import type { Nullable } from '@noeldemartin/utils';
|
|
6
|
+
import type { SolidModel, SolidTypeIndex } from 'soukai-solid';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
clearSchemaMigrations,
|
|
10
|
+
getRemoteClass,
|
|
11
|
+
getSchemaMigration,
|
|
12
|
+
getSchemaMigrations,
|
|
13
|
+
} from '@aerogel/plugin-local-first/lib/mirroring';
|
|
14
|
+
import { getContainerRegisteredClasses } from '@aerogel/plugin-local-first/lib/inference';
|
|
15
|
+
|
|
16
|
+
import LoadsChildren from './mixins/LoadsChildren';
|
|
17
|
+
import LoadsTypeIndex from './mixins/LoadsTypeIndex';
|
|
18
|
+
import TracksLocalModels from './mixins/TracksLocalModels';
|
|
19
|
+
|
|
20
|
+
const BaseJob = Job as typeof Job<JobListener, MigrateJobStatus, MigrateJobStatus>;
|
|
21
|
+
|
|
22
|
+
export interface MigrateJobStatus extends JobStatus {
|
|
23
|
+
children?: MigrateModelJobStatus[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MigrateModelJobStatus extends JobStatus {
|
|
27
|
+
modelUrl: string;
|
|
28
|
+
children?: MigrateModelJobStatus[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default class Migrate extends mixed(BaseJob, [LoadsChildren, LoadsTypeIndex, TracksLocalModels]) {
|
|
32
|
+
|
|
33
|
+
public static restore(status: MigrateJobStatus): Migrate {
|
|
34
|
+
return tap(new Migrate(), (job) => {
|
|
35
|
+
job.status = status;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected _models?: SolidModel[];
|
|
40
|
+
|
|
41
|
+
public get models(): SolidModel[] | undefined {
|
|
42
|
+
return this._models;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public set models(models: SolidModel[] | undefined) {
|
|
46
|
+
if (!models) {
|
|
47
|
+
delete this._models;
|
|
48
|
+
delete this.localModels;
|
|
49
|
+
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const childrenStatus = objectMap(this.status.children ?? [], 'modelUrl');
|
|
54
|
+
|
|
55
|
+
this._models = models;
|
|
56
|
+
this.localModels = map([], 'url');
|
|
57
|
+
this.status.children = models.map(
|
|
58
|
+
(model) =>
|
|
59
|
+
childrenStatus[model.url] ?? {
|
|
60
|
+
modelUrl: model.url,
|
|
61
|
+
completed: false,
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected async run(): Promise<void> {
|
|
67
|
+
if (!this.models) {
|
|
68
|
+
throw new Error('Cant start migration without models');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await this.indexLocalModels(this.models);
|
|
72
|
+
|
|
73
|
+
const modelStatuses = this.status.children ?? [];
|
|
74
|
+
const typeIndex = await this.loadTypeIndex();
|
|
75
|
+
|
|
76
|
+
for (const modelStatus of modelStatuses) {
|
|
77
|
+
const model = this.models.find(({ url }) => modelStatus.modelUrl === url);
|
|
78
|
+
|
|
79
|
+
if (!model || modelStatus.completed) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await this.migrateRegisteredModel(model, modelStatus, typeIndex);
|
|
84
|
+
await this.updateProgress(() => (modelStatus.completed = true));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const [modelClass, schema] of getSchemaMigrations()) {
|
|
88
|
+
await modelClass.updateSchema(schema);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
clearSchemaMigrations();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
protected async migrateRegisteredModel(
|
|
95
|
+
model: SolidModel,
|
|
96
|
+
status: MigrateModelJobStatus,
|
|
97
|
+
typeIndex?: Nullable<SolidTypeIndex>,
|
|
98
|
+
): Promise<void> {
|
|
99
|
+
await this.migrate(model, status);
|
|
100
|
+
|
|
101
|
+
if (!isContainer(model)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const registeredClasses = getContainerRegisteredClasses(model.static());
|
|
106
|
+
const registration = typeIndex?.registrations.find(({ instanceContainer }) => instanceContainer === model.url);
|
|
107
|
+
|
|
108
|
+
if (!typeIndex || !registration || registeredClasses.length === 0) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
registration.setAttribute(
|
|
113
|
+
'forClass',
|
|
114
|
+
registeredClasses
|
|
115
|
+
.map((modelClass) => getSchemaMigration(modelClass)?.rdfsClasses ?? modelClass.rdfsClass)
|
|
116
|
+
.flat(),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
await typeIndex.save();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
protected async migrate(localModel: SolidModel, status: MigrateModelJobStatus): Promise<void> {
|
|
123
|
+
this.assertNotCancelled();
|
|
124
|
+
|
|
125
|
+
await this.migrateChildren(localModel, status);
|
|
126
|
+
|
|
127
|
+
const schema = getSchemaMigration(localModel.static());
|
|
128
|
+
|
|
129
|
+
if (!schema) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const remoteModel = await getRemoteClass(localModel.static()).find(localModel.url);
|
|
134
|
+
const migratedModel = await remoteModel?.migrateSchema(schema);
|
|
135
|
+
|
|
136
|
+
await localModel.migrateSchema(schema, {
|
|
137
|
+
mintOperationUrl: (operation) => {
|
|
138
|
+
return migratedModel?.operations?.find((migratedOperation) => {
|
|
139
|
+
if (
|
|
140
|
+
!isInstanceOf(operation, PropertyOperation) ||
|
|
141
|
+
!isInstanceOf(migratedOperation, PropertyOperation)
|
|
142
|
+
) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
operation.property === migratedOperation.property &&
|
|
148
|
+
operation.date.getTime() === migratedOperation.date.getTime()
|
|
149
|
+
);
|
|
150
|
+
})?.url;
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
protected async migrateChildren(localModel: SolidModel, status: MigrateModelJobStatus): Promise<void> {
|
|
156
|
+
if (!isContainer(localModel)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const children = await this.loadContainedModels(localModel, { ignoreTombstones: true });
|
|
161
|
+
|
|
162
|
+
status.children ??= children.map((child) => ({
|
|
163
|
+
modelUrl: child.url,
|
|
164
|
+
completed: false,
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
for (const childStatus of status.children) {
|
|
168
|
+
const child = children.find(({ url }) => childStatus.modelUrl === url);
|
|
169
|
+
|
|
170
|
+
if (!child || childStatus.completed) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await this.migrate(child, childStatus);
|
|
175
|
+
await this.updateProgress(() => (childStatus.completed = true));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
}
|