diff --git a/proto/common/v1.proto b/proto/common/v1.proto index ac54704..8952417 100644 --- a/proto/common/v1.proto +++ b/proto/common/v1.proto @@ -3,15 +3,12 @@ package mapeo; import "google/protobuf/timestamp.proto"; import "options.proto"; +import "versionId/v1.proto"; message Common_1 { // 32-byte random generated number optional bytes docId = 1 [(required) = true]; - message Link { - bytes coreDiscoveryKey = 1; - int32 index = 2; - } - repeated Link links = 2; + 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 diff --git a/proto/preset/v1.proto b/proto/preset/v1.proto index a79613f..d98931f 100644 --- a/proto/preset/v1.proto +++ b/proto/preset/v1.proto @@ -5,6 +5,7 @@ import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "tags/v1.proto"; import "common/v1.proto"; +import "versionId/v1.proto"; import "options.proto"; message Preset_1 { @@ -15,6 +16,15 @@ message Preset_1 { Common_1 common = 1; string name = 5; + repeated Geometry geometry = 6; + map tags = 7; + map addTags = 8; + map removeTags = 9; + repeated FieldRef fieldRefs = 10; + optional IconRef iconRef = 11; + repeated string terms = 12; + string color = 13; + enum Geometry { geometry_unspecified = 0; point = 1; @@ -23,12 +33,15 @@ message Preset_1 { area = 4; relation = 5; } - repeated Geometry geometry = 6; - map tags = 7; - map addTags = 8; - map removeTags = 9; - repeated bytes fieldIds = 10; - optional bytes iconId = 11; - repeated string terms = 12; - string color = 13; + + + message FieldRef { + bytes docId = 1; + VersionId_1 versionId = 2; + } + + message IconRef { + bytes docId = 1; + VersionId_1 versionId = 2; + } } diff --git a/proto/track/v1.proto b/proto/track/v1.proto index 4aa53bd..4f9e5d3 100644 --- a/proto/track/v1.proto +++ b/proto/track/v1.proto @@ -6,6 +6,7 @@ import "google/protobuf/struct.proto"; import "tags/v1.proto"; import "common/v1.proto"; import "options.proto"; +import "versionId/v1.proto"; message Track_1 { // **DO NOT CHANGE dataTypeId** generated with `openssl rand -hex 6` @@ -14,25 +15,22 @@ message Track_1 { Common_1 common = 1; repeated Position locations = 2; - repeated Ref refs = 3; + repeated ObservationRef observationRefs = 3; repeated Attachment attachments = 4; map tags = 5; - enum RefType { - ref_type_unspecified = 0; - observation = 1; - } - message Ref { - bytes id = 1; - RefType type = 2; - } - enum AttachmentType { attachment_type_unspecified = 0; photo = 1; video = 2; audio = 3; } + + message ObservationRef { + bytes docId = 1; + VersionId_1 versionId = 2; + } + message Attachment { bytes driveDiscoveryId = 1; string name = 2; diff --git a/proto/translation/v1.proto b/proto/translation/v1.proto index 64d111d..be2108c 100644 --- a/proto/translation/v1.proto +++ b/proto/translation/v1.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package mapeo; import "common/v1.proto"; +import "versionId/v1.proto"; import "options.proto"; message Translation_1 { @@ -10,12 +11,17 @@ message Translation_1 { option (schemaName) = "translation"; Common_1 common = 1; + DocRef docRef = 2; - string schemaNameRef = 2; - bytes docIdRef = 3; - string fieldRef = 4; - string languageCode = 5; - string regionCode = 6; - string message = 7; + string propertyRef = 3; + string languageCode = 4; + string regionCode = 5; + string message = 6; + + message DocRef { + bytes docId = 1; + VersionId_1 versionId = 2; + string type = 3; + } } diff --git a/proto/versionId/v1.proto b/proto/versionId/v1.proto new file mode 100644 index 0000000..ba6487f --- /dev/null +++ b/proto/versionId/v1.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; +package mapeo; + +message VersionId_1 { + bytes coreDiscoveryKey = 1; + int32 index = 2; +} diff --git a/schema/preset/v1.json b/schema/preset/v1.json index c100b28..efcbb97 100644 --- a/schema/preset/v1.json +++ b/schema/preset/v1.json @@ -76,17 +76,24 @@ "description": "Tags that are removed when changing to another preset (default is the same value as 'addTags' which in turn defaults to 'tags')", "$ref": "#/definitions/tags" }, - "fieldIds": { - "description": "hex-encoded string. IDs of fields to displayed to the user when the preset is created or edited", + "fieldRefs": { "type": "array", + "description": "References to any fields that this preset is related to.", "items": { - "type": "string" + "type": "object", + "properties": { + "docId": { + "description": "hex-encoded id of the element that this observation references", + "type": "string" + }, + "versionId": { + "description": "core discovery id (hex-encoded 32-byte buffer) and core index number, separated by '/'", + "type": "string" + } + }, + "required": ["docId", "versionId"] } }, - "iconId": { - "description": "hex-encoded string. ID of preset icon which represents this preset", - "type": "string" - }, "terms": { "description": "Synonyms or related terms (used for search)", "type": "array", @@ -99,17 +106,17 @@ "type": "string", "pattern": "^#[a-fA-F0-9]{6}$" } -}, -"required": [ - "name", - "geometry", - "tags", - "addTags", - "removeTags", - "fieldIds", - "schemaName", - "terms", - "color" -], -"additionalProperties": false + }, + "required": [ + "name", + "geometry", + "tags", + "addTags", + "removeTags", + "fieldRefs", + "schemaName", + "terms", + "color" + ], + "additionalProperties": false } diff --git a/schema/track/v1.json b/schema/track/v1.json index a3e4399..126737b 100644 --- a/schema/track/v1.json +++ b/schema/track/v1.json @@ -61,26 +61,22 @@ "$ref": "#/definitions/position" } }, - "refs": { + "observationRefs": { "type": "array", "description": "References to any observations that this track is related to.", "items": { "type": "object", "properties": { - "id": { + "docId": { "description": "hex-encoded id of the element that this track references", "type": "string" }, - "type": { - "description": "type of the element that this track references", - "type": "string", - "enum": ["ref_type_unspecified", "observation", "UNRECOGNIZED"], - "meta:enum": { - "UNRECOGNIZED": "future reference type" - } + "versionId": { + "description": "core discovery id (hex-encoded 32-byte buffer) and core index number, separated by '/'", + "type": "string" } }, - "required": ["id", "type"] + "required": ["docId", "versionId"] } }, "attachments": { @@ -160,6 +156,12 @@ } } }, - "required": ["schemaName", "locations", "tags", "refs", "attachments"], + "required": [ + "schemaName", + "locations", + "tags", + "observationRefs", + "attachments" + ], "additionalProperties": false } diff --git a/schema/translation/v1.json b/schema/translation/v1.json index 5d4bfc7..df83d13 100644 --- a/schema/translation/v1.json +++ b/schema/translation/v1.json @@ -4,20 +4,30 @@ "description": "A translation is a translated message in a single language for a string property/field of any Mapeo record type", "title": "translation", "type": "object", - "properties":{ - "schemaName":{ - "type":"string", - "const": "translation" - }, - "schemaNameRef": { + "properties": { + "schemaName": { "type": "string", - "description": "schema name of record related to the field being translated" + "const": "translation" }, - "docIdRef": { - "type": "string", - "description": "the docId of the record that this field is translated for" + "docRef": { + "type": "object", + "properties": { + "docId": { + "description": "hex-encoded id of the element that this observation references", + "type": "string" + }, + "versionId": { + "description": "core discovery id (hex-encoded 32-byte buffer) and core index number, separated by '/'", + "type": "string" + }, + "type": { + "description": "type of the element that this translation references", + "type": "string" + } + }, + "required": ["docId", "versionId", "type"] }, - "fieldRef": { + "propertyRef": { "type": "string", "description": "identifier for translated field/property in dot-prop notation" }, @@ -26,14 +36,21 @@ "description": "three-letter ISO 169-3 language code" }, "regionCode": { - "type": "string", - "description": "two-letter country code from ISO 3166-1 alpha-2 or a three-digit code from UN M.49 for geographical regions" -}, + "type": "string", + "description": "two-letter country code from ISO 3166-1 alpha-2 or a three-digit code from UN M.49 for geographical regions" + }, "message": { "type": "string", "description": "the translated string" } }, - "required": ["schemaName", "schemaNameRef", "docIdRef", "fieldRef", "languageCode", "regionCode", "message"], + "required": [ + "schemaName", + "docRef", + "propertyRef", + "languageCode", + "regionCode", + "message" + ], "additionalProperties": false } diff --git a/scripts/lib/parse-config.js b/scripts/lib/parse-config.js index d24dad1..a909cf7 100644 --- a/scripts/lib/parse-config.js +++ b/scripts/lib/parse-config.js @@ -6,7 +6,7 @@ import schema from 'protocol-buffers-schema' import { capitalize, PROJECT_ROOT } from './utils.js' // These messages are embedded in others and do not define Mapeo data types -const EMBEDDED_MESSAGES = ['tags', 'common'] +const EMBEDDED_MESSAGES = ['tags', 'common', 'versionId'] // We avoid creating data type IDs that match these, since blobs (e.g. icons) // can be stored in Mapeo hypercores, and we want to avoid trying to parse a diff --git a/src/lib/decode-conversions.ts b/src/lib/decode-conversions.ts index b3d3bfe..afedc9c 100644 --- a/src/lib/decode-conversions.ts +++ b/src/lib/decode-conversions.ts @@ -122,11 +122,16 @@ export const convertPreset: ConvertFunction<'preset'> = ( ...jsonSchemaCommon, ...rest, geometry, - iconId: rest.iconId ? rest.iconId.toString('hex') : undefined, tags: convertTags(rest.tags), addTags: convertTags(rest.addTags), removeTags: convertTags(rest.removeTags), - fieldIds: rest.fieldIds.map((id) => id.toString('hex')), + fieldRefs: rest.fieldRefs.map(({ docId, versionId }) => { + if (!versionId) throw new Error('missing fieldRef.versionId for preset') + return { + docId: docId.toString('hex'), + versionId: getVersionId(versionId), + } + }), } } @@ -201,10 +206,17 @@ export const convertTranslation: ConvertFunction<'translation'> = ( ) => { const { common, schemaVersion, ...rest } = message const jsonSchemaCommon = convertCommon(common, versionObj) + if (!message.docRef) throw new Error('missing docRef for translation') + if (!message.docRef.versionId) + throw new Error('missing docRef.versionId for translation') return { ...jsonSchemaCommon, ...rest, - docIdRef: message.docIdRef.toString('hex'), + docRef: { + docId: message.docRef.docId.toString('hex'), + versionId: getVersionId(message.docRef.versionId), + type: message.docRef.type, + }, } } @@ -212,14 +224,20 @@ export const convertTrack: ConvertFunction<'track'> = (message, versionObj) => { const { common, schemaVersion, ...rest } = message const jsonSchemaCommon = convertCommon(common, versionObj) const locations = message.locations.map(convertTrackPosition) - const refs = message.refs.map(({ id, type }) => ({ - id: id.toString('hex'), - type, - })) + const observationRefs = message.observationRefs.map( + ({ docId, versionId }) => { + if (!versionId) + throw new Error('missing observationRef.versionId from track') + return { + docId: docId.toString('hex'), + versionId: getVersionId(versionId), + } + } + ) return { ...jsonSchemaCommon, ...rest, - refs, + observationRefs, locations, attachments: message.attachments.map(convertAttachment), tags: convertTags(message.tags), diff --git a/src/lib/encode-conversions.ts b/src/lib/encode-conversions.ts index 461d694..de4d413 100644 --- a/src/lib/encode-conversions.ts +++ b/src/lib/encode-conversions.ts @@ -81,8 +81,10 @@ export const convertPreset: ConvertFunction<'preset'> = (mapeoDoc) => { tags: convertTags(mapeoDoc.tags), addTags: convertTags(mapeoDoc.addTags), removeTags: convertTags(mapeoDoc.removeTags), - fieldIds: mapeoDoc.fieldIds.map((field) => Buffer.from(field, 'hex')), - iconId: mapeoDoc.iconId ? Buffer.from(mapeoDoc.iconId, 'hex') : undefined, + fieldRefs: mapeoDoc.fieldRefs.map(({ docId, versionId }) => ({ + docId: Buffer.from(docId, 'hex'), + versionId: parseVersionId(versionId), + })), } } @@ -192,20 +194,29 @@ export const convertTranslation: ConvertFunction<'translation'> = ( return { common: convertCommon(mapeoDoc), ...mapeoDoc, - docIdRef: Buffer.from(mapeoDoc.docIdRef, 'hex'), + docRef: { + docId: Buffer.from(mapeoDoc.docRef.docId, 'hex'), + versionId: parseVersionId(mapeoDoc.docRef.versionId), + type: mapeoDoc.docRef.type, + }, } } export const convertTrack: ConvertFunction<'track'> = (mapeoDoc) => { - const refs = mapeoDoc.refs.map((ref) => { - return { id: Buffer.from(ref.id, 'hex'), type: ref.type } - }) + const observationRefs = mapeoDoc.observationRefs.map( + ({ docId, versionId }) => { + return { + docId: Buffer.from(docId, 'hex'), + versionId: parseVersionId(versionId), + } + } + ) const attachments = mapeoDoc.attachments.map(convertAttachment) const track: CurrentProtoTypes['track'] = { common: convertCommon(mapeoDoc), ...mapeoDoc, - refs, + observationRefs, attachments, tags: convertTags(mapeoDoc.tags), locations: mapeoDoc.locations, diff --git a/test/fixtures/cached.js b/test/fixtures/cached.js index effc550..d42f6d3 100644 --- a/test/fixtures/cached.js +++ b/test/fixtures/cached.js @@ -27,7 +27,10 @@ export const cachedValues = { line: [randomBytes(32).toString('hex')], relation: [randomBytes(32).toString('hex')], }, - fieldIds: [randomBytes(32).toString('hex')], + refs: { + docId: randomBytes(32).toString('hex'), + versionId: `${randomBytes(32).toString('hex')}/0`, + }, iconId: randomBytes(32).toString('hex'), docIdRef: randomBytes(32).toString('hex'), } diff --git a/test/fixtures/good-docs-completed.js b/test/fixtures/good-docs-completed.js index 8de0795..dec1804 100644 --- a/test/fixtures/good-docs-completed.js +++ b/test/fixtures/good-docs-completed.js @@ -137,8 +137,12 @@ export const goodDocsCompleted = [ removeTags: { deleteInmeditaly: ['this list', 'of things'], }, - fieldIds: cachedValues.fieldIds, - iconId: cachedValues.iconId, + fieldRefs: [ + { + docId: cachedValues.refs.docId, + versionId: cachedValues.refs.versionId, + }, + ], color: '#ff00ff', terms: ['imastring'], deleted: false, @@ -256,9 +260,12 @@ export const goodDocsCompleted = [ createdBy: cachedValues.createdBy, links: [], deleted: false, - schemaNameRef: 'preset', - docIdRef: cachedValues.docIdRef, - fieldRef: 'terms[0]', + docRef: { + docId: cachedValues.refs.docId, + versionId: cachedValues.refs.versionId, + type: 'preset', + }, + propertyRef: 'terms[0]', languageCode: 'es', regionCode: 'AR', message: 'agroforestación', @@ -294,14 +301,14 @@ export const goodDocsCompleted = [ }, }, ], - refs: [ + observationRefs: [ { - id: randomBytes(32).toString('hex'), - type: 'observation', + docId: randomBytes(32).toString('hex'), + versionId: cachedValues.refs.versionId, }, { - id: randomBytes(32).toString('hex'), - type: 'observation', + docId: randomBytes(32).toString('hex'), + versionId: cachedValues.refs.versionId, }, ], attachments: [ diff --git a/test/fixtures/good-docs-minimal.js b/test/fixtures/good-docs-minimal.js index 00f744d..8346d2b 100644 --- a/test/fixtures/good-docs-minimal.js +++ b/test/fixtures/good-docs-minimal.js @@ -71,7 +71,7 @@ export const goodDocsMinimal = [ tags: {}, addTags: {}, removeTags: {}, - fieldIds: [], + fieldRefs: [], terms: [], deleted: false, color: '#ff00ff', @@ -165,9 +165,12 @@ export const goodDocsMinimal = [ createdBy: cachedValues.createdBy, links: [], deleted: false, - schemaNameRef: 'field', - docIdRef: cachedValues.docIdRef, - fieldRef: 'label', + docRef: { + docId: cachedValues.refs.docId, + versionId: cachedValues.refs.versionId, + type: 'field', + }, + propertyRef: 'label', languageCode: 'qu', regionCode: 'PE', message: `sach'a`, @@ -185,7 +188,7 @@ export const goodDocsMinimal = [ links: [], deleted: false, locations: [], - refs: [], + observationRefs: [], attachments: [], tags: {}, },