Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refactor type to schemaType #41

Merged
merged 1 commit into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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