Skip to content

Commit

Permalink
feat: Updated core ownership records
Browse files Browse the repository at this point in the history
Fixes Add `CoreOwnership` schema to `@mapeo/schema` #125
  • Loading branch information
gmaclennan committed Aug 23, 2023
1 parent 2b1f272 commit fd2e981
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 58 deletions.
24 changes: 15 additions & 9 deletions proto/coreOwnership/v1.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ message CoreOwnership_1 {

Common_1 common = 1;

string action = 5;
bytes coreId = 6;
bytes projectId = 7;
string storeType = 8;
string signature = 9;
int32 authorIndex = 10;
int32 deviceIndex = 11;
bytes authorId = 12;
string capabilityType = 13;
bytes authCoreId = 5;
bytes configCoreId = 6;
bytes dataCoreId = 7;
bytes blobCoreId = 8;
bytes blobIndexCoreId = 9;
CoreSignatures coreSignatures = 10;
bytes identitySignature = 11;

message CoreSignatures {
bytes auth = 1;
bytes config = 2;
bytes data = 3;
bytes blob = 4;
bytes blobIndex = 5;
}
}
39 changes: 28 additions & 11 deletions schema/coreOwnership/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,40 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://mapeo.world/schemas/coreOwnership/v1.json",
"title": "CoreOwnership",
"description": "Which cores belong to which identity key?",
"type": "object",
"properties": {
"schemaName": {
"type": "string",
"const": "coreOwnership"
},
"action": {
"type": "string"
"authCoreId": {
"type": "string",
"description": "Hex-encoded key of auth store writer core"
},
"configCoreId": {
"type": "string",
"description": "Hex-encoded key of config store writer core"
},
"dataCoreId": {
"type": "string",
"description": "Hex-encoded key of data store writer core"
},
"blobCoreId": {
"type": "string",
"description": "Hex-encoded key of blob store writer core"
},
"coreId": { "type": "string" },
"projectId": { "type": "string" },
"storeType": { "type": "string" },
"signature": { "type": "string" },
"authorIndex": { "type": "integer" },
"deviceIndex": { "type": "integer" },
"authorId": {"type": "string"},
"capabilityType": {"type": "string"}
"blobIndexCoreId": {
"type": "string",
"description": "Hex-encoded key of blobIndex store writer core"
}
},
"required": ["schemaName", "action", "coreId", "projectId", "storeType", "signature", "authorIndex", "deviceIndex", "authorId", "capabilityType"]
"required": [
"schemaName",
"authCoreId",
"configCoreId",
"dataCoreId",
"blobCoreId",
"blobIndexCoreId"
]
}
7 changes: 5 additions & 2 deletions src/decode.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ProtoTypes } from './proto/types.js'
import {
type MapeoDoc,
type ProtoTypesWithSchemaInfo,
type SchemaName,
type DataTypeId,
type ValidSchemaDef,
type MapeoDocInternal,
} from './types.js'

import { Decode } from './proto/index.js'
Expand Down Expand Up @@ -37,7 +37,10 @@ for (const [schemaName, dataTypeId] of Object.entries(dataTypeIds) as Array<
* @param buf Buffer to be decoded
* @param versionObj public key (coreKey) of the core where this block is stored, and the index of the block in the core.
* */
export function decode(buf: Buffer, versionObj: VersionIdObject): MapeoDoc {
export function decode(
buf: Buffer,
versionObj: VersionIdObject
): MapeoDocInternal {
const schemaDef = decodeBlockPrefix(buf)

const encodedMsg = buf.subarray(
Expand Down
12 changes: 10 additions & 2 deletions src/encode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { MapeoDoc, OmitUnion, SchemaName, ValidSchemaDef } from './types.js'
import {
type MapeoDocInternal,
type OmitUnion,
type SchemaName,
type ValidSchemaDef,
} from './types.js'
import { currentSchemaVersions, dataTypeIds } from './config.js'
// @ts-ignore
import * as cenc from 'compact-encoding'
Expand All @@ -13,12 +18,15 @@ import {
convertDeviceInfo,
convertCoreOwnership,
} from './lib/encode-converstions.js'
import { CoreOwnership } from './index.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<MapeoDoc, 'versionId'>): Buffer {
export function encode(
mapeoDoc: OmitUnion<MapeoDocInternal, 'versionId'>
): Buffer {
const { schemaName } = mapeoDoc
const schemaVersion = currentSchemaVersions[schemaName]
const schemaDef = { schemaName, schemaVersion }
Expand Down
26 changes: 21 additions & 5 deletions src/lib/decode-conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
type MapeoCommon,
type TagValuePrimitive,
type JsonTagValue,
type MapeoDocInternal,
} from '../types.js'
import { VersionIdObject, getVersionId } from './utils.js'

Expand All @@ -20,7 +21,7 @@ import { VersionIdObject, getVersionId } from './utils.js'
type ConvertFunction<TSchemaName extends SchemaName> = (
message: Extract<ProtoTypesWithSchemaInfo, { schemaName: TSchemaName }>,
versionObj: VersionIdObject
) => FilterBySchemaName<MapeoDoc, TSchemaName>
) => FilterBySchemaName<MapeoDocInternal, TSchemaName>

export const convertProject: ConvertFunction<'project'> = (
message,
Expand Down Expand Up @@ -151,14 +152,29 @@ export const convertCoreOwnership: ConvertFunction<'coreOwnership'> = (
message,
versionObj
) => {
const { common, schemaVersion, ...rest } = message
if (!message.coreSignatures) {
throw new Error('Invalid message: missing core signatures')
}
const {
common,
schemaVersion,
authCoreId,
configCoreId,
dataCoreId,
blobCoreId,
blobIndexCoreId,
...rest
} = message
const jsonSchemaCommon = convertCommon(common, versionObj)
return {
...jsonSchemaCommon,
...rest,
coreId: message.coreId.toString('hex'),
projectId: message.projectId.toString('hex'),
authorId: message.authorId.toString('hex'),
authCoreId: authCoreId.toString('hex'),
configCoreId: configCoreId.toString('hex'),
dataCoreId: dataCoreId.toString('hex'),
blobCoreId: blobCoreId.toString('hex'),
blobIndexCoreId: blobIndexCoreId.toString('hex'),
coreSignatures: message.coreSignatures,
}
}

Expand Down
20 changes: 12 additions & 8 deletions src/lib/encode-converstions.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import { CurrentProtoTypes } from '../proto/types.js'
import {
MapeoDoc,
ProtoTypesWithSchemaInfo,
SchemaName,
MapeoCommon,
TagValuePrimitive,
JsonTagValue,
OmitUnion,
CoreOwnershipSignatures,
MapeoDocInternal,
} from '../types.js'
import { TagValue_1, type TagValue_1_PrimitiveValue } from '../proto/tags/v1.js'
import { Observation_5_Metadata } from '../proto/observation/v5.js'
import { parseVersionId } from './utils.js'
import { CoreOwnership } from '../index.js'

/** Function type for converting a protobuf type of any version for a particular
* schema name, and returning the most recent JSONSchema type */
type ConvertFunction<TSchemaName extends SchemaName> = (
mapeoDoc: Extract<
OmitUnion<MapeoDoc, 'versionId'>,
OmitUnion<MapeoDocInternal, 'versionId'>,
{ schemaName: TSchemaName }
>
) => CurrentProtoTypes[TSchemaName]
Expand Down Expand Up @@ -120,15 +122,17 @@ export const convertDeviceInfo: ConvertFunction<'deviceInfo'> = (mapeoDoc) => {
}
}

export const convertCoreOwnership: ConvertFunction<'coreOwnership'> = (
mapeoDoc
) => {
export const convertCoreOwnership = (
mapeoDoc: Omit<CoreOwnership, 'versionId'> & CoreOwnershipSignatures
): CurrentProtoTypes['coreOwnership'] => {
return {
common: convertCommon(mapeoDoc),
...mapeoDoc,
coreId: Buffer.from(mapeoDoc.coreId, 'hex'),
projectId: Buffer.from(mapeoDoc.projectId, 'hex'),
authorId: Buffer.from(mapeoDoc.authorId, 'hex'),
authCoreId: Buffer.from(mapeoDoc.authCoreId, 'hex'),
configCoreId: Buffer.from(mapeoDoc.configCoreId, 'hex'),
dataCoreId: Buffer.from(mapeoDoc.dataCoreId, 'hex'),
blobCoreId: Buffer.from(mapeoDoc.blobCoreId, 'hex'),
blobIndexCoreId: Buffer.from(mapeoDoc.blobIndexCoreId, 'hex'),
}
}

Expand Down
14 changes: 14 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Shared types
import { type ProtoTypesWithSchemaInfo as AllProtoTypesWithSchemaInfo } from './proto/types.js'
import {
CoreOwnership,
type MapeoDoc as AllMapeoDocs,
type MapeoValue as AllMapeoValues,
type MapeoCommon,
Expand Down Expand Up @@ -52,10 +53,23 @@ export type MapeoValue = FilterBySchemaName<
AllMapeoValues,
SupportedSchemaNames
>
/** 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 =
| Exclude<MapeoDoc, CoreOwnership>
| (CoreOwnership & CoreOwnershipSignatures)

/** Union of all valid data type ids */
export type DataTypeId = Values<typeof dataTypeIds>

type Namespace = 'auth' | 'config' | 'data' | 'blob' | 'blobIndex'

export type CoreOwnershipSignatures = {
coreSignatures: Record<Namespace, Buffer>
identitySignature: Buffer
}

// HELPER TYPES
/**
* This is a Pick over a union, that keeps it as a distributive type
Expand Down
24 changes: 14 additions & 10 deletions test/fixtures/good-docs-completed.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { cachedValues } from './cached.js'
/**
* @type {Array<{
* expected: Partial<import('../../dist/types').MapeoDoc>,
* doc: import('../../dist/types').MapeoDoc
* doc: import('../../dist/types').MapeoDocInternal
* }>}
*/
export const goodDocsCompleted = [
Expand Down Expand Up @@ -168,15 +168,19 @@ export const goodDocsCompleted = [
createdAt: cachedValues.createdAt,
updatedAt: cachedValues.updatedAt,
links: [],
action: 'remove',
coreId: cachedValues.coreId,
projectId: cachedValues.projectId,
storeType: 'blob',
signature: 'mySig',
authorIndex: 100,
deviceIndex: 2,
authorId: cachedValues.authorId,
capabilityType: 'someCapability',
authCoreId: Buffer.from('authCoreId').toString('hex'),
configCoreId: Buffer.from('configCoreId').toString('hex'),
dataCoreId: Buffer.from('dataCoreId').toString('hex'),
blobCoreId: Buffer.from('blobCoreId').toString('hex'),
blobIndexCoreId: Buffer.from('blobIndexCoreId').toString('hex'),
coreSignatures: {
auth: Buffer.from('auth'),
config: Buffer.from('config'),
data: Buffer.from('data'),
blob: Buffer.from('blob'),
blobIndex: Buffer.from('blobIndex'),
},
identitySignature: Buffer.from('identity'),
},
expected: {},
},
Expand Down
24 changes: 14 additions & 10 deletions test/fixtures/good-docs-minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { cachedValues } from './cached.js'
*
* @type {Array<{
* expected: Partial<import('../../dist/types').MapeoDoc>,
* doc: import('../../dist/types').MapeoDoc
* doc: import('../../dist/types').MapeoDocInternal
* }>}
*/
export const goodDocsMinimal = [
Expand Down Expand Up @@ -112,15 +112,19 @@ export const goodDocsMinimal = [
createdAt: cachedValues.createdAt,
updatedAt: cachedValues.updatedAt,
links: [],
action: 'remove',
coreId: cachedValues.coreId,
projectId: cachedValues.projectId,
storeType: 'blob',
signature: 'mySig',
authorId: cachedValues.authorId,
capabilityType: 'someCapability',
authorIndex: 0,
deviceIndex: 0,
authCoreId: Buffer.from('authCoreId').toString('hex'),
configCoreId: Buffer.from('configCoreId').toString('hex'),
dataCoreId: Buffer.from('dataCoreId').toString('hex'),
blobCoreId: Buffer.from('blobCoreId').toString('hex'),
blobIndexCoreId: Buffer.from('blobIndexCoreId').toString('hex'),
coreSignatures: {
auth: Buffer.from('auth'),
config: Buffer.from('config'),
data: Buffer.from('data'),
blob: Buffer.from('blob'),
blobIndex: Buffer.from('blobIndex'),
},
identitySignature: Buffer.from('identity'),
},
expected: {},
},
Expand Down
15 changes: 14 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,18 @@ then decoding and comparing the two objects - extra values shouldn't be present`
* @return {object}
* */
function stripUndef(obj) {
return JSON.parse(JSON.stringify(obj))
// Apologies, if I was not so lazy I would write a deep compare function that
// ignores undefined properties, but instead we do it the lazy way and round
// trip to JSON in order to remove undefined properties. Properties that are
// Buffers won't survive the round trip, so we remove them and add them back.
// Messy, but works well enough for tests. Sorry.
const { coreSignatures, identitySignature, ...rest } = obj
const noUndef = JSON.parse(JSON.stringify(rest))
if (coreSignatures) {
noUndef.coreSignatures = coreSignatures
}
if (identitySignature) {
noUndef.identitySignature = identitySignature
}
return noUndef
}

0 comments on commit fd2e981

Please sign in to comment.