diff --git a/proto/common/v1.proto b/proto/common/v1.proto index 8952417..3736181 100644 --- a/proto/common/v1.proto +++ b/proto/common/v1.proto @@ -11,8 +11,7 @@ message Common_1 { repeated VersionId_1 links = 2; google.protobuf.Timestamp createdAt = 3 [(required) = true]; google.protobuf.Timestamp updatedAt = 4 [(required) = true]; - // 32-byte hash of the discovery key of a core - bytes createdBy = 5 [(required) = true]; + VersionId_1 originalVersionId = 5; bool deleted = 6 [(required) = true]; } /* ignored fields and differences from common.json jsonSchema diff --git a/schema/common/v1.json b/schema/common/v1.json index 0f439b4..213d246 100644 --- a/schema/common/v1.json +++ b/schema/common/v1.json @@ -13,6 +13,10 @@ "description": "core discovery id (hex-encoded 32-byte buffer) and core index number, separated by '/'", "type": "string" }, + "originalVersionId": { + "description": "Version ID of the original version of this document. For the original version, matches `versionId`.", + "type": "string" + }, "schemaName": { "description": "Name of Mapeo data type / schema", "type": "string" @@ -22,10 +26,6 @@ "type": "string", "format": "date-time" }, - "createdBy": { - "description": "discovery id (hex-encoded 32-byte buffer) of the hypercore where the first version of this document is written", - "type": "string" - }, "updatedAt": { "description": "RFC3339-formatted datetime of when this version of the element was created", "type": "string", @@ -51,7 +51,7 @@ "updatedAt", "links", "versionId", - "createdBy", + "originalVersionId", "deleted" ] } diff --git a/src/decode.ts b/src/decode.ts index e310425..be5c90d 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -4,7 +4,7 @@ import { type SchemaName, type DataTypeId, type ValidSchemaDef, - type MapeoDocInternal, + type MapeoDocDecode, } from './types.js' import { Decode } from './proto/index.js' @@ -47,7 +47,7 @@ for (const [schemaName, dataTypeId] of Object.entries(dataTypeIds) as Array< export function decode( buf: Buffer, versionObj: VersionIdObject -): MapeoDocInternal { +): MapeoDocDecode { const schemaDef = decodeBlockPrefix(buf) const encodedMsg = buf.subarray( diff --git a/src/encode.ts b/src/encode.ts index f1f2f75..7294c08 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -1,6 +1,6 @@ +import type { SetOptional } from 'type-fest' import { - type MapeoDocInternal, - type OmitUnion, + type MapeoDocEncode, type SchemaName, type ValidSchemaDef, } from './types.js' @@ -28,9 +28,7 @@ import { ExhaustivenessError } from './lib/utils.js' * Encode a an object validated against a schema as a binary protobuf prefixed * with the encoded data type ID and schema version, to send to an hypercore. */ -export function encode( - mapeoDoc: OmitUnion -): Buffer { +export function encode(mapeoDoc: MapeoDocEncode): Buffer { const { schemaName } = mapeoDoc const schemaVersion = currentSchemaVersions[schemaName] const schemaDef = { schemaName, schemaVersion } diff --git a/src/index.ts b/src/index.ts index 222a3f1..f3ce288 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,4 +11,4 @@ export { export * from './schema/index.js' export * from './schemas.js' -export { type MapeoDocInternal } from './types.js' +export { type MapeoDocDecode, type MapeoDocEncode } from './types.js' diff --git a/src/lib/decode-conversions.ts b/src/lib/decode-conversions.ts index e230223..75299c7 100644 --- a/src/lib/decode-conversions.ts +++ b/src/lib/decode-conversions.ts @@ -18,7 +18,7 @@ import { type MapeoCommon, type TagValuePrimitive, type JsonTagValue, - type MapeoDocInternal, + type MapeoDocDecode, } from '../types.js' import { ExhaustivenessError, VersionIdObject, getVersionId } from './utils.js' import type { Observation, Track } from '../index.js' @@ -32,7 +32,7 @@ import { ProjectSettings } from '../schema/projectSettings.js' type ConvertFunction = ( message: Extract, versionObj: VersionIdObject -) => FilterBySchemaName +) => FilterBySchemaName export const convertProjectSettings: ConvertFunction<'projectSettings'> = ( message, @@ -402,13 +402,24 @@ function convertCommon( throw new Error('Missing required common properties') } + const versionId = getVersionId(versionObj) + + /** @type {string} */ let originalVersionId + if (common.originalVersionId) { + originalVersionId = getVersionId(common.originalVersionId) + } else if (common.links.length === 0) { + originalVersionId = versionId + } else { + throw new Error('Cannot determine original version ID; data is malformed') + } + return { docId: common.docId.toString('hex'), - versionId: getVersionId(versionObj), + versionId, + originalVersionId, links: common.links.map((link) => getVersionId(link)), createdAt: common.createdAt, updatedAt: common.updatedAt, - createdBy: common.createdBy.toString('hex'), deleted: common.deleted, } } diff --git a/src/lib/encode-conversions.ts b/src/lib/encode-conversions.ts index 2b36e31..de38903 100644 --- a/src/lib/encode-conversions.ts +++ b/src/lib/encode-conversions.ts @@ -1,3 +1,4 @@ +import type { SetOptional } from 'type-fest' import { CurrentProtoTypes } from '../proto/types.js' import { ProtoTypesWithSchemaInfo, @@ -5,9 +6,8 @@ import { MapeoCommon, TagValuePrimitive, JsonTagValue, - OmitUnion, CoreOwnershipSignatures, - MapeoDocInternal, + MapeoDocEncode, } from '../types.js' import { TagValue_1, type TagValue_1_PrimitiveValue } from '../proto/tags/v1.js' import { Icon } from '../schema/icon.js' @@ -27,10 +27,7 @@ import { /** Function type for converting a protobuf type of any version for a particular * schema name, and returning the most recent JSONSchema type */ type ConvertFunction = ( - mapeoDoc: Extract< - OmitUnion, - { schemaName: TSchemaName } - > + mapeoDoc: Extract ) => CurrentProtoTypes[TSchemaName] export const convertProjectSettings: ConvertFunction<'projectSettings'> = ( @@ -143,7 +140,8 @@ export const convertDeviceInfo: ConvertFunction<'deviceInfo'> = (mapeoDoc) => { } export const convertCoreOwnership = ( - mapeoDoc: Omit & CoreOwnershipSignatures + mapeoDoc: Omit & + CoreOwnershipSignatures ): CurrentProtoTypes['coreOwnership'] => { return { common: convertCommon(mapeoDoc), @@ -242,13 +240,15 @@ export const convertTrack: ConvertFunction<'track'> = (mapeoDoc) => { } function convertCommon( - common: Omit + common: SetOptional ): ProtoTypesWithSchemaInfo['common'] { return { docId: Buffer.from(common.docId, 'hex'), + originalVersionId: common.originalVersionId + ? parseVersionId(common.originalVersionId) + : undefined, createdAt: common.createdAt, updatedAt: common.updatedAt, - createdBy: Buffer.from(common.createdBy, 'hex'), links: common.links.map((link) => parseVersionId(link)), deleted: common.deleted, } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9d90869..0910b5f 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -62,7 +62,7 @@ export function parseVersionId(versionId: string): VersionIdObject { /** * @template {import('@mapeo/schema').MapeoDoc & { forks?: string[] }} T * @param {T} doc - * @returns {Omit} + * @returns {Omit} */ export function valueOf( doc: TDoc & { forks?: string[] } @@ -71,11 +71,11 @@ export function valueOf( const { docId, versionId, + originalVersionId, links, forks, createdAt, updatedAt, - createdBy, deleted, ...rest } = doc diff --git a/src/types.ts b/src/types.ts index 79d20b2..90b649c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -59,10 +59,20 @@ export type MapeoValue = FilterBySchemaName< /** The decode and encode functions expect core ownership signatures as buffers, * but these are not included in the JSON schema definitions because they are * stripped before they are indexed */ -export type MapeoDocInternal = +export type MapeoDocDecode = | Exclude | (CoreOwnership & CoreOwnershipSignatures) +/** + * MapeoDoc for encoding does not need a versionId, and if links is an empty + * array, it does not need a originalVersionId either. + */ +export type MapeoDocEncode = + | (OmitUnion & { + links: [] + }) + | OmitUnion + /** Union of all valid data type ids */ export type DataTypeId = Values diff --git a/test/fixtures/bad-docs.js b/test/fixtures/bad-docs.js index e10afec..4e37aaf 100644 --- a/test/fixtures/bad-docs.js +++ b/test/fixtures/bad-docs.js @@ -13,10 +13,10 @@ export const badDocs = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.versionId, schemaName: 'observOtion', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], attachments: [], tags: {}, @@ -24,16 +24,38 @@ export const badDocs = [ deleted: false, }, }, + { + text: 'missing expected originalVersionId', + /** @type Omit */ + doc: { + docId: cachedValues.docId, + versionId: cachedValues.versionId, + schemaName: 'observation', + createdAt: cachedValues.createdAt, + updatedAt: cachedValues.updatedAt, + links: [ + JSON.stringify({ + coreDiscoveryKey: Buffer.from('abc123'), + index: 123, + }), + ], + refs: [], + attachments: [], + tags: {}, + metadata: {}, + deleted: false, + }, + }, { text: 'role doc with empty roleId', /** @type {import('../../dist/index.js').Role} */ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.versionId, schemaName: 'role', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], roleId: '', fromIndex: 4, @@ -45,10 +67,10 @@ export const badDocs = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.versionId, schemaName: 'icon', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], deleted: false, variants: [ @@ -66,10 +88,10 @@ export const badDocs = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.versionId, schemaName: 'preset', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], name: 'myPreset', geometry: ['point'], diff --git a/test/fixtures/cached.js b/test/fixtures/cached.js index b0897da..ca94b58 100644 --- a/test/fixtures/cached.js +++ b/test/fixtures/cached.js @@ -5,10 +5,10 @@ const date = new Date().toJSON() export const cachedValues = { docId: randomBytes(32).toString('hex'), versionId: `${randomBytes(32).toString('hex')}/0`, + originalVersionId: `${randomBytes(32).toString('hex')}/0`, projectId: randomBytes(32).toString('hex'), authorId: randomBytes(32).toString('hex'), coreId: randomBytes(32).toString('hex'), - createdBy: randomBytes(32).toString('hex'), createdAt: date, updatedAt: date, attachments: { diff --git a/test/fixtures/good-docs-completed.js b/test/fixtures/good-docs-completed.js index 074f4d9..b4f9c7c 100644 --- a/test/fixtures/good-docs-completed.js +++ b/test/fixtures/good-docs-completed.js @@ -14,10 +14,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'observation', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], lat: 24.0424, lon: 21.0214, @@ -78,10 +78,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'projectSettings', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], defaultPresets: { point: cachedValues.defaultPresets.point, @@ -105,10 +105,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'field', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], tagKey: 'otherTagKey', type: 'number', @@ -130,10 +130,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'preset', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], name: 'myPreset', geometry: ['point', 'vertex', 'line'], @@ -167,10 +167,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'role', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], roleId: '6fd029a78243', fromIndex: 5, @@ -182,10 +182,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'deviceInfo', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], name: 'my device name', deviceType: 'desktop', @@ -197,10 +197,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'deviceInfo', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], name: 'my device name', // @ts-expect-error @@ -215,10 +215,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'coreOwnership', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], authCoreId: Buffer.from('authCoreId').toString('hex'), configCoreId: Buffer.from('configCoreId').toString('hex'), @@ -241,11 +241,11 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'icon', name: 'tree', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], deleted: false, variants: [ @@ -268,10 +268,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'translation', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], deleted: false, docRef: { @@ -290,10 +290,10 @@ export const goodDocsCompleted = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'track', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], deleted: false, locations: [ diff --git a/test/fixtures/good-docs-minimal.js b/test/fixtures/good-docs-minimal.js index 24b50f6..01c5416 100644 --- a/test/fixtures/good-docs-minimal.js +++ b/test/fixtures/good-docs-minimal.js @@ -16,10 +16,10 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'observation', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], attachments: [], tags: {}, @@ -32,10 +32,10 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'projectSettings', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, configMetadata: { name: 'mapeo-config', fileVersion: '1.0', @@ -51,10 +51,10 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'field', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], tagKey: 'myTagKey', label: 'my label', @@ -67,10 +67,10 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'preset', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], name: 'myPreset', geometry: ['point'], @@ -88,10 +88,10 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'role', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], roleId: '3b0104e370f9', fromIndex: 5, @@ -103,10 +103,10 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'deviceInfo', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], name: 'my device name', deleted: false, @@ -117,10 +117,10 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'coreOwnership', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], authCoreId: Buffer.from('authCoreId').toString('hex'), configCoreId: Buffer.from('configCoreId').toString('hex'), @@ -143,11 +143,11 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'icon', name: 'tree', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], deleted: false, variants: [ @@ -165,10 +165,10 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'translation', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], deleted: false, docRef: { @@ -187,10 +187,10 @@ export const goodDocsMinimal = [ doc: { docId: cachedValues.docId, versionId: cachedValues.versionId, + originalVersionId: cachedValues.originalVersionId, schemaName: 'track', createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, - createdBy: cachedValues.createdBy, links: [], deleted: false, locations: [],