diff --git a/drizzle/project/0000_supreme_forgotten_one.sql b/drizzle/project/0000_careless_magus.sql similarity index 97% rename from drizzle/project/0000_supreme_forgotten_one.sql rename to drizzle/project/0000_careless_magus.sql index 9010ac79..843623ff 100644 --- a/drizzle/project/0000_supreme_forgotten_one.sql +++ b/drizzle/project/0000_careless_magus.sql @@ -85,6 +85,24 @@ CREATE TABLE `icon` ( `forks` text NOT NULL ); --> statement-breakpoint +CREATE TABLE `membership_backlink` ( + `versionId` text PRIMARY KEY NOT NULL +); +--> statement-breakpoint +CREATE TABLE `membership` ( + `docId` text PRIMARY KEY NOT NULL, + `versionId` text NOT NULL, + `schemaName` text NOT NULL, + `createdAt` text NOT NULL, + `createdBy` text NOT NULL, + `updatedAt` text NOT NULL, + `links` text NOT NULL, + `deleted` integer NOT NULL, + `roleId` text NOT NULL, + `fromIndex` integer NOT NULL, + `forks` text NOT NULL +); +--> statement-breakpoint CREATE TABLE `observation_backlink` ( `versionId` text PRIMARY KEY NOT NULL ); @@ -131,24 +149,6 @@ CREATE TABLE `preset` ( `forks` text NOT NULL ); --> statement-breakpoint -CREATE TABLE `role_backlink` ( - `versionId` text PRIMARY KEY NOT NULL -); ---> statement-breakpoint -CREATE TABLE `role` ( - `docId` text PRIMARY KEY NOT NULL, - `versionId` text NOT NULL, - `schemaName` text NOT NULL, - `createdAt` text NOT NULL, - `createdBy` text NOT NULL, - `updatedAt` text NOT NULL, - `links` text NOT NULL, - `deleted` integer NOT NULL, - `roleId` text NOT NULL, - `fromIndex` real NOT NULL, - `forks` text NOT NULL -); ---> statement-breakpoint CREATE TABLE `translation_backlink` ( `versionId` text PRIMARY KEY NOT NULL ); diff --git a/drizzle/project/meta/0000_snapshot.json b/drizzle/project/meta/0000_snapshot.json index 7fa05f6f..3a2d96bf 100644 --- a/drizzle/project/meta/0000_snapshot.json +++ b/drizzle/project/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "5", "dialect": "sqlite", - "id": "f68815a6-4e7a-4ca7-9882-6f31bb34b716", + "id": "aed41a2e-b005-48a3-b3d0-1f02e9defa70", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "coreOwnership_backlink": { @@ -505,8 +505,8 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "observation_backlink": { - "name": "observation_backlink", + "membership_backlink": { + "name": "membership_backlink", "columns": { "versionId": { "name": "versionId", @@ -521,8 +521,8 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "observation": { - "name": "observation", + "membership": { + "name": "membership", "columns": { "docId": { "name": "docId", @@ -580,44 +580,16 @@ "notNull": true, "autoincrement": false }, - "lat": { - "name": "lat", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "lon": { - "name": "lon", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "refs": { - "name": "refs", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "attachments": { - "name": "attachments", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "tags": { - "name": "tags", + "roleId": { + "name": "roleId", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "metadata": { - "name": "metadata", - "type": "text", + "fromIndex": { + "name": "fromIndex", + "type": "integer", "primaryKey": false, "notNull": true, "autoincrement": false @@ -635,8 +607,8 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "preset_backlink": { - "name": "preset_backlink", + "observation_backlink": { + "name": "observation_backlink", "columns": { "versionId": { "name": "versionId", @@ -651,8 +623,8 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "preset": { - "name": "preset", + "observation": { + "name": "observation", "columns": { "docId": { "name": "docId", @@ -710,57 +682,43 @@ "notNull": true, "autoincrement": false }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "geometry": { - "name": "geometry", - "type": "text", + "lat": { + "name": "lat", + "type": "real", "primaryKey": false, - "notNull": true, + "notNull": false, "autoincrement": false }, - "tags": { - "name": "tags", - "type": "text", + "lon": { + "name": "lon", + "type": "real", "primaryKey": false, - "notNull": true, + "notNull": false, "autoincrement": false }, - "addTags": { - "name": "addTags", + "refs": { + "name": "refs", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "removeTags": { - "name": "removeTags", + "attachments": { + "name": "attachments", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "fieldIds": { - "name": "fieldIds", + "tags": { + "name": "tags", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "iconId": { - "name": "iconId", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "terms": { - "name": "terms", + "metadata": { + "name": "metadata", "type": "text", "primaryKey": false, "notNull": true, @@ -779,8 +737,8 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "role_backlink": { - "name": "role_backlink", + "preset_backlink": { + "name": "preset_backlink", "columns": { "versionId": { "name": "versionId", @@ -795,8 +753,8 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "role": { - "name": "role", + "preset": { + "name": "preset", "columns": { "docId": { "name": "docId", @@ -854,16 +812,58 @@ "notNull": true, "autoincrement": false }, - "roleId": { - "name": "roleId", + "name": { + "name": "name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "fromIndex": { - "name": "fromIndex", - "type": "real", + "geometry": { + "name": "geometry", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "addTags": { + "name": "addTags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "removeTags": { + "name": "removeTags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fieldIds": { + "name": "fieldIds", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconId": { + "name": "iconId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms": { + "name": "terms", + "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false diff --git a/drizzle/project/meta/_journal.json b/drizzle/project/meta/_journal.json index 37f6585d..7dbe4690 100644 --- a/drizzle/project/meta/_journal.json +++ b/drizzle/project/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "5", - "when": 1707965473123, - "tag": "0000_supreme_forgotten_one", + "when": 1712654482206, + "tag": "0000_careless_magus", "breakpoints": true } ] diff --git a/mapeo-schema-3.0.0-next.14.tgz b/mapeo-schema-3.0.0-next.14.tgz new file mode 100644 index 00000000..5e961022 Binary files /dev/null and b/mapeo-schema-3.0.0-next.14.tgz differ diff --git a/package-lock.json b/package-lock.json index c7250fd0..798bc092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@fastify/type-provider-typebox": "^3.3.0", "@hyperswarm/secret-stream": "^6.1.2", "@mapeo/crypto": "1.0.0-alpha.10", - "@mapeo/schema": "3.0.0-next.14", + "@mapeo/schema": "file:mapeo-schema-3.0.0-next.14.tgz", "@mapeo/sqlite-indexer": "1.0.0-alpha.8", "@sinclair/typebox": "^0.29.6", "b4a": "^1.6.3", @@ -819,8 +819,9 @@ }, "node_modules/@mapeo/schema": { "version": "3.0.0-next.14", - "resolved": "https://registry.npmjs.org/@mapeo/schema/-/schema-3.0.0-next.14.tgz", - "integrity": "sha512-i0AUHbwMxUyggk6SDURxLPXOVCWlLAszSKUYm2fviQXYrGNcUALniw8JBn3x5jzfCFW1xrTUNhIcnt4IuF95mA==", + "resolved": "file:mapeo-schema-3.0.0-next.14.tgz", + "integrity": "sha512-m1883dVXuSSdGPTNNdF4CoOLYWLhsmszwTvptrI9Ap+QRvZnMB3aQmwHRLMxaZ3KBU5W7jjy2Vt9brUhvsRPow==", + "license": "MIT", "dependencies": { "@json-schema-tools/dereferencer": "^1.6.1", "ajv": "^8.12.0", diff --git a/package.json b/package.json index aea06b48..4f467e41 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "@fastify/type-provider-typebox": "^3.3.0", "@hyperswarm/secret-stream": "^6.1.2", "@mapeo/crypto": "1.0.0-alpha.10", - "@mapeo/schema": "3.0.0-next.14", + "@mapeo/schema": "file:mapeo-schema-3.0.0-next.14.tgz", "@mapeo/sqlite-indexer": "1.0.0-alpha.8", "@sinclair/typebox": "^0.29.6", "b4a": "^1.6.3", diff --git a/src/datastore/index.js b/src/datastore/index.js index b8f7e8d3..61f39054 100644 --- a/src/datastore/index.js +++ b/src/datastore/index.js @@ -37,7 +37,7 @@ const NAMESPACE_SCHEMAS = /** @type {const} */ ({ 'deviceInfo', 'icon', ], - auth: ['coreOwnership', 'role'], + auth: ['coreOwnership', 'membership'], }) /** diff --git a/src/mapeo-project.js b/src/mapeo-project.js index d5e3ab68..01fc305b 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -21,7 +21,7 @@ import { fieldTable, observationTable, presetTable, - roleTable, + membershipTable, iconTable, } from './schema/project.js' import { @@ -152,7 +152,7 @@ export class MapeoProject extends TypedEmitter { presetTable, fieldTable, coreOwnershipTable, - roleTable, + membershipTable, deviceInfoTable, iconTable, ], @@ -220,9 +220,9 @@ export class MapeoProject extends TypedEmitter { table: coreOwnershipTable, db, }), - role: new DataType({ + membership: new DataType({ dataStore: this.#dataStores.auth, - table: roleTable, + table: membershipTable, db, }), deviceInfo: new DataType({ @@ -248,7 +248,7 @@ export class MapeoProject extends TypedEmitter { identityKeypair, }) this.#roles = new Roles({ - dataType: this.#dataTypes.role, + membership: this.#dataTypes.membership, coreOwnership: this.#coreOwnership, coreManager: this.#coreManager, projectKey: projectKey, @@ -559,7 +559,7 @@ export class MapeoProject extends TypedEmitter { async [kProjectLeave]() { // 1. Check that the device can leave the project - const roleDocs = await this.#dataTypes.role.getMany() + const roleDocs = await this.#dataTypes.membership.getMany() // 1.1 Check that we are not blocked in the project const ownRole = roleDocs.find(({ docId }) => this.#deviceId === docId) diff --git a/src/roles.js b/src/roles.js index eda0b224..cd1d4e04 100644 --- a/src/roles.js +++ b/src/roles.js @@ -214,7 +214,7 @@ export const ROLES = { } export class Roles { - #dataType + #membership #coreOwnership #coreManager #projectCreatorAuthCoreId @@ -227,18 +227,24 @@ export class Roles { * @param {object} opts * @param {import('./datatype/index.js').DataType< * import('./datastore/index.js').DataStore<'auth'>, - * typeof import('./schema/project.js').roleTable, - * 'role', - * import('@mapeo/schema').Role, - * import('@mapeo/schema').RoleValue - * >} opts.dataType + * typeof import('./schema/project.js').membershipTable, + * 'membership', + * import('@mapeo/schema').Membership, + * import('@mapeo/schema').MembershipValue + * >} opts.membership * @param {import('./core-ownership.js').CoreOwnership} opts.coreOwnership * @param {import('./core-manager/index.js').CoreManager} opts.coreManager * @param {Buffer} opts.projectKey * @param {Buffer} opts.deviceKey public key of this device */ - constructor({ dataType, coreOwnership, coreManager, projectKey, deviceKey }) { - this.#dataType = dataType + constructor({ + membership, + coreOwnership, + coreManager, + projectKey, + deviceKey, + }) { + this.#membership = membership this.#coreOwnership = coreOwnership this.#coreManager = coreManager this.#projectCreatorAuthCoreId = projectKey.toString('hex') @@ -255,8 +261,8 @@ export class Roles { /** @type {string} */ let roleId try { - const roleAssignment = await this.#dataType.getByDocId(deviceId) - roleId = roleAssignment.roleId + const membership = await this.#membership.getByDocId(deviceId) + roleId = membership.roleId } catch (e) { // The project creator will have the creator role const authCoreId = await this.#coreOwnership.getCoreId(deviceId, 'auth') @@ -283,7 +289,7 @@ export class Roles { * @returns {Promise>} Map of deviceId to Role */ async getAll() { - const roles = await this.#dataType.getMany() + const memberships = await this.#membership.getMany() /** @type {Map} */ const result = new Map() /** @type {undefined | string} */ @@ -300,17 +306,17 @@ export class Roles { // them in the returned map } - for (const role of roles) { - if (!isRoleId(role.roleId)) { + for (const member of memberships) { + if (!isRoleId(member.roleId)) { console.error("Found a value that wasn't a role ID") continue } - if (role.roleId === CREATOR_ROLE_ID) { + if (member.roleId === CREATOR_ROLE_ID) { console.error('Unexpected creator role') continue } - const deviceId = role.docId - result.set(deviceId, ROLES[role.roleId]) + const deviceId = member.docId + result.set(deviceId, ROLES[member.roleId]) } const includesSelf = result.has(this.#ownDeviceId) if (!includesSelf) { @@ -369,22 +375,22 @@ export class Roles { } } - const existingRoleDoc = await this.#dataType + const existingMembershipDoc = await this.#membership .getByDocId(deviceId) .catch(() => null) - if (existingRoleDoc) { - await this.#dataType.update( - [existingRoleDoc.versionId, ...existingRoleDoc.forks], + if (existingMembershipDoc) { + await this.#membership.update( + [existingMembershipDoc.versionId, ...existingMembershipDoc.forks], { - schemaName: 'role', + schemaName: 'membership', roleId, fromIndex, } ) } else { - await this.#dataType[kCreateWithDocId](deviceId, { - schemaName: 'role', + await this.#membership[kCreateWithDocId](deviceId, { + schemaName: 'membership', roleId, fromIndex, }) diff --git a/src/schema/project.js b/src/schema/project.js index 070b2764..6108bb4c 100644 --- a/src/schema/project.js +++ b/src/schema/project.js @@ -20,7 +20,10 @@ export const coreOwnershipTable = sqliteTable( 'coreOwnership', toColumns(schemas.coreOwnership) ) -export const roleTable = sqliteTable('role', toColumns(schemas.role)) +export const membershipTable = sqliteTable( + 'membership', + toColumns(schemas.membership) +) export const deviceInfoTable = sqliteTable( 'deviceInfo', toColumns(schemas.deviceInfo) @@ -32,7 +35,7 @@ export const observationBacklinkTable = backlinkTable(observationTable) export const presetBacklinkTable = backlinkTable(presetTable) export const fieldBacklinkTable = backlinkTable(fieldTable) export const coreOwnershipBacklinkTable = backlinkTable(coreOwnershipTable) -export const roleBacklinkTable = backlinkTable(roleTable) +export const membershipBacklinkTable = backlinkTable(membershipTable) export const deviceInfoBacklinkTable = backlinkTable(deviceInfoTable) export const iconBacklinkTable = backlinkTable(iconTable) diff --git a/test-e2e/members.js b/test-e2e/members.js index caf55f39..655178fa 100644 --- a/test-e2e/members.js +++ b/test-e2e/members.js @@ -363,18 +363,18 @@ test('roles - assignRole()', async (t) => { ) await t.test('invitor updates invitee role to coordinator', async (st) => { - const roleRecordBefore = await invitorProject[kDataTypes].role.getByDocId( - invitee.deviceId - ) + const roleRecordBefore = await invitorProject[ + kDataTypes + ].membership.getByDocId(invitee.deviceId) await invitorProject.$member.assignRole( invitee.deviceId, COORDINATOR_ROLE_ID ) - const roleRecordAfter = await invitorProject[kDataTypes].role.getByDocId( - invitee.deviceId - ) + const roleRecordAfter = await invitorProject[ + kDataTypes + ].membership.getByDocId(invitee.deviceId) t.alike( roleRecordAfter.links, @@ -399,15 +399,15 @@ test('roles - assignRole()', async (t) => { }) await t.test('invitee updates own role to member', async (st) => { - const roleRecordBefore = await inviteeProject[kDataTypes].role.getByDocId( - invitee.deviceId - ) + const roleRecordBefore = await inviteeProject[ + kDataTypes + ].membership.getByDocId(invitee.deviceId) await inviteeProject.$member.assignRole(invitee.deviceId, MEMBER_ROLE_ID) - const roleRecordAfter = await inviteeProject[kDataTypes].role.getByDocId( - invitee.deviceId - ) + const roleRecordAfter = await inviteeProject[ + kDataTypes + ].membership.getByDocId(invitee.deviceId) t.alike( roleRecordAfter.links, @@ -474,9 +474,9 @@ test('roles - assignRole() with forked role', async (t) => { // 3. Verify that invitee2 role is now forked - const invitee2RoleForked = await invitee1Project[kDataTypes].role.getByDocId( - invitee2.deviceId - ) + const invitee2RoleForked = await invitee1Project[ + kDataTypes + ].membership.getByDocId(invitee2.deviceId) t.is(invitee2RoleForked.forks.length, 1, 'invitee2 role has one fork') // 4. Assign role again, which should merge forked records @@ -485,9 +485,9 @@ test('roles - assignRole() with forked role', async (t) => { await waitForSync(projects, 'initial') - const invitee2RoleMerged = await invitee1Project[kDataTypes].role.getByDocId( - invitee2.deviceId - ) + const invitee2RoleMerged = await invitee1Project[ + kDataTypes + ].membership.getByDocId(invitee2.deviceId) t.is(invitee2RoleMerged.forks.length, 0, 'invitee2 role has no forks') await disconnectPeers(managers)