diff --git a/src/mapeo-manager.js b/src/mapeo-manager.js index d138f5679..d90e78d12 100644 --- a/src/mapeo-manager.js +++ b/src/mapeo-manager.js @@ -67,6 +67,8 @@ export class MapeoManager extends TypedEmitter { /** @type {import('./types.js').CoreStorage} */ #coreStorage #dbFolder + /** @type {string} */ + #projectMigrationsFolder #deviceId #localPeers #invite @@ -78,24 +80,32 @@ export class MapeoManager extends TypedEmitter { * @param {Object} opts * @param {Buffer} opts.rootKey 16-bytes of random data that uniquely identify the device, used to derive a 32-byte master key, which is used to derive all the keypairs used for Mapeo * @param {string} opts.dbFolder Folder for sqlite Dbs. Folder must exist. Use ':memory:' to store everything in-memory + * @param {string} opts.projectMigrationsFolder path for drizzle migrations folder for project database + * @param {string} opts.clientMigrationsFolder path for drizzle migrations folder for client database * @param {string | import('./types.js').CoreStorage} opts.coreStorage Folder for hypercore storage or a function that returns a RandomAccessStorage instance * @param {{ port?: number, logger: import('fastify').FastifyServerOptions['logger'] }} [opts.mediaServerOpts] */ - constructor({ rootKey, dbFolder, coreStorage, mediaServerOpts }) { + constructor({ + rootKey, + dbFolder, + projectMigrationsFolder, + clientMigrationsFolder, + coreStorage, + mediaServerOpts, + }) { super() this.#keyManager = new KeyManager(rootKey) this.#deviceId = getDeviceId(this.#keyManager) this.#l = new Logger({ deviceId: this.#deviceId, ns: 'manager' }) this.#dbFolder = dbFolder + this.#projectMigrationsFolder = projectMigrationsFolder const sqlite = new Database( dbFolder === ':memory:' ? ':memory:' : path.join(dbFolder, CLIENT_SQLITE_FILE_NAME) ) this.#db = drizzle(sqlite) - migrate(this.#db, { - migrationsFolder: new URL('../drizzle/client', import.meta.url).pathname, - }) + migrate(this.#db, { migrationsFolder: clientMigrationsFolder }) this.#localPeers = new LocalPeers({ logger: this.#l }) this.#localPeers.on('peers', (peers) => { @@ -352,6 +362,7 @@ export class MapeoManager extends TypedEmitter { return new MapeoProject({ ...this.#projectStorage(projectId), ...projectKeys, + projectMigrationsFolder: this.#projectMigrationsFolder, keyManager: this.#keyManager, sharedDb: this.#db, sharedIndexWriter: this.#projectSettingsIndexWriter, diff --git a/src/mapeo-project.js b/src/mapeo-project.js index 25bd118fa..5d5852254 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -69,6 +69,7 @@ export class MapeoProject { /** * @param {Object} opts * @param {string} opts.dbPath Path to store project sqlite db. Use `:memory:` for memory storage + * @param {string} opts.projectMigrationsFolder path for drizzle migration folder for project * @param {import('@mapeo/crypto').KeyManager} opts.keyManager mapeo/crypto KeyManager instance * @param {Buffer} opts.projectKey 32-byte public key of the project creator core * @param {Buffer} [opts.projectSecretKey] 32-byte secret key of the project creator core @@ -83,6 +84,7 @@ export class MapeoProject { */ constructor({ dbPath, + projectMigrationsFolder, sharedDb, sharedIndexWriter, coreStorage, @@ -101,9 +103,7 @@ export class MapeoProject { ///////// 1. Setup database const sqlite = new Database(dbPath) const db = drizzle(sqlite) - migrate(db, { - migrationsFolder: new URL('../drizzle/project', import.meta.url).pathname, - }) + migrate(db, { migrationsFolder: projectMigrationsFolder }) ///////// 2. Setup random-access-storage functions diff --git a/test-e2e/capabilities.js b/test-e2e/capabilities.js index 7019857fa..2acdf3f7a 100644 --- a/test-e2e/capabilities.js +++ b/test-e2e/capabilities.js @@ -12,10 +12,17 @@ import { } from '../src/capabilities.js' import { randomBytes } from 'crypto' +const projectMigrationsFolder = new URL('../drizzle/project', import.meta.url) + .pathname +const clientMigrationsFolder = new URL('../drizzle/client', import.meta.url) + .pathname + test('Creator capabilities and role assignment', async (t) => { const rootKey = KeyManager.generateRootKey() const manager = new MapeoManager({ rootKey, + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -44,6 +51,8 @@ test('Creator capabilities and role assignment', async (t) => { test('New device without capabilities', async (t) => { const rootKey = KeyManager.generateRootKey() const manager = new MapeoManager({ + projectMigrationsFolder, + clientMigrationsFolder, rootKey, dbFolder: ':memory:', coreStorage: () => new RAM(), @@ -81,6 +90,8 @@ test('getMany() - on invitor device', async (t) => { const creatorDeviceId = km.getIdentityKeypair().publicKey.toString('hex') const manager = new MapeoManager({ rootKey, + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -118,6 +129,8 @@ test('getMany() - on newly invited device before sync', async (t) => { const km = new KeyManager(rootKey) const deviceId = km.getIdentityKeypair().publicKey.toString('hex') const manager = new MapeoManager({ + projectMigrationsFolder, + clientMigrationsFolder, rootKey, dbFolder: ':memory:', coreStorage: () => new RAM(), diff --git a/test-e2e/core-ownership.js b/test-e2e/core-ownership.js index 85a376ff1..97ec86061 100644 --- a/test-e2e/core-ownership.js +++ b/test-e2e/core-ownership.js @@ -11,6 +11,10 @@ test('CoreOwnership', async (t) => { const km = new KeyManager(rootKey) const manager = new MapeoManager({ rootKey, + projectMigrationsFolder: new URL('../drizzle/project', import.meta.url) + .pathname, + clientMigrationsFolder: new URL('../drizzle/client', import.meta.url) + .pathname, dbFolder: ':memory:', coreStorage: () => new RAM(), }) diff --git a/test-e2e/device-info.js b/test-e2e/device-info.js index d33c2175c..eb3860562 100644 --- a/test-e2e/device-info.js +++ b/test-e2e/device-info.js @@ -5,10 +5,17 @@ import RAM from 'random-access-memory' import { MapeoManager } from '../src/mapeo-manager.js' +const projectMigrationsFolder = new URL('../drizzle/project', import.meta.url) + .pathname +const clientMigrationsFolder = new URL('../drizzle/client', import.meta.url) + .pathname + test('write and read deviceInfo', async (t) => { const rootKey = KeyManager.generateRootKey() const manager = new MapeoManager({ rootKey, + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -27,6 +34,8 @@ test('device info written to projects', (t) => { t.test('when creating project', async (st) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -47,6 +56,8 @@ test('device info written to projects', (t) => { t.test('when adding project', async (st) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -70,6 +81,8 @@ test('device info written to projects', (t) => { t.test('after updating global device info', async (st) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) diff --git a/test-e2e/manager-basic.js b/test-e2e/manager-basic.js index a8c8e1255..274fd5ed3 100644 --- a/test-e2e/manager-basic.js +++ b/test-e2e/manager-basic.js @@ -4,9 +4,16 @@ import { KeyManager } from '@mapeo/crypto' import RAM from 'random-access-memory' import { MapeoManager } from '../src/mapeo-manager.js' +const projectMigrationsFolder = new URL('../drizzle/project', import.meta.url) + .pathname +const clientMigrationsFolder = new URL('../drizzle/client', import.meta.url) + .pathname + test('Managing created projects', async (t) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -109,6 +116,8 @@ test('Managing created projects', async (t) => { test('Managing added projects', async (t) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -175,6 +184,8 @@ test('Managing added projects', async (t) => { test('Managing both created and added projects', async (t) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -213,6 +224,8 @@ test('Managing both created and added projects', async (t) => { test('Manager cannot add project that already exists', async (t) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -240,6 +253,8 @@ test('Consistent storage folders', async (t) => { const storageNames = [] const manager = new MapeoManager({ rootKey: randomBytesSeed('root_key').subarray(0, 16), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: (name) => { storageNames.push(name) @@ -263,6 +278,8 @@ test('Consistent storage folders', async (t) => { test('manager.start() and manager.stop()', async (t) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) diff --git a/test-e2e/manager-invite.js b/test-e2e/manager-invite.js index 4a0b866ce..1b92df406 100644 --- a/test-e2e/manager-invite.js +++ b/test-e2e/manager-invite.js @@ -7,6 +7,11 @@ import { InviteResponse_Decision } from '../src/generated/rpc.js' import { MapeoManager, kRPC } from '../src/mapeo-manager.js' import { replicate } from '../tests/helpers/local-peers.js' +const projectMigrationsFolder = new URL('../drizzle/project', import.meta.url) + .pathname +const clientMigrationsFolder = new URL('../drizzle/client', import.meta.url) + .pathname + test('member invite accepted', async (t) => { t.plan(10) @@ -14,6 +19,8 @@ test('member invite accepted', async (t) => { const creator = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -39,6 +46,8 @@ test('member invite accepted', async (t) => { const joiner = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -107,6 +116,8 @@ test('member invite rejected', async (t) => { const creator = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -133,6 +144,8 @@ test('member invite rejected', async (t) => { const joiner = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) diff --git a/test-e2e/media-server.js b/test-e2e/media-server.js index 39a68eada..f7f68b2a3 100644 --- a/test-e2e/media-server.js +++ b/test-e2e/media-server.js @@ -14,12 +14,19 @@ const BLOB_FIXTURES_DIR = fileURLToPath( new URL('../tests/fixtures/blob-api/', import.meta.url) ) +const projectMigrationsFolder = new URL('../drizzle/project', import.meta.url) + .pathname +const clientMigrationsFolder = new URL('../drizzle/client', import.meta.url) + .pathname + test('retrieving blobs using url', async (t) => { const clock = FakeTimers.install({ shouldAdvanceTime: true }) t.teardown(() => clock.uninstall()) const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -111,6 +118,8 @@ test('retrieving icons using url', async (t) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) diff --git a/test-e2e/members.js b/test-e2e/members.js index 64c28dee9..2d4b6b653 100644 --- a/test-e2e/members.js +++ b/test-e2e/members.js @@ -13,6 +13,11 @@ import { } from '../src/capabilities.js' import { replicate } from '../tests/helpers/local-peers.js' +const projectMigrationsFolder = new URL('../drizzle/project', import.meta.url) + .pathname +const clientMigrationsFolder = new URL('../drizzle/client', import.meta.url) + .pathname + test('getting yourself after creating project', async (t) => { const { manager } = setup() @@ -159,6 +164,8 @@ test('getting invited member after invite accepted', async (t) => { function setup() { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) @@ -180,6 +187,8 @@ function setup() { const otherManager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder, + clientMigrationsFolder, dbFolder: ':memory:', coreStorage: () => new RAM(), }) diff --git a/test-e2e/project-crud.js b/test-e2e/project-crud.js index 4d344fe19..53ae1b2bf 100644 --- a/test-e2e/project-crud.js +++ b/test-e2e/project-crud.js @@ -65,6 +65,10 @@ function getUpdateFixture(value) { test('CRUD operations', async (t) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder: new URL('../drizzle/project', import.meta.url) + .pathname, + clientMigrationsFolder: new URL('../drizzle/client', import.meta.url) + .pathname, dbFolder: ':memory:', coreStorage: () => new RAM(), }) diff --git a/test-e2e/project-settings.js b/test-e2e/project-settings.js index f8a2bb9de..709eae728 100644 --- a/test-e2e/project-settings.js +++ b/test-e2e/project-settings.js @@ -8,6 +8,10 @@ import RAM from 'random-access-memory' test('Project settings create, read, and update operations', async (t) => { const manager = new MapeoManager({ rootKey: KeyManager.generateRootKey(), + projectMigrationsFolder: new URL('../drizzle/project', import.meta.url) + .pathname, + clientMigrationsFolder: new URL('../drizzle/client', import.meta.url) + .pathname, dbFolder: ':memory:', coreStorage: () => new RAM(), }) diff --git a/test-types/data-types.ts b/test-types/data-types.ts index dd332a130..086828c13 100644 --- a/test-types/data-types.ts +++ b/test-types/data-types.ts @@ -28,6 +28,8 @@ const sqlite = new Database(':memory:') const mapeoProject = new MapeoProject({ dbPath: ':memory:', + projectMigrationsFolder: new URL('../drizzle/project', import.meta.url) + .pathname, coreStorage: () => new RAM(), keyManager: new KeyManager(randomBytes(32)), projectKey: randomBytes(32),