Skip to content

Commit

Permalink
feat: refactor type to schemaType (#41)
Browse files Browse the repository at this point in the history
Co-authored-by: Tomás Ciccola <tciccola@digital-democracy.com>
  • Loading branch information
tomasciccola and Tomás Ciccola authored Apr 3, 2023
1 parent 87dcac7 commit 3fdb759
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 78 deletions.
41 changes: 21 additions & 20 deletions examples/schema_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { randomBytes } from 'node:crypto'

// DEVICE
// const obj = {
// type: 'Device',
// schemaType: 'Device',
// schemaVersion: 1,
// id: randomBytes(32).toString('hex'),
// action: 'device:add',
Expand All @@ -28,18 +28,18 @@ import { randomBytes } from 'node:crypto'
// }

// ROLE
const obj = {
id: randomBytes(32).toString('hex'),
type: 'Role',
schemaVersion: 1,
role: 'project-creator',
created_at: new Date(),
projectId: randomBytes(32).toString('hex'),
action: 'role:set',
signature: 'hi',
authorIndex: 10,
deviceIndex: 10,
}
// const obj = {
// id: randomBytes(32).toString('hex'),
// schemaType: 'Role',
// schemaVersion: 1,
// role: 'project-creator',
// created_at: new Date(),
// projectId: randomBytes(32).toString('hex'),
// action: 'role:set',
// signature: 'hi',
// authorIndex: 10,
// deviceIndex: 10,
// }

// CORE OWNERSHIP
// const obj = {
Expand All @@ -65,17 +65,18 @@ const obj = {
// }

// FIELD_1
// const obj = {
// id: randomBytes(32).toString('hex'),
// type: 'Field',
// schemaVersion: 1,
// key: 'hi',
// }
const obj = {
id: randomBytes(32).toString('hex'),
schemaType: 'Field',
schemaVersion: 1,
key: ['hi'],
type: 'text',
}

// OBSERVATION 4
// const obj = {
// id: randomBytes(32).toString('hex'),
// type: 'observation',
// schemaType: 'observation',
// schemaVersion: 4,
// created_at: new Date().toJSON(),
// }
Expand Down
49 changes: 30 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const jsonSchemaToProto = (obj) => {
common.created_at = new Date(common.created_at)
common.timestamp = new Date(common.timestamp)

const key = formatSchemaKey(obj.type, obj.schemaVersion)
const key = formatSchemaKey(obj.schemaType, obj.schemaVersion)
// when we inherit from common, common is actually a field inside the protobuf object,
// so we don't destructure it
return inheritsFromCommon(key)
Expand All @@ -52,20 +52,23 @@ const jsonSchemaToProto = (obj) => {
* @param {import('./types/proto/index').ProtobufSchemas} protobufObj
* @param {Object} obj
* @param {Number} obj.schemaVersion
* @param {String} obj.type
* @param {String} obj.schemaType
* @param {String} obj.version
* @returns {import('./types/schema/index').MapeoRecord}
*/
const protoToJsonSchema = (protobufObj, { schemaVersion, type, version }) => {
const protoToJsonSchema = (
protobufObj,
{ schemaVersion, schemaType, version }
) => {
/** @type {Object} */
let obj = { ...protobufObj, schemaVersion, type }
let obj = { ...protobufObj, schemaVersion, schemaType }
if (obj.common) {
obj = { ...obj, ...obj.common }
delete obj.common
}

// Preset_1 and Field_1 don't have a version field and don't accept additional fields
const key = formatSchemaKey(type, schemaVersion)
const key = formatSchemaKey(schemaType, schemaVersion)
if (key !== 'Preset_1' && key !== 'Field_1') {
obj.version = version
}
Expand All @@ -80,7 +83,7 @@ const protoToJsonSchema = (protobufObj, { schemaVersion, type, version }) => {
/**
* given a schemaVersion and type, return a buffer with the corresponding data
* @param {Object} obj
* @param {string} obj.dataTypeId hex encoded string of a 6-byte buffer indicating type
* @param {string} obj.dataTypeId hex encoded string of a 6-byte buffer indicating schemaType
* @param {number | undefined} obj.schemaVersion number to indicate version. Gets converted to a padded 4-byte hex string
* @returns {Buffer} blockPrefix for corresponding schema
*/
Expand All @@ -93,7 +96,7 @@ export const encodeBlockPrefix = ({ dataTypeId, schemaVersion }) => {
}

/**
* given a buffer, return schemaVersion and type
* given a buffer, return schemaVersion and dataTypeId
* @param {Buffer} buf
* @returns {{dataTypeId:String, schemaVersion:Number}}
*/
Expand All @@ -118,14 +121,22 @@ export const decodeBlockPrefix = (buf) => {
* @returns {Boolean} indicating if the object is valid
*/
export const validate = (obj) => {
const key = formatSchemaKey(obj.type, obj.schemaVersion)
const key = formatSchemaKey(obj.schemaType, obj.schemaVersion)

// Preset_1 doesn't have a type field, so validation won't pass
// but we still need it to now which schema to validate, so we delete it after grabbing the key
if (key === 'Preset_1') delete obj['type']
// Field_1 doesn't have a schemaVersion field, so validation won't pass
if (key === 'Preset_1') delete obj['schemaType']
// Field_1 doesn't have a schemaVersion nor schemaType field, so validation won't pass
// but we still need it to now which schema to validate, so we delete it after grabbing the key
if (key === 'Field_1') delete obj['schemaVersion']
if (key === 'Field_1') {
delete obj['schemaVersion']
delete obj['schemaType']
}

if (key === 'Observation_4' || key === 'Filter_1') {
obj.type = obj.schemaType
delete obj.schemaType
}

const validatefn = JSONSchemas[key]
const isValid = validatefn(obj)
Expand All @@ -138,17 +149,17 @@ export const validate = (obj) => {
* @param {import('./types/schema/index').MapeoRecord} obj - Object to be encoded
* @returns {Buffer} protobuf encoded buffer with dataTypeIdSize + schemaVersionSize bytes prepended, one for the type of record and the other for the version of the schema */
export const encode = (obj) => {
const key = formatSchemaKey(obj.type, obj.schemaVersion)
const key = formatSchemaKey(obj.schemaType, obj.schemaVersion)
// some schemas don't have type field so it can be undefined
const type = obj.type || ''
const schemaType = obj.schemaType || ''
if (!ProtobufSchemas[key]) {
throw new Error(
`Invalid schemaVersion for ${type} version ${obj.schemaVersion}`
`Invalid schemaVersion for ${schemaType} version ${obj.schemaVersion}`
)
}

const blockPrefix = encodeBlockPrefix({
dataTypeId: schemasPrefix[type].dataTypeId,
dataTypeId: schemasPrefix[schemaType].dataTypeId,
schemaVersion: obj.schemaVersion,
})
const record = jsonSchemaToProto(obj)
Expand All @@ -164,20 +175,20 @@ export const encode = (obj) => {
* */
export const decode = (buf, { coreId, seq }) => {
const { dataTypeId, schemaVersion } = decodeBlockPrefix(buf)
const type = Object.keys(schemasPrefix).reduce(
const schemaType = Object.keys(schemasPrefix).reduce(
(type, key) => (schemasPrefix[key].dataTypeId === dataTypeId ? key : type),
''
)
const key = formatSchemaKey(type, schemaVersion)
const key = formatSchemaKey(schemaType, schemaVersion)
if (!ProtobufSchemas[key]) {
throw new Error(
`Invalid schemaVersion for ${type} version ${schemaVersion}`
`Invalid schemaVersion for ${schemaType} version ${schemaVersion}`
)
}

const version = `${coreId.toString('hex')}/${seq.toString()}`
const record = buf.subarray(dataTypeIdSize + schemaVersionSize, buf.length)

const protobufObj = ProtobufSchemas[key].decode(record)
return protoToJsonSchema(protobufObj, { schemaVersion, type, version })
return protoToJsonSchema(protobufObj, { schemaVersion, schemaType, version })
}
1 change: 1 addition & 0 deletions proto/field/v1.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ message Field_1 {
bytes id = 1;
// keys can be an array of strings or a string
google.protobuf.Any key = 2;
string type = 3;
}
4 changes: 2 additions & 2 deletions schema/common/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"description": "ID of the device that made this edit",
"type": "string"
},
"type": {
"schemaType": {
"description": "enum that defines the type of document in the database (defines which schema should be used)",
"type": "string"
},
Expand All @@ -49,5 +49,5 @@
"minimum": 1
}
},
"required": ["id", "created_at", "type"]
"required": ["id", "created_at", "schemaType"]
}
2 changes: 1 addition & 1 deletion schema/coreOwnership/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "object",
"allOf":[{"$ref": "../common/v1.json"}],
"properties": {
"type": { "type": "string", "pattern": "^coreOwnership$" },
"schemaType": { "type": "string", "pattern": "^coreOwnership$" },
"action": {
"type": "string",
"enum": ["core:owner"]
Expand Down
2 changes: 1 addition & 1 deletion schema/device/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "object",
"allOf":[{"$ref": "../common/v1.json"}],
"properties": {
"type": {
"schemaType": {
"type": "string",
"pattern": "^Device$"
},
Expand Down
6 changes: 3 additions & 3 deletions schema/observation/v5.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"type": "object",
"allOf":[{"$ref": "../common/v1.json"}],
"properties": {
"type": {
"description": "Must be `observation`",
"schemaType": {
"description": "Must be `Observation`",
"type": "string",
"enum": ["Observation"]
},
Expand Down Expand Up @@ -152,5 +152,5 @@
"additionalProperties": true
}
},
"required": ["id", "version", "created_at", "type", "schemaVersion"]
"required": ["id", "version", "created_at", "schemaType", "schemaVersion"]
}
2 changes: 1 addition & 1 deletion schema/role/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "object",
"allOf":[{"$ref": "../common/v1.json"}],
"properties": {
"type": {
"schemaType": {
"type": "string",
"pattern": "^Role$"
},
Expand Down
50 changes: 29 additions & 21 deletions scripts/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ const __dirname = new URL('.', import.meta.url).pathname

/**
* @param {string} p
* @returns {{type: String, schemaVersion: Number, schema:Object}}
* @returns {{schemaType: String, schemaVersion: Number, schema:Object}}
*/
const loadSchema = (p) => {
const { dir, name } = path.parse(p)
return {
// we get the type of the schema from the directory
type: dir.replace('../schema/', ''),
schemaType: dir.replace('../schema/', ''),
// we get the version from the filename
schemaVersion: parseInt(name.replace('v', '')),
schema: JSON.parse(fs.readFileSync(new URL(p, import.meta.url)).toString()),
Expand All @@ -34,11 +34,14 @@ const schemas = glob
.sync('../schema/*/*.json', { cwd: 'scripts' })
.map(loadSchema)

const schemaExports = schemas.reduce((acc, { schema, schemaVersion, type }) => {
const key = formatSchemaKey(type, schemaVersion)
acc[key] = schema['$id']
return acc
}, {})
const schemaExports = schemas.reduce(
(acc, { schema, schemaVersion, schemaType }) => {
const key = formatSchemaKey(schemaType, schemaVersion)
acc[key] = schema['$id']
return acc
},
{}
)

// compile schemas
const ajv = new Ajv({
Expand Down Expand Up @@ -66,24 +69,25 @@ const jsonSchemaType = `
${schemas
.map(
/** @param {Object} schema */
({ schemaVersion, type }) => {
const varName = `${formatSchemaType(type)}_${schemaVersion}`
({ schemaVersion, schemaType }) => {
const varName = `${formatSchemaType(schemaType)}_${schemaVersion}`
return `import { ${formatSchemaType(
type
)} as ${varName} } from './${type}/v${schemaVersion}'`
schemaType
)} as ${varName} } from './${schemaType}/v${schemaVersion}'`
}
)
.join('\n')}
interface base {
schemaType?: string;
type?: string;
schemaVersion?: number;
}
export type MapeoRecord = (${schemas
.map(
/** @param {Object} schema */
({ schemaVersion, type }) => {
return `${formatSchemaType(type)}_${schemaVersion}`
({ schemaVersion, schemaType }) => {
return `${formatSchemaType(schemaType)}_${schemaVersion}`
}
)
.join(' | ')}) & base
Expand All @@ -100,7 +104,7 @@ const obj = protobufFiles
.map((p) => {
const { name, dir } = path.parse(p)
return {
type: dir.replace('../types/proto/', ''),
schemaType: dir.replace('../types/proto/', ''),
schemaVersion: name,
}
})
Expand All @@ -109,21 +113,25 @@ const linesjs = []
const linesdts = []
const union = obj
.map(
({ type, schemaVersion }) =>
`${formatSchemaType(type)}_${schemaVersion.replace('v', '')}`
({ schemaType, schemaVersion }) =>
`${formatSchemaType(schemaType)}_${schemaVersion.replace('v', '')}`
)
.join(' & ')

obj.forEach(({ type, schemaVersion }) => {
const linejs = `export { ${formatSchemaType(type)}_${schemaVersion.replace(
obj.forEach(({ schemaType, schemaVersion }) => {
const linejs = `export { ${formatSchemaType(
schemaType
)}_${schemaVersion.replace(
'v',
''
)} } from './${type}/${schemaVersion}.js'`
)} } from './${schemaType}/${schemaVersion}.js'`

const linedts = `import { ${formatSchemaType(type)}_${schemaVersion.replace(
const linedts = `import { ${formatSchemaType(
schemaType
)}_${schemaVersion.replace(
'v',
''
)} } from './${type}/${schemaVersion}'`
)} } from './${schemaType}/${schemaVersion}'`
linesdts.push(linedts)
linesjs.push(linejs)
})
Expand Down
Loading

0 comments on commit 3fdb759

Please sign in to comment.