@dxos/migrations 0.8.3 → 0.8.4-main.05e74ebcff
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/LICENSE +102 -5
- package/README.md +3 -3
- package/dist/lib/browser/index.mjs +72 -106
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +72 -106
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/migration-builder.d.ts +4 -4
- package/dist/types/src/migration-builder.d.ts.map +1 -1
- package/dist/types/src/migrations.d.ts +2 -1
- package/dist/types/src/migrations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -17
- package/src/migration-builder.ts +33 -32
- package/src/migrations.test.ts +25 -17
- package/src/migrations.ts +14 -6
- package/dist/lib/node/index.cjs +0 -290
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
package/package.json
CHANGED
|
@@ -1,41 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/migrations",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4-main.05e74ebcff",
|
|
4
4
|
"description": "",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
-
"
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
8
12
|
"author": "info@dxos.org",
|
|
9
|
-
"sideEffects":
|
|
13
|
+
"sideEffects": false,
|
|
10
14
|
"type": "module",
|
|
11
15
|
"exports": {
|
|
12
16
|
".": {
|
|
17
|
+
"source": "./src/index.ts",
|
|
13
18
|
"types": "./dist/types/src/index.d.ts",
|
|
14
19
|
"browser": "./dist/lib/browser/index.mjs",
|
|
15
20
|
"node": "./dist/lib/node-esm/index.mjs"
|
|
16
21
|
}
|
|
17
22
|
},
|
|
18
23
|
"types": "dist/types/src/index.d.ts",
|
|
19
|
-
"typesVersions": {
|
|
20
|
-
"*": {}
|
|
21
|
-
},
|
|
22
24
|
"files": [
|
|
23
25
|
"dist",
|
|
24
26
|
"src"
|
|
25
27
|
],
|
|
26
28
|
"dependencies": {
|
|
27
|
-
"@automerge/automerge": "3.
|
|
28
|
-
"@automerge/automerge-repo": "2.0.
|
|
29
|
-
"@
|
|
30
|
-
"@dxos/echo-db": "0.8.
|
|
31
|
-
"@dxos/echo-
|
|
32
|
-
"@dxos/invariant": "0.8.
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/util": "0.8.
|
|
29
|
+
"@automerge/automerge": "3.2.6",
|
|
30
|
+
"@automerge/automerge-repo": "2.6.0-subduction.17",
|
|
31
|
+
"@effect-atom/atom": "^0.5.1",
|
|
32
|
+
"@dxos/echo-db": "0.8.4-main.05e74ebcff",
|
|
33
|
+
"@dxos/echo-protocol": "0.8.4-main.05e74ebcff",
|
|
34
|
+
"@dxos/invariant": "0.8.4-main.05e74ebcff",
|
|
35
|
+
"@dxos/keys": "0.8.4-main.05e74ebcff",
|
|
36
|
+
"@dxos/log": "0.8.4-main.05e74ebcff",
|
|
37
|
+
"@dxos/client": "0.8.4-main.05e74ebcff",
|
|
38
|
+
"@dxos/util": "0.8.4-main.05e74ebcff",
|
|
39
|
+
"@dxos/protocols": "0.8.4-main.05e74ebcff",
|
|
40
|
+
"@dxos/echo": "0.8.4-main.05e74ebcff"
|
|
37
41
|
},
|
|
38
|
-
"devDependencies": {},
|
|
39
42
|
"publishConfig": {
|
|
40
43
|
"access": "public"
|
|
41
44
|
}
|
package/src/migration-builder.ts
CHANGED
|
@@ -2,22 +2,17 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { next as
|
|
5
|
+
import { next as A, type Doc } from '@automerge/automerge';
|
|
6
6
|
import { type AnyDocumentId, type DocumentId } from '@automerge/automerge-repo';
|
|
7
|
-
import
|
|
7
|
+
import type * as Schema from 'effect/Schema';
|
|
8
8
|
|
|
9
9
|
import { type Space } from '@dxos/client/echo';
|
|
10
10
|
import { CreateEpochRequest } from '@dxos/client/halo';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
SpaceDocVersion,
|
|
15
|
-
encodeReference,
|
|
16
|
-
type DatabaseDirectory,
|
|
17
|
-
type ObjectStructure,
|
|
18
|
-
} from '@dxos/echo-protocol';
|
|
19
|
-
import { requireTypeReference } from '@dxos/echo-schema';
|
|
11
|
+
import { type DocHandleProxy, ObjectCore, type RepoProxy, migrateDocument } from '@dxos/echo-db';
|
|
12
|
+
import { type DatabaseDirectory, EncodedReference, type ObjectStructure, SpaceDocVersion } from '@dxos/echo-protocol';
|
|
13
|
+
import { getSchemaDXN } from '@dxos/echo/internal';
|
|
20
14
|
import { invariant } from '@dxos/invariant';
|
|
15
|
+
import { DXN } from '@dxos/keys';
|
|
21
16
|
import { type MaybePromise } from '@dxos/util';
|
|
22
17
|
|
|
23
18
|
/*
|
|
@@ -50,9 +45,9 @@ export class MigrationBuilder {
|
|
|
50
45
|
private _newRoot?: DocHandleProxy<DatabaseDirectory> = undefined;
|
|
51
46
|
|
|
52
47
|
constructor(private readonly _space: Space) {
|
|
53
|
-
this._repo = this._space.db.coreDatabase._repo;
|
|
48
|
+
this._repo = this._space.internal.db.coreDatabase._repo;
|
|
54
49
|
// TODO(wittjosiah): Accessing private API.
|
|
55
|
-
this._rootDoc = (this._space.db.coreDatabase as any)._automergeDocLoader
|
|
50
|
+
this._rootDoc = (this._space.internal.db.coreDatabase as any)._automergeDocLoader
|
|
56
51
|
.getSpaceRootDocHandle()
|
|
57
52
|
.doc() as Doc<DatabaseDirectory>;
|
|
58
53
|
}
|
|
@@ -91,7 +86,7 @@ export class MigrationBuilder {
|
|
|
91
86
|
objects: {
|
|
92
87
|
[id]: {
|
|
93
88
|
system: {
|
|
94
|
-
type:
|
|
89
|
+
type: EncodedReference.fromDXN(getSchemaDXN(schema)!),
|
|
95
90
|
},
|
|
96
91
|
data: props,
|
|
97
92
|
meta: {
|
|
@@ -101,27 +96,29 @@ export class MigrationBuilder {
|
|
|
101
96
|
},
|
|
102
97
|
};
|
|
103
98
|
const migratedDoc = migrateDocument(oldHandle.doc() as Doc<DatabaseDirectory>, newState);
|
|
104
|
-
const newHandle = this._repo.import<DatabaseDirectory>(
|
|
99
|
+
const newHandle = this._repo.import<DatabaseDirectory>(A.save(migratedDoc));
|
|
100
|
+
await newHandle.whenReady();
|
|
101
|
+
invariant(newHandle.url, 'Migrated document URL not available after whenReady');
|
|
105
102
|
this._newLinks[id] = newHandle.url;
|
|
106
|
-
this._addHandleToFlushList(newHandle);
|
|
103
|
+
this._addHandleToFlushList(newHandle.documentId!);
|
|
107
104
|
}
|
|
108
105
|
|
|
109
106
|
async addObject(schema: Schema.Schema.AnyNoContext, props: any): Promise<string> {
|
|
110
|
-
const core = this._createObject({ schema, props });
|
|
107
|
+
const core = await this._createObject({ schema, props });
|
|
111
108
|
return core.id;
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
createReference(id: string) {
|
|
115
|
-
return
|
|
112
|
+
return EncodedReference.fromDXN(DXN.fromLocalObjectId(id));
|
|
116
113
|
}
|
|
117
114
|
|
|
118
115
|
deleteObject(id: string): void {
|
|
119
116
|
this._deleteObjects.push(id);
|
|
120
117
|
}
|
|
121
118
|
|
|
122
|
-
changeProperties(changeFn: (properties: ObjectStructure) => void): void {
|
|
119
|
+
async changeProperties(changeFn: (properties: ObjectStructure) => void): Promise<void> {
|
|
123
120
|
if (!this._newRoot) {
|
|
124
|
-
this._buildNewRoot();
|
|
121
|
+
await this._buildNewRoot();
|
|
125
122
|
}
|
|
126
123
|
invariant(this._newRoot, 'New root not created');
|
|
127
124
|
|
|
@@ -129,7 +126,8 @@ export class MigrationBuilder {
|
|
|
129
126
|
const propertiesStructure = doc.objects?.[this._space.properties.id];
|
|
130
127
|
propertiesStructure && changeFn(propertiesStructure);
|
|
131
128
|
});
|
|
132
|
-
this.
|
|
129
|
+
await this._newRoot.whenReady();
|
|
130
|
+
this._addHandleToFlushList(this._newRoot.documentId!);
|
|
133
131
|
}
|
|
134
132
|
|
|
135
133
|
/**
|
|
@@ -137,13 +135,14 @@ export class MigrationBuilder {
|
|
|
137
135
|
*/
|
|
138
136
|
async _commit(): Promise<void> {
|
|
139
137
|
if (!this._newRoot) {
|
|
140
|
-
this._buildNewRoot();
|
|
138
|
+
await this._buildNewRoot();
|
|
141
139
|
}
|
|
142
140
|
invariant(this._newRoot, 'New root not created');
|
|
143
141
|
|
|
144
142
|
await this._space.db.flush();
|
|
145
143
|
|
|
146
144
|
// Create new epoch.
|
|
145
|
+
invariant(this._newRoot.url, 'New root URL not available');
|
|
147
146
|
await this._space.internal.createEpoch({
|
|
148
147
|
migration: CreateEpochRequest.Migration.REPLACE_AUTOMERGE_ROOT,
|
|
149
148
|
automergeRootUrl: this._newRoot.url,
|
|
@@ -161,14 +160,14 @@ export class MigrationBuilder {
|
|
|
161
160
|
return docHandle;
|
|
162
161
|
}
|
|
163
162
|
|
|
164
|
-
private _buildNewRoot(): void {
|
|
163
|
+
private async _buildNewRoot(): Promise<void> {
|
|
165
164
|
const links = { ...(this._rootDoc.links ?? {}) };
|
|
166
165
|
for (const id of this._deleteObjects) {
|
|
167
166
|
delete links[id];
|
|
168
167
|
}
|
|
169
168
|
|
|
170
169
|
for (const [id, url] of Object.entries(this._newLinks)) {
|
|
171
|
-
links[id] = new
|
|
170
|
+
links[id] = new A.RawString(url);
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
this._newRoot = this._repo.create<DatabaseDirectory>({
|
|
@@ -179,10 +178,11 @@ export class MigrationBuilder {
|
|
|
179
178
|
objects: this._rootDoc.objects,
|
|
180
179
|
links,
|
|
181
180
|
});
|
|
182
|
-
this.
|
|
181
|
+
await this._newRoot.whenReady();
|
|
182
|
+
this._addHandleToFlushList(this._newRoot.documentId!);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
private _createObject({
|
|
185
|
+
private async _createObject({
|
|
186
186
|
id,
|
|
187
187
|
schema,
|
|
188
188
|
props,
|
|
@@ -190,14 +190,14 @@ export class MigrationBuilder {
|
|
|
190
190
|
id?: string;
|
|
191
191
|
schema: Schema.Schema.AnyNoContext;
|
|
192
192
|
props: any;
|
|
193
|
-
}): ObjectCore {
|
|
193
|
+
}): Promise<ObjectCore> {
|
|
194
194
|
const core = new ObjectCore();
|
|
195
195
|
if (id) {
|
|
196
196
|
core.id = id;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
core.initNewObject(props);
|
|
200
|
-
core.setType(
|
|
200
|
+
core.setType(EncodedReference.fromDXN(getSchemaDXN(schema)!));
|
|
201
201
|
const newHandle = this._repo.create<DatabaseDirectory>({
|
|
202
202
|
version: SpaceDocVersion.CURRENT,
|
|
203
203
|
access: {
|
|
@@ -207,13 +207,14 @@ export class MigrationBuilder {
|
|
|
207
207
|
[core.id]: core.getDoc() as ObjectStructure,
|
|
208
208
|
},
|
|
209
209
|
});
|
|
210
|
-
|
|
211
|
-
this.
|
|
210
|
+
await newHandle.whenReady();
|
|
211
|
+
this._newLinks[core.id] = newHandle.url!;
|
|
212
|
+
this._addHandleToFlushList(newHandle.documentId!);
|
|
212
213
|
|
|
213
214
|
return core;
|
|
214
215
|
}
|
|
215
216
|
|
|
216
|
-
private _addHandleToFlushList(
|
|
217
|
-
this._flushIds.push(
|
|
217
|
+
private _addHandleToFlushList(id: DocumentId): void {
|
|
218
|
+
this._flushIds.push(id);
|
|
218
219
|
}
|
|
219
220
|
}
|
package/src/migrations.test.ts
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { Client } from '@dxos/client';
|
|
8
|
-
import {
|
|
8
|
+
import { type Space } from '@dxos/client/echo';
|
|
9
9
|
import { TestBuilder } from '@dxos/client/testing';
|
|
10
|
-
import {
|
|
10
|
+
import { Filter, Obj } from '@dxos/echo';
|
|
11
|
+
import { TestSchema } from '@dxos/echo/testing';
|
|
11
12
|
|
|
12
13
|
import { Migrations } from './migrations';
|
|
13
14
|
|
|
@@ -15,17 +16,17 @@ Migrations.define('test', [
|
|
|
15
16
|
{
|
|
16
17
|
version: '1970-01-01',
|
|
17
18
|
next: async ({ builder }) => {
|
|
18
|
-
await builder.addObject(Expando, { namespace: 'test', count: 1 });
|
|
19
|
+
await builder.addObject(TestSchema.Expando, { namespace: 'test', count: 1 });
|
|
19
20
|
},
|
|
20
21
|
},
|
|
21
22
|
{
|
|
22
23
|
version: '1970-01-02',
|
|
23
24
|
next: async ({ space, builder }) => {
|
|
24
|
-
// TODO(dmaretskyi): Is this intended to query only expando objects? Change to `Filter.type(Expando, { namespace: 'test' })`
|
|
25
|
-
const
|
|
25
|
+
// TODO(dmaretskyi): Is this intended to query only expando objects? Change to `Filter.type(TestSchema.Expando, { namespace: 'test' })`
|
|
26
|
+
const objects = await space.db.query(Filter.props<any>({ namespace: 'test' })).run();
|
|
26
27
|
for (const object of objects) {
|
|
27
28
|
await builder.migrateObject(object.id, ({ data }) => ({
|
|
28
|
-
schema: Expando,
|
|
29
|
+
schema: TestSchema.Expando,
|
|
29
30
|
props: { namespace: data.namespace, count: 2 },
|
|
30
31
|
}));
|
|
31
32
|
}
|
|
@@ -34,11 +35,11 @@ Migrations.define('test', [
|
|
|
34
35
|
{
|
|
35
36
|
version: '1970-01-03',
|
|
36
37
|
next: async ({ space, builder }) => {
|
|
37
|
-
// TODO(dmaretskyi): Is this intended to query only expando objects? Change to `Filter.type(Expando, { namespace: 'test' })`
|
|
38
|
-
const
|
|
38
|
+
// TODO(dmaretskyi): Is this intended to query only expando objects? Change to `Filter.type(TestSchema.Expando, { namespace: 'test' })`
|
|
39
|
+
const objects = await space.db.query(Filter.props<any>({ namespace: 'test' })).run();
|
|
39
40
|
for (const object of objects) {
|
|
40
41
|
await builder.migrateObject(object.id, ({ data }) => ({
|
|
41
|
-
schema: Expando,
|
|
42
|
+
schema: TestSchema.Expando,
|
|
42
43
|
props: { namespace: data.namespace, count: data.count * 3 },
|
|
43
44
|
}));
|
|
44
45
|
}
|
|
@@ -46,7 +47,8 @@ Migrations.define('test', [
|
|
|
46
47
|
},
|
|
47
48
|
]);
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
// Flaky. We wanna depreacate and rewrite migration builder anyway.
|
|
51
|
+
describe.skip('Migrations', () => {
|
|
50
52
|
let client: Client;
|
|
51
53
|
let space: Space;
|
|
52
54
|
|
|
@@ -67,32 +69,38 @@ describe('Migrations', () => {
|
|
|
67
69
|
|
|
68
70
|
test('if no migrations have been run before, runs all migrations', async () => {
|
|
69
71
|
await Migrations.migrate(space);
|
|
70
|
-
const
|
|
72
|
+
const objects = await space.db.query(Filter.type(TestSchema.Expando, { namespace: 'test' })).run();
|
|
71
73
|
expect(objects).to.have.length(1);
|
|
72
74
|
expect(objects[0].count).to.equal(6);
|
|
73
75
|
expect(space.properties['test.version']).to.equal('1970-01-03');
|
|
74
76
|
});
|
|
75
77
|
|
|
76
78
|
test('if some migrations have been run before, runs only the remaining migrations', async () => {
|
|
77
|
-
space.properties
|
|
78
|
-
|
|
79
|
+
Obj.update(space.properties, (obj) => {
|
|
80
|
+
obj['test.version'] = '1970-01-02';
|
|
81
|
+
});
|
|
82
|
+
await space.db.graph.schemaRegistry.register([TestSchema.Expando]);
|
|
83
|
+
space.db.add(Obj.make(TestSchema.Expando, { namespace: 'test', count: 5 }));
|
|
84
|
+
await space.db.flush();
|
|
79
85
|
await Migrations.migrate(space);
|
|
80
|
-
const
|
|
86
|
+
const objects = await space.db.query(Filter.type(TestSchema.Expando, { namespace: 'test' })).run();
|
|
81
87
|
expect(objects).to.have.length(1);
|
|
82
88
|
expect(objects[0].count).to.equal(15);
|
|
83
89
|
expect(space.properties['test.version']).to.equal('1970-01-03');
|
|
84
90
|
});
|
|
85
91
|
|
|
86
92
|
test('if all migrations have been run before, does nothing', async () => {
|
|
87
|
-
space.properties
|
|
93
|
+
Obj.update(space.properties, (obj) => {
|
|
94
|
+
obj['test.version'] = '1970-01-03';
|
|
95
|
+
});
|
|
88
96
|
await Migrations.migrate(space);
|
|
89
|
-
const
|
|
97
|
+
const objects = await space.db.query(Filter.type(TestSchema.Expando, { namespace: 'test' })).run();
|
|
90
98
|
expect(objects).to.have.length(0);
|
|
91
99
|
});
|
|
92
100
|
|
|
93
101
|
test('if target version is specified, runs only the migrations up to that version', async () => {
|
|
94
102
|
await Migrations.migrate(space, '1970-01-02');
|
|
95
|
-
const
|
|
103
|
+
const objects = await space.db.query(Filter.type(TestSchema.Expando, { namespace: 'test' })).run();
|
|
96
104
|
expect(objects).to.have.length(1);
|
|
97
105
|
expect(objects[0].count).to.equal(2);
|
|
98
106
|
expect(space.properties['test.version']).to.equal('1970-01-02');
|
package/src/migrations.ts
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom } from '@effect-atom/atom';
|
|
6
|
+
import * as Registry from '@effect-atom/atom/Registry';
|
|
7
|
+
|
|
8
|
+
import { type Space, SpaceState } from '@dxos/client/echo';
|
|
6
9
|
import { invariant } from '@dxos/invariant';
|
|
7
10
|
import { type MaybePromise } from '@dxos/util';
|
|
8
11
|
|
|
@@ -21,7 +24,8 @@ export type Migration = {
|
|
|
21
24
|
export class Migrations {
|
|
22
25
|
static namespace?: string;
|
|
23
26
|
static migrations: Migration[] = [];
|
|
24
|
-
private static
|
|
27
|
+
private static _registry = Registry.make();
|
|
28
|
+
private static _stateAtom = Atom.make<{ running: string[] }>({ running: [] }).pipe(Atom.keepAlive);
|
|
25
29
|
|
|
26
30
|
static get versionProperty() {
|
|
27
31
|
return this.namespace && `${this.namespace}.version`;
|
|
@@ -32,7 +36,8 @@ export class Migrations {
|
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
static running(space: Space): boolean {
|
|
35
|
-
|
|
39
|
+
const state = this._registry.get(this._stateAtom);
|
|
40
|
+
return state.running.includes(space.key.toHex());
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
static define(namespace: string, migrations: Migration[]): void {
|
|
@@ -52,20 +57,23 @@ export class Migrations {
|
|
|
52
57
|
return false;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
const spaceKey = space.key.toHex();
|
|
61
|
+
const currentState = this._registry.get(this._stateAtom);
|
|
62
|
+
this._registry.set(this._stateAtom, { running: [...currentState.running, spaceKey] });
|
|
56
63
|
if (targetIndex > currentIndex) {
|
|
57
64
|
const migrations = this.migrations.slice(currentIndex, targetIndex);
|
|
58
65
|
for (const migration of migrations) {
|
|
59
66
|
const builder = new MigrationBuilder(space);
|
|
60
67
|
await migration.next({ space, builder });
|
|
61
|
-
builder.changeProperties((propertiesStructure) => {
|
|
68
|
+
await builder.changeProperties((propertiesStructure) => {
|
|
62
69
|
invariant(this.versionProperty, 'Migrations namespace not set');
|
|
63
70
|
propertiesStructure.data[this.versionProperty] = migration.version;
|
|
64
71
|
});
|
|
65
72
|
await builder._commit();
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
|
-
this.
|
|
75
|
+
const finalState = this._registry.get(this._stateAtom);
|
|
76
|
+
this._registry.set(this._stateAtom, { running: finalState.running.filter((key) => key !== spaceKey) });
|
|
69
77
|
|
|
70
78
|
return true;
|
|
71
79
|
}
|