@dxos/migrations 0.8.4-main.ae835ea → 0.8.4-main.bc2380dfbc

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/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@dxos/migrations",
3
- "version": "0.8.4-main.ae835ea",
3
+ "version": "0.8.4-main.bc2380dfbc",
4
4
  "description": "",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
- "license": "MIT",
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": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  ".": {
@@ -17,26 +21,24 @@
17
21
  }
18
22
  },
19
23
  "types": "dist/types/src/index.d.ts",
20
- "typesVersions": {
21
- "*": {}
22
- },
23
24
  "files": [
24
25
  "dist",
25
26
  "src"
26
27
  ],
27
28
  "dependencies": {
28
- "@automerge/automerge": "3.1.2",
29
- "@automerge/automerge-repo": "2.4.0",
30
- "@dxos/client": "0.8.4-main.ae835ea",
31
- "@dxos/echo": "0.8.4-main.ae835ea",
32
- "@dxos/echo-db": "0.8.4-main.ae835ea",
33
- "@dxos/echo-protocol": "0.8.4-main.ae835ea",
34
- "@dxos/invariant": "0.8.4-main.ae835ea",
35
- "@dxos/protocols": "0.8.4-main.ae835ea",
36
- "@dxos/util": "0.8.4-main.ae835ea",
37
- "@dxos/log": "0.8.4-main.ae835ea"
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-protocol": "0.8.4-main.bc2380dfbc",
33
+ "@dxos/echo": "0.8.4-main.bc2380dfbc",
34
+ "@dxos/echo-db": "0.8.4-main.bc2380dfbc",
35
+ "@dxos/client": "0.8.4-main.bc2380dfbc",
36
+ "@dxos/invariant": "0.8.4-main.bc2380dfbc",
37
+ "@dxos/log": "0.8.4-main.bc2380dfbc",
38
+ "@dxos/util": "0.8.4-main.bc2380dfbc",
39
+ "@dxos/protocols": "0.8.4-main.bc2380dfbc",
40
+ "@dxos/keys": "0.8.4-main.bc2380dfbc"
38
41
  },
39
- "devDependencies": {},
40
42
  "publishConfig": {
41
43
  "access": "public"
42
44
  }
@@ -8,16 +8,11 @@ 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 { requireTypeReference } from '@dxos/echo/internal';
12
11
  import { type DocHandleProxy, ObjectCore, type RepoProxy, migrateDocument } from '@dxos/echo-db';
13
- import {
14
- type DatabaseDirectory,
15
- type ObjectStructure,
16
- Reference,
17
- SpaceDocVersion,
18
- encodeReference,
19
- } from '@dxos/echo-protocol';
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: encodeReference(requireTypeReference(schema)),
89
+ type: EncodedReference.fromDXN(getSchemaDXN(schema)!),
95
90
  },
96
91
  data: props,
97
92
  meta: {
@@ -102,26 +97,28 @@ export class MigrationBuilder {
102
97
  };
103
98
  const migratedDoc = migrateDocument(oldHandle.doc() as Doc<DatabaseDirectory>, newState);
104
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 encodeReference(Reference.localObjectReference(id));
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._addHandleToFlushList(this._newRoot);
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,7 +160,7 @@ 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];
@@ -179,10 +178,11 @@ export class MigrationBuilder {
179
178
  objects: this._rootDoc.objects,
180
179
  links,
181
180
  });
182
- this._addHandleToFlushList(this._newRoot);
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(requireTypeReference(schema));
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
- this._newLinks[core.id] = newHandle.url;
211
- this._addHandleToFlushList(newHandle);
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(handle: DocHandleProxy<any>): void {
217
- this._flushIds.push(handle.documentId);
217
+ private _addHandleToFlushList(id: DocumentId): void {
218
+ this._flushIds.push(id);
218
219
  }
219
220
  }
@@ -5,10 +5,10 @@
5
5
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
6
6
 
7
7
  import { Client } from '@dxos/client';
8
- import { Filter, type Space } from '@dxos/client/echo';
8
+ import { type Space } from '@dxos/client/echo';
9
9
  import { TestBuilder } from '@dxos/client/testing';
10
- import { Obj, Type } from '@dxos/echo';
11
- import { Expando } from '@dxos/echo/internal';
10
+ import { Filter, Obj } from '@dxos/echo';
11
+ import { TestSchema } from '@dxos/echo/testing';
12
12
 
13
13
  import { Migrations } from './migrations';
14
14
 
@@ -16,17 +16,17 @@ Migrations.define('test', [
16
16
  {
17
17
  version: '1970-01-01',
18
18
  next: async ({ builder }) => {
19
- await builder.addObject(Expando, { namespace: 'test', count: 1 });
19
+ await builder.addObject(TestSchema.Expando, { namespace: 'test', count: 1 });
20
20
  },
21
21
  },
22
22
  {
23
23
  version: '1970-01-02',
24
24
  next: async ({ space, builder }) => {
25
- // TODO(dmaretskyi): Is this intended to query only expando objects? Change to `Filter.type(Expando, { namespace: 'test' })`
26
- const { objects } = await space.db.query(Filter.props<any>({ namespace: 'test' })).run();
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();
27
27
  for (const object of objects) {
28
28
  await builder.migrateObject(object.id, ({ data }) => ({
29
- schema: Expando,
29
+ schema: TestSchema.Expando,
30
30
  props: { namespace: data.namespace, count: 2 },
31
31
  }));
32
32
  }
@@ -35,11 +35,11 @@ Migrations.define('test', [
35
35
  {
36
36
  version: '1970-01-03',
37
37
  next: async ({ space, builder }) => {
38
- // TODO(dmaretskyi): Is this intended to query only expando objects? Change to `Filter.type(Expando, { namespace: 'test' })`
39
- const { objects } = await space.db.query(Filter.props<any>({ namespace: 'test' })).run();
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();
40
40
  for (const object of objects) {
41
41
  await builder.migrateObject(object.id, ({ data }) => ({
42
- schema: Expando,
42
+ schema: TestSchema.Expando,
43
43
  props: { namespace: data.namespace, count: data.count * 3 },
44
44
  }));
45
45
  }
@@ -47,7 +47,8 @@ Migrations.define('test', [
47
47
  },
48
48
  ]);
49
49
 
50
- describe('Migrations', () => {
50
+ // Flaky. We wanna depreacate and rewrite migration builder anyway.
51
+ describe.skip('Migrations', () => {
51
52
  let client: Client;
52
53
  let space: Space;
53
54
 
@@ -68,32 +69,38 @@ describe('Migrations', () => {
68
69
 
69
70
  test('if no migrations have been run before, runs all migrations', async () => {
70
71
  await Migrations.migrate(space);
71
- const { objects } = await space.db.query(Filter.type(Expando, { namespace: 'test' })).run();
72
+ const objects = await space.db.query(Filter.type(TestSchema.Expando, { namespace: 'test' })).run();
72
73
  expect(objects).to.have.length(1);
73
74
  expect(objects[0].count).to.equal(6);
74
75
  expect(space.properties['test.version']).to.equal('1970-01-03');
75
76
  });
76
77
 
77
78
  test('if some migrations have been run before, runs only the remaining migrations', async () => {
78
- space.properties['test.version'] = '1970-01-02';
79
- space.db.add(Obj.make(Type.Expando, { namespace: 'test', count: 5 }));
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();
80
85
  await Migrations.migrate(space);
81
- const { objects } = await space.db.query(Filter.type(Expando, { namespace: 'test' })).run();
86
+ const objects = await space.db.query(Filter.type(TestSchema.Expando, { namespace: 'test' })).run();
82
87
  expect(objects).to.have.length(1);
83
88
  expect(objects[0].count).to.equal(15);
84
89
  expect(space.properties['test.version']).to.equal('1970-01-03');
85
90
  });
86
91
 
87
92
  test('if all migrations have been run before, does nothing', async () => {
88
- space.properties['test.version'] = '1970-01-03';
93
+ Obj.update(space.properties, (obj) => {
94
+ obj['test.version'] = '1970-01-03';
95
+ });
89
96
  await Migrations.migrate(space);
90
- const { objects } = await space.db.query(Filter.type(Expando, { namespace: 'test' })).run();
97
+ const objects = await space.db.query(Filter.type(TestSchema.Expando, { namespace: 'test' })).run();
91
98
  expect(objects).to.have.length(0);
92
99
  });
93
100
 
94
101
  test('if target version is specified, runs only the migrations up to that version', async () => {
95
102
  await Migrations.migrate(space, '1970-01-02');
96
- const { objects } = await space.db.query(Filter.type(Expando, { namespace: 'test' })).run();
103
+ const objects = await space.db.query(Filter.type(TestSchema.Expando, { namespace: 'test' })).run();
97
104
  expect(objects).to.have.length(1);
98
105
  expect(objects[0].count).to.equal(2);
99
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 { type Space, SpaceState, live } from '@dxos/client/echo';
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 _state = live<{ running: string[] }>({ running: [] });
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
- return this._state.running.includes(space.key.toHex());
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
- this._state.running.push(space.key.toHex());
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._state.running.splice(this._state.running.indexOf(space.key.toHex()), 1);
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
  }