diff --git a/.gitignore b/.gitignore index 347f69ba..c4f46a99 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,8 @@ docs/* # generated code dist/* + +/config.ts +*.js +!/scripts/** +!index.js diff --git a/buf.gen.yaml b/buf.gen.yaml index fe8ccb9f..44a66583 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -9,5 +9,9 @@ plugins: - importSuffix=.js - env=node - snakeToCamel=false - - useOptionals=messages - # - initializeFieldsAsUndefined=true + - useDate=string + # - useOptionals=none + - stringEnums=true + - enumsAsLiterals=true + - outputIndex=true + - oneof=unions diff --git a/index.js b/index.js index db843221..c182f79d 100644 --- a/index.js +++ b/index.js @@ -6,77 +6,66 @@ import * as cenc from 'compact-encoding' import * as Schemas from './dist/schemas.js' import * as ProtobufSchemas from './types/proto/index.js' import schemasPrefix from './schemasPrefix.js' -import { inheritsFromCommon, formatSchemaKey } from './utils.js' +import { + formatSchemaKey, + hexStringToBuffer, + bufferToHexString, + hexStringToCoreVersion, + coreVersionToHexString, + getLastVersionForSchema, +} from './utils.js' const dataTypeIdSize = 6 const schemaVersionSize = 2 + +const dataTypeIdToSchema = Object.keys(schemasPrefix).reduce( + (prefixMap, schemaType) => { + const val = schemasPrefix[schemaType] + if (val.dataTypeId) { + prefixMap[val.dataTypeId] = { + schemaType, + schemaVersion: val.schemaVersions, + } + } + return prefixMap + }, + {} +) + /** * @param {import('./types').JSONSchema} obj - Object to be encoded * @returns {import('./types').ProtobufSchema} */ const jsonSchemaToProto = (obj) => { - const commonKeys = [ - 'id', - 'created_at', - 'links', - 'timestamp', - 'userId', - 'deviceId', - ] - - /** @type {Object} */ - const uncommon = Object.keys(obj) - .filter((k) => !commonKeys.includes(k)) - .reduce((uncommon, field) => ({ ...uncommon, [field]: obj[field] }), {}) - - /** @type {Object} */ - const common = commonKeys - .filter((field) => obj[field]) - .reduce((common, field) => ({ ...common, [field]: obj[field] }), {}) - - common.id = Buffer.from(obj['id'], 'hex') - // turn date represented as string to Date - common.created_at = new Date(common.created_at) - common.timestamp = new Date(common.timestamp) - - 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) - ? { ...uncommon, common } - : { ...uncommon, ...common } + // @ts-ignore + return { + ...obj, + id: hexStringToBuffer(obj.id), + links: obj.links.map(hexStringToCoreVersion), + createdAt: obj.createdAt, + updatedAt: obj.updatedAt, + } } /** * @param {import('./types').ProtobufSchema} protobufObj * @param {Object} obj - * @param {Number} obj.schemaVersion - * @param {String} obj.schemaType - * @param {String} obj.version + * @param {import('./types').schemaType} obj.schemaType + * @param {string} obj.version * @returns {import('./types').JSONSchema} */ -const protoToJsonSchema = ( - protobufObj, - { schemaVersion, schemaType, version } -) => { - /** @type {Object} */ - 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(schemaType, schemaVersion) - if (key !== 'Preset_1' && key !== 'Field_1') { - obj.version = version +const protoToJsonSchema = (protobufObj, { schemaType, version }) => { + // @ts-ignore + return { + ...protobufObj, + // @ts-ignore + schemaType, + id: bufferToHexString(protobufObj.id), + links: protobufObj.links.map(coreVersionToHexString), + createdAt: protobufObj.createdAt || '', + updatedAt: protobufObj.updatedAt || '', + version, } - - obj.id = obj.id.toString('hex') - // turn date represented as Date to string - if (obj.created_at) obj.created_at = obj.created_at.toJSON() - if (obj.timestamp) obj.timestamp = obj.timestamp.toJSON() - return obj } /** @@ -120,23 +109,7 @@ export const decodeBlockPrefix = (buf) => { * @returns {Boolean} indicating if the object is valid */ export const validate = (obj) => { - 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['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'] - delete obj['schemaType'] - } - - if (key === 'Observation_4' || key === 'Filter_1') { - obj.type = obj.schemaType - delete obj.schemaType - } - + const key = formatSchemaKey(obj.schemaType) const validatefn = Schemas[key] const isValid = validatefn(obj) if (!isValid) throw new Error(JSON.stringify(validatefn.errors, null, 4)) @@ -148,18 +121,26 @@ export const validate = (obj) => { * @param {import('./types').JSONSchema} 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.schemaType, obj.schemaVersion) - // some schemas don't have type field so it can be undefined - const schemaType = obj.schemaType || '' + const key = `${formatSchemaKey(obj.schemaType)}` + // check if schemaType is valid if (!ProtobufSchemas[key]) { throw new Error( - `Invalid schemaVersion for ${schemaType} version ${obj.schemaVersion}` + `Invalid schemaVersion for ${obj.schemaType} version ${obj.schemaVersion}` + ) + } + + // check if using lastSchemaVersion + const lastSchemaVersion = getLastVersionForSchema(obj.schemaType) + if (obj.schemaVersion && lastSchemaVersion !== obj.schemaVersion) { + throw new Error( + `Invalid schema version ${obj.schemaVersion} for ${obj.schemaType}. +Only valid to use schema version ${lastSchemaVersion}` ) } const blockPrefix = encodeBlockPrefix({ - dataTypeId: schemasPrefix[schemaType].dataTypeId, - schemaVersion: obj.schemaVersion, + dataTypeId: schemasPrefix[obj.schemaType].dataTypeId, + schemaVersion: lastSchemaVersion, }) const record = jsonSchemaToProto(obj) const partial = ProtobufSchemas[key].fromPartial(record) @@ -174,20 +155,17 @@ export const encode = (obj) => { * */ export const decode = (buf, { coreId, seq }) => { const { dataTypeId, schemaVersion } = decodeBlockPrefix(buf) - const schemaType = Object.keys(schemasPrefix).reduce( - (type, key) => (schemasPrefix[key].dataTypeId === dataTypeId ? key : type), - '' - ) - const key = formatSchemaKey(schemaType, schemaVersion) + const schemaType = dataTypeIdToSchema[dataTypeId].schemaType + const key = formatSchemaKey(schemaType) if (!ProtobufSchemas[key]) { throw new Error( `Invalid schemaVersion for ${schemaType} version ${schemaVersion}` ) } + const version = coreVersionToHexString({ coreId, seq }) - 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, schemaType, version }) + + return protoToJsonSchema(protobufObj, { schemaType, version }) } diff --git a/package-lock.json b/package-lock.json index 3b73c218..4f372c6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,17 +20,21 @@ "typedoc-plugin-markdown": "^3.13.6" }, "devDependencies": { + "@types/json-schema": "^7.0.12", "@types/tape": "^4.13.2", "c8": "^7.12.0", "eslint": "^8.28.0", "eslint-plugin-prettier": "^4.2.1", "hypercore": "^10.4.1", "json-schema-to-typescript": "^11.0.2", + "mkdirp": "^3.0.1", "npm-run-all": "^4.1.5", "prettier": "^2.8.0", + "protocol-buffers-schema": "^3.6.0", "random-access-memory": "^6.0.0", + "rimraf": "^5.0.1", "tape": "^5.6.1", - "ts-proto": "^1.138.0", + "ts-proto": "^1.156.0", "typescript": "^4.8.4" } }, @@ -146,6 +150,102 @@ "timeout-refresh": "^2.0.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -221,6 +321,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -306,9 +416,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "node_modules/@types/lodash": { @@ -317,12 +427,6 @@ "integrity": "sha512-kb9/98N6X8gyME9Cf7YaqIMvYGnBSWqEci6tiettE6iJWH1XdJz/PO8LB0GtLCG7x8dU3KWhZT+lA1a35127tA==", "dev": true }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "dev": true - }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -333,12 +437,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" }, - "node_modules/@types/object-hash": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.4.tgz", - "integrity": "sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA==", - "dev": true - }, "node_modules/@types/prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", @@ -544,6 +642,41 @@ "node": ">=10.12.0" } }, + "node_modules/c8/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -573,9 +706,9 @@ } }, "node_modules/case-anything": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", - "integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==", + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz", + "integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==", "dev": true, "engines": { "node": ">=12.13" @@ -707,12 +840,6 @@ "type": "^1.0.1" } }, - "node_modules/dataloader": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", - "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==", - "dev": true - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -832,6 +959,12 @@ "detect-libc": "^1.0.3" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1310,6 +1443,41 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/flat-tree": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.9.0.tgz", @@ -2137,6 +2305,24 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz", + "integrity": "sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", @@ -2228,6 +2414,18 @@ "glob": "^7.1.6" } }, + "node_modules/json-schema-to-typescript/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2285,11 +2483,20 @@ "dev": true }, "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", "dev": true }, + "node_modules/lru-cache": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.0.tgz", + "integrity": "sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", @@ -2375,16 +2582,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.2.tgz", + "integrity": "sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "dev": true, "bin": { - "mkdirp": "bin/cmd.js" + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { @@ -2667,15 +2886,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-hash": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", - "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -2841,6 +3051,22 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -2911,9 +3137,9 @@ } }, "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2927,15 +3153,19 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "engines": { + "node": ">=12.0.0" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "dev": true + }, "node_modules/protomux": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.4.0.tgz", @@ -3162,35 +3392,92 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", + "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "glob": "^10.2.5" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.3.tgz", + "integrity": "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/signal-exit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true, + "engines": { + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3484,6 +3771,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.padend": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz", @@ -3558,6 +3860,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -3759,40 +4074,37 @@ } }, "node_modules/ts-poet": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.3.0.tgz", - "integrity": "sha512-RjS37SnXMa9En8xvQf//+rvNNNCB7x2TKP/ZfsiQFidtfN3A6FYgPDESe4r7YA3F663XO2ozx+2buQItGOLMxg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.5.0.tgz", + "integrity": "sha512-44jURLT1HG6+NsDcadM826V6Ekux+wk07Go+MX5Gfx+8zcPKfUiFEtnjL9imuRVGSCRtloRLqw4bDGZVJYGZMQ==", "dev": true, "dependencies": { "dprint-node": "^1.0.7" } }, "node_modules/ts-proto": { - "version": "1.138.0", - "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.138.0.tgz", - "integrity": "sha512-C12rKQdzV2/7ncusEkcyO6Z3EK+04TfZSVdRwmhwkrNcwcktm3Azg7NKBpDTgvpktGQ4nTTPRSlO5CGTkx1zJg==", + "version": "1.156.0", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.156.0.tgz", + "integrity": "sha512-GnOxEAD1mRkiqV9VLv48GrNdps8gXp+vE9rWToCPyIxCIjVjkH3ls8iXxRXOS9LSwueJT8F+N9w7Xy3zftUecA==", "dev": true, "dependencies": { - "@types/object-hash": "^1.3.0", - "case-anything": "^2.1.10", - "dataloader": "^1.4.0", - "object-hash": "^1.3.1", - "protobufjs": "^6.11.3", - "ts-poet": "^6.2.0", - "ts-proto-descriptors": "1.7.1" + "case-anything": "^2.1.13", + "protobufjs": "^7.2.4", + "ts-poet": "^6.5.0", + "ts-proto-descriptors": "1.14.0" }, "bin": { "protoc-gen-ts_proto": "protoc-gen-ts_proto" } }, "node_modules/ts-proto-descriptors": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-1.7.1.tgz", - "integrity": "sha512-oIKUh3K4Xts4v29USGLfUG+2mEk32MsqpgZAOUyUlkrcIdv34yE+k2oZ2Nzngm6cV/JgFdOxRCqeyvmWHuYAyw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-1.14.0.tgz", + "integrity": "sha512-xqLA6cBTfof+mZ/sIw/pZviyhnWWcWqRBjyjaMW5O4fIogpawT4aa0wI8rKh0rYIrQzoHxLugmFu4+rdiWaGEQ==", "dev": true, "dependencies": { - "long": "^4.0.0", - "protobufjs": "^6.8.8" + "long": "^5.0.0", + "protobufjs": "^7.2.4" } }, "node_modules/type": { @@ -4053,6 +4365,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -4223,6 +4553,71 @@ "timeout-refresh": "^2.0.0" } }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -4283,6 +4678,13 @@ "fastq": "^1.6.0" } }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -4368,9 +4770,9 @@ "dev": true }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "@types/lodash": { @@ -4379,12 +4781,6 @@ "integrity": "sha512-kb9/98N6X8gyME9Cf7YaqIMvYGnBSWqEci6tiettE6iJWH1XdJz/PO8LB0GtLCG7x8dU3KWhZT+lA1a35127tA==", "dev": true }, - "@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "dev": true - }, "@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -4395,12 +4791,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" }, - "@types/object-hash": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.4.tgz", - "integrity": "sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA==", - "dev": true - }, "@types/prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", @@ -4557,6 +4947,31 @@ "v8-to-istanbul": "^9.0.0", "yargs": "^16.2.0", "yargs-parser": "^20.2.9" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "call-bind": { @@ -4582,9 +4997,9 @@ "dev": true }, "case-anything": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", - "integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==", + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz", + "integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==", "dev": true }, "chacha20-universal": { @@ -4695,12 +5110,6 @@ "type": "^1.0.1" } }, - "dataloader": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", - "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==", - "dev": true - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4788,6 +5197,12 @@ "detect-libc": "^1.0.3" } }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -5166,6 +5581,31 @@ "requires": { "flatted": "^3.1.0", "rimraf": "^3.0.2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "flat-tree": { @@ -5779,6 +6219,16 @@ "istanbul-lib-report": "^3.0.0" } }, + "jackspeak": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz", + "integrity": "sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", @@ -5844,6 +6294,12 @@ "requires": { "@types/glob": "^7.1.3" } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true } } }, @@ -5895,9 +6351,15 @@ "dev": true }, "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "dev": true + }, + "lru-cache": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.0.tgz", + "integrity": "sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==", "dev": true }, "lru-queue": { @@ -5964,10 +6426,16 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, + "minipass": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.2.tgz", + "integrity": "sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA==", + "dev": true + }, "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "dev": true }, "ms": { @@ -6202,12 +6670,6 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true }, - "object-hash": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", - "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", - "dev": true - }, "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -6325,6 +6787,16 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "requires": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + } + }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -6370,9 +6842,9 @@ } }, "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", "dev": true, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -6385,11 +6857,16 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" } }, + "protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "dev": true + }, "protomux": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.4.0.tgz", @@ -6559,27 +7036,60 @@ "dev": true }, "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", + "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "^10.2.5" }, "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + } + }, "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.3.tgz", + "integrity": "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" } + }, + "signal-exit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true } } }, @@ -6832,6 +7342,17 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "string.prototype.padend": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz", @@ -6885,6 +7406,15 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -7042,37 +7572,34 @@ } }, "ts-poet": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.3.0.tgz", - "integrity": "sha512-RjS37SnXMa9En8xvQf//+rvNNNCB7x2TKP/ZfsiQFidtfN3A6FYgPDESe4r7YA3F663XO2ozx+2buQItGOLMxg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.5.0.tgz", + "integrity": "sha512-44jURLT1HG6+NsDcadM826V6Ekux+wk07Go+MX5Gfx+8zcPKfUiFEtnjL9imuRVGSCRtloRLqw4bDGZVJYGZMQ==", "dev": true, "requires": { "dprint-node": "^1.0.7" } }, "ts-proto": { - "version": "1.138.0", - "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.138.0.tgz", - "integrity": "sha512-C12rKQdzV2/7ncusEkcyO6Z3EK+04TfZSVdRwmhwkrNcwcktm3Azg7NKBpDTgvpktGQ4nTTPRSlO5CGTkx1zJg==", + "version": "1.156.0", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.156.0.tgz", + "integrity": "sha512-GnOxEAD1mRkiqV9VLv48GrNdps8gXp+vE9rWToCPyIxCIjVjkH3ls8iXxRXOS9LSwueJT8F+N9w7Xy3zftUecA==", "dev": true, "requires": { - "@types/object-hash": "^1.3.0", - "case-anything": "^2.1.10", - "dataloader": "^1.4.0", - "object-hash": "^1.3.1", - "protobufjs": "^6.11.3", - "ts-poet": "^6.2.0", - "ts-proto-descriptors": "1.7.1" + "case-anything": "^2.1.13", + "protobufjs": "^7.2.4", + "ts-poet": "^6.5.0", + "ts-proto-descriptors": "1.14.0" } }, "ts-proto-descriptors": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-1.7.1.tgz", - "integrity": "sha512-oIKUh3K4Xts4v29USGLfUG+2mEk32MsqpgZAOUyUlkrcIdv34yE+k2oZ2Nzngm6cV/JgFdOxRCqeyvmWHuYAyw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-1.14.0.tgz", + "integrity": "sha512-xqLA6cBTfof+mZ/sIw/pZviyhnWWcWqRBjyjaMW5O4fIogpawT4aa0wI8rKh0rYIrQzoHxLugmFu4+rdiWaGEQ==", "dev": true, "requires": { - "long": "^4.0.0", - "protobufjs": "^6.8.8" + "long": "^5.0.0", + "protobufjs": "^7.2.4" } }, "type": { @@ -7265,6 +7792,17 @@ "strip-ansi": "^6.0.0" } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 6cafcfbb..472e6e4b 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,13 @@ }, "scripts": { "example": "node examples/schema_test.js", - "protobuf": "buf generate ./proto", - "jsonschema": "scripts/./jsonts", + "protoTypes": "buf generate ./proto", "tsc": "tsc", "tsc-index": "tsc index.js -d --emitDeclarationOnly --allowJs --allowSyntheticDefaultImports --module es2022 --target es2019 --moduleResolution node", "generate": "node scripts/generate.js", "doc": "typedoc --plugin typedoc-plugin-markdown", "clean": "rm -rf types dist docs", - "build": "npm-run-all protobuf jsonschema generate tsc tsc-index doc", + "build": "npm-run-all generate tsc tsc-index doc", "prepublishOnly": "npm run build", "test": "tape 'test/**/*.js'" }, @@ -37,17 +36,21 @@ }, "homepage": "https://github.com/digidem/mapeo-schema#readme", "devDependencies": { + "@types/json-schema": "^7.0.12", "@types/tape": "^4.13.2", "c8": "^7.12.0", "eslint": "^8.28.0", "eslint-plugin-prettier": "^4.2.1", "hypercore": "^10.4.1", "json-schema-to-typescript": "^11.0.2", + "mkdirp": "^3.0.1", "npm-run-all": "^4.1.5", "prettier": "^2.8.0", + "protocol-buffers-schema": "^3.6.0", "random-access-memory": "^6.0.0", + "rimraf": "^5.0.1", "tape": "^5.6.1", - "ts-proto": "^1.138.0", + "ts-proto": "^1.156.0", "typescript": "^4.8.4" }, "prettier": { diff --git a/proto/common/v1.proto b/proto/common/v1.proto index 0a67d2bf..6075ab4f 100644 --- a/proto/common/v1.proto +++ b/proto/common/v1.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package mapeo; import "google/protobuf/timestamp.proto"; - +import "options.proto"; message Common_1 { // 32-byte random generated number diff --git a/proto/options.proto b/proto/options.proto index a6c93b25..65ad369f 100644 --- a/proto/options.proto +++ b/proto/options.proto @@ -23,5 +23,5 @@ extend google.protobuf.FieldOptions { // should be marked as optional in the protobuf definition, because otherwise // they are set to the default value and the generated type does not include // `undefined` - boolean required = 50003 [retention = RETENTION_SOURCE] + bool required = 50003 [retention = RETENTION_SOURCE]; } diff --git a/scripts/generate.js b/scripts/generate.js index 5928912f..dc144b42 100644 --- a/scripts/generate.js +++ b/scripts/generate.js @@ -1,191 +1,52 @@ // @ts-check -// This script is use to generate various files to be used at runtime: -// * dist/schemas.js - all the validating functions for jsonSchema -// * types/schema/index.d.ts - Union type for all the JsonSchemas -// * types/proto/index.d.ts - Union type for all the ProtobufSchemas -// * types/proto/index.js - Exports all protobufs from one file -// * types/index.d.ts - re-exports JSONSchema and ProtobufSchema types for better importing - -import fs from 'node:fs' +import fs from 'fs' import path from 'path' -import { URL } from 'url' -import Ajv from 'ajv' -import standaloneCode from 'ajv/dist/standalone/index.js' -import glob from 'glob-promise' -import { formatSchemaKey, formatSchemaType } from '../utils.js' - -const __dirname = new URL('.', import.meta.url).pathname +import { mkdirp } from 'mkdirp' +import { rimraf } from 'rimraf' +import { execSync } from 'child_process' -/** - * @param {string} p - * @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 - 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()), - } -} +import { parseConfig } from './lib/parse-config.js' +import { generateProtoTypes } from './lib/generate-proto-types.js' +import { PROJECT_ROOT } from './lib/utils.js' +import { generateConfig } from './lib/generate-config.js' +import { readJSONSchema } from './lib/read-json-schema.js' +import { generateValidations } from './lib/generate-validations.js' +import { generateJSONSchemaTS } from './lib/generate-jsonschema-ts.js' -const schemas = glob - .sync('../schema/*/*.json', { cwd: 'scripts' }) - .map(loadSchema) +const DIST_DIRNAME = path.join(PROJECT_ROOT, 'dist') +const TYPES_DIRNAME = path.join(PROJECT_ROOT, 'types') +rimraf.sync(DIST_DIRNAME) +rimraf.sync(TYPES_DIRNAME) -const schemaExports = schemas.reduce( - (acc, { schema, schemaVersion, schemaType }) => { - const key = formatSchemaKey(schemaType, schemaVersion) - acc[key] = schema['$id'] - return acc - }, - {} -) +mkdirp.sync(DIST_DIRNAME) +mkdirp.sync(path.join(TYPES_DIRNAME, 'proto')) +mkdirp.sync(path.join(TYPES_DIRNAME, 'schema')) -// compile schemas -const ajv = new Ajv({ - schemas: schemas.map(({ schema }) => schema), - code: { source: true, esm: true }, - formats: { 'date-time': true }, -}) -ajv.addKeyword('meta:enum') +execSync('buf generate ./proto', { cwd: PROJECT_ROOT }) -// generate validation code -let schemaValidations = standaloneCode(ajv, schemaExports) +const config = parseConfig() -const dist = path.join(__dirname, '../dist') -if (!fs.existsSync(dist)) { - fs.mkdirSync(dist) -} - -// dist/schemas.js +const protoTypesFile = generateProtoTypes(config) fs.writeFileSync( - path.join(__dirname, '../dist', 'schemas.js'), - schemaValidations + path.join(PROJECT_ROOT, 'types/proto/types.ts'), + protoTypesFile ) -const latestSchemaVersions = schemas.reduce( - (acc, { schemaVersion, schemaType }) => { - if (!acc[schemaType]) { - acc[schemaType] = schemaVersion - } else { - if (acc[schemaType] < schemaVersion) { - acc[schemaType] = schemaVersion - } - } - return acc - }, - {} -) - -// types/schema/index.d.ts -const jsonSchemaType = ` -${schemas - .map( - /** @param {Object} schema */ - ({ schemaVersion, schemaType }) => { - const varName = `${formatSchemaType(schemaType)}_${schemaVersion}` - return `import { ${formatSchemaType( - schemaType - )} as ${varName} } from './${schemaType}/v${schemaVersion}'` - } - ) - .join('\n')} - -interface base { -schemaType?: string; -type?: string; -schemaVersion?: number; -} -export type JSONSchema = (${schemas - .map( - /** @param {Object} schema */ - ({ schemaVersion, schemaType }) => - `${formatSchemaType(schemaType)}_${schemaVersion}` - ) - .join(' | ')}) & base -${schemas - .map(({ schemaType, schemaVersion }) => { - const as = - latestSchemaVersions[schemaType] !== schemaVersion - ? `as ${formatSchemaType(schemaType)}_${schemaVersion}` - : '' - return `export { ${formatSchemaType( - schemaType - )} ${as} } from './${schemaType}/v${latestSchemaVersions[schemaType]}'` - }) - .join('\n')}` -fs.writeFileSync( - path.join(__dirname, '../types/schema/index.d.ts'), - jsonSchemaType -) +const configFile = generateConfig(config) +fs.writeFileSync(path.join(PROJECT_ROOT, 'config.ts'), configFile) -// types/proto/index.d.ts and types/proto/index.js -const protobufFiles = glob.sync('../types/proto/*/*.ts', { cwd: 'scripts' }) -const obj = protobufFiles - .filter((f) => !f.match(/.d.ts/)) - .map((p) => { - const { name, dir } = path.parse(p) - return { - schemaType: dir.replace('../types/proto/', ''), - schemaVersion: name, - } - }) +const jsonSchemas = readJSONSchema(config) -const linesjs = [] -const linesdts = [] -const union = obj - .map( - ({ schemaType, schemaVersion }) => - `${formatSchemaType(schemaType)}_${schemaVersion.replace('v', '')}` - ) - .join(' | ') +const validationCode = generateValidations(config, jsonSchemas) +fs.writeFileSync(path.join(DIST_DIRNAME, 'schemas.js'), validationCode) -const individualExports = schemas - .map( - ({ schemaType, schemaVersion }) => - `export { ${formatSchemaType(schemaType)}_${schemaVersion} ${ - latestSchemaVersions[schemaType] === schemaVersion - ? `as ${formatSchemaType(schemaType)}` - : '' - }} from './${schemaType}/v${schemaVersion}'` +const jsonSchemaTSDefs = await generateJSONSchemaTS(config, jsonSchemas) +for (const [filenameBase, ts] of Object.entries(jsonSchemaTSDefs)) { + const filepath = path.join( + PROJECT_ROOT, + 'types', + 'schema', + filenameBase + '.ts' ) - .join('\n') - -obj.forEach(({ schemaType, schemaVersion }) => { - const linejs = `export { ${formatSchemaType( - schemaType - )}_${schemaVersion.replace( - 'v', - '' - )} } from './${schemaType}/${schemaVersion}.js'` - - const linedts = `import { ${formatSchemaType( - schemaType - )}_${schemaVersion.replace( - 'v', - '' - )} } from './${schemaType}/${schemaVersion}'` - linesdts.push(linedts) - linesjs.push(linejs) -}) - -fs.writeFileSync( - path.join(__dirname, '../types/proto/index.js'), - linesjs.join('\n') -) -fs.writeFileSync( - path.join(__dirname, '../types/proto/index.d.ts'), - `${linesdts.join('\n')} -export type ProtobufSchema = ${union} -${individualExports}` -) - -// types/index.d.ts -fs.writeFileSync( - path.join(__dirname, '../types/index.d.ts'), - `export { ProtobufSchema } from './proto' -export { JSONSchema } from './schema'` -) + fs.writeFileSync(filepath, ts) +} diff --git a/scripts/lib/generate-config.js b/scripts/lib/generate-config.js new file mode 100644 index 00000000..b16d0a4e --- /dev/null +++ b/scripts/lib/generate-config.js @@ -0,0 +1,45 @@ +/** + * @param {ReturnType} config + */ +export function generateConfig({ + currentSchemaVersions, + dataTypeIds, + protoTypeDefs, +}) { + const idsLines = ['export const dataTypeIds = {'] + for (const [schemaName, dataTypeId] of Object.entries(dataTypeIds)) { + idsLines.push(` ${schemaName}: '${dataTypeId}',`) + } + idsLines.push('} as const') + + const versionsLines = ['export const currentSchemaVersions = {'] + for (const [schemaName, schemaVersion] of Object.entries( + currentSchemaVersions + )) { + versionsLines.push(` ${schemaName}: ${schemaVersion},`) + } + versionsLines.push('} as const') + + const knownVersions = {} + for (const { schemaName, schemaVersion } of protoTypeDefs) { + const existing = knownVersions[schemaName] + knownVersions[schemaName] = existing + ? [...existing, schemaVersion] + : [schemaVersion] + } + + const knownVersionsLines = ['export const knownSchemaVersions = {'] + for (const [schemaName, schemaVersions] of Object.entries(knownVersions)) { + knownVersionsLines.push(` ${schemaName}: [${schemaVersions.join(', ')}],`) + } + knownVersionsLines.push('}') + + return [ + ...idsLines, + '', + ...versionsLines, + '', + ...knownVersionsLines, + '', + ].join('\n') +} diff --git a/scripts/lib/generate-jsonschema-ts.js b/scripts/lib/generate-jsonschema-ts.js new file mode 100644 index 00000000..9ab93849 --- /dev/null +++ b/scripts/lib/generate-jsonschema-ts.js @@ -0,0 +1,48 @@ +// @ts-check +import { compile } from 'json-schema-to-typescript' +import { capitalize } from './utils.js' + +/** + * Returns generated typescript definitions for JSONSchemas + * + * @param {ReturnType} config + * @param {Record} jsonSchemas + */ +export async function generateJSONSchemaTS(config, jsonSchemas) { + /** @type {Record} */ + const typescriptDefs = {} + for (const [schemaName, jsonSchema] of Object.entries(jsonSchemas)) { + // @ts-ignore + const ts = await compile(jsonSchema, capitalize(schemaName), { + additionalProperties: false, + unknownAny: false, + }) + typescriptDefs[schemaName] = ts + } + + const indexLines = [] + + for (const schemaName of Object.keys(jsonSchemas)) { + const typeName = capitalize(schemaName) + indexLines.push(`import { type ${typeName} } from './${schemaName}'`) + } + + indexLines.push('') + indexLines.push('export type JsonSchemaTypes = ') + + for (const schemaName of Object.keys(jsonSchemas)) { + const typeName = capitalize(schemaName) + indexLines.push(` | ${typeName}`) + } + + indexLines.push('') + + for (const schemaName of Object.keys(jsonSchemas)) { + const typeName = capitalize(schemaName) + indexLines.push(`export { ${typeName} }`) + } + + typescriptDefs.index = indexLines.join('\n') + '\n' + + return typescriptDefs +} diff --git a/scripts/lib/generate-proto-types.js b/scripts/lib/generate-proto-types.js new file mode 100644 index 00000000..d0c7bef4 --- /dev/null +++ b/scripts/lib/generate-proto-types.js @@ -0,0 +1,73 @@ +// @ts-check + +// types/proto/index.d.ts and types/proto/index.js +import { capitalize } from './utils.js' + +/** + * @param {ReturnType} config + */ +export function generateProtoTypes({ currentSchemaVersions, protoTypeDefs }) { + const typeImports = protoTypeDefs + .map(({ schemaName, schemaVersion, typeName }) => { + return `import { ${typeName} } from './${schemaName}/v${schemaVersion}'` + }) + .join('\n') + + const allProtoDefs = + 'export type ProtoTypes =\n | ' + + protoTypeDefs + .map(({ typeName }) => { + return `${typeName}` + }) + .join('\n | ') + + const protoTypesWithSchemaInfo = + '/** Union of all Proto Types (including non-current versions) with schemaName and schemaVersion */' + + 'export type ProtoTypesWithSchemaInfo =\n | ' + + protoTypeDefs + .map(({ schemaName, schemaVersion, typeName }) => { + return `${typeName} & { schemaName: '${schemaName}', schemaVersion: ${schemaVersion} }` + }) + .join('\n | ') + + const currentProtoTypes = + 'export type CurrentProtoTypes =\n | ' + + protoTypeDefs + .filter(({ schemaName, schemaVersion }) => { + return currentSchemaVersions[schemaName] === schemaVersion + }) + .map(({ schemaName, schemaVersion }) => { + const typeName = getTypeName(schemaName, schemaVersion) + return `${typeName}` + }) + .join('\n | ') + + const protoTypesExports = protoTypeDefs + .map(({ typeName }) => { + return `export { ${typeName} }` + }) + .join('\n') + + const protoTypeNames = + 'export type ProtoTypeNames =\n | ' + + protoTypeDefs.map(({ typeName }) => `'${typeName}'`).join('\n | ') + + return ( + typeImports + + '\n\n' + + allProtoDefs + + '\n\n' + + protoTypesWithSchemaInfo + + '\n\n' + + currentProtoTypes + + '\n\n' + + protoTypesExports + + '\n\n' + + protoTypeNames + + '\n' + ) +} + +function getTypeName(schemaName, schemaVersion) { + return capitalize(schemaName) + '_' + schemaVersion +} diff --git a/scripts/lib/generate-validations.js b/scripts/lib/generate-validations.js new file mode 100644 index 00000000..a95aa9d8 --- /dev/null +++ b/scripts/lib/generate-validations.js @@ -0,0 +1,29 @@ +// @ts-check +import Ajv from 'ajv' +import standaloneCode from 'ajv/dist/standalone/index.js' + +/** + * Returns generated code for validation functions + * + * @param {ReturnType} config + * @param {Record} jsonSchemas + */ +export function generateValidations(config, jsonSchemas) { + const schemas = Object.entries(jsonSchemas) + + const schemaExports = schemas.reduce((acc, [schemaName, jsonSchema]) => { + acc[schemaName] = jsonSchema['$id'] + return acc + }, {}) + + // compile schemas + const ajv = new Ajv({ + schemas: schemas.map(([, jsonSchema]) => jsonSchema), + code: { source: true, esm: true }, + formats: { 'date-time': true }, + }) + ajv.addKeyword('meta:enum') + + // generate validation code + return standaloneCode(ajv, schemaExports) +} diff --git a/scripts/lib/parse-config.js b/scripts/lib/parse-config.js new file mode 100644 index 00000000..172f5c78 --- /dev/null +++ b/scripts/lib/parse-config.js @@ -0,0 +1,88 @@ +// @ts-check +import path from 'path' +import fs from 'fs' +import glob from 'glob-promise' +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'] + +/** + * Parse the proto message types and check: + * + * - All message types have a dataTypeId and schemaName + * - dataTypeId does not collide + * - Message names match folder names + * + * Then return map of schemaName to dataTypeId + * and map of schemaName to current schemaVersion + */ +export function parseConfig() { + const protobufFiles = glob.sync( + `proto/!(${EMBEDDED_MESSAGES.join('|')})/*.proto`, + { + cwd: PROJECT_ROOT, + absolute: true, + } + ) + + /** @type {Map} dataTypeId: schemaName */ + const duplicateIdCheck = new Map() + + /** @type {Record} */ + const dataTypeIds = {} + /** @type {Record} */ + const currentSchemaVersions = {} + /** @type {Array<{ schemaVersion: number, schemaName: string, typeName: string }>} */ + const protoTypeDefs = [] + + for (const filepath of protobufFiles) { + const sch = schema.parse(fs.readFileSync(filepath)) + const folderName = path.basename(path.dirname(filepath)) + const message = sch.messages.find((m) => { + return m.name.startsWith(capitalize(folderName) + '_') + }) + if (!message) throw new Error('No message found in ' + filepath) + const match = /_(\d+)$/.exec(message.name) + if (!match) { + throw new Error( + 'Message name must end with `_` and version number in ' + filepath + ) + } + const schemaVersion = Number(match[1]) + const { dataTypeId, schemaName } = message.options + if (typeof dataTypeId !== 'string') { + throw new Error('Missing dataTypeId option in ' + filepath) + } + if (typeof schemaName !== 'string') { + throw new Error('Missing schemaName option in ' + filepath) + } + if ( + duplicateIdCheck.has(dataTypeId) && + duplicateIdCheck.get(dataTypeId) !== schemaName + ) { + throw new Error('Duplicate dataTypeId in ' + filepath) + } + duplicateIdCheck.set(dataTypeId, schemaName) + + dataTypeIds[schemaName] = dataTypeId + + const prevSchemaVersion = currentSchemaVersions[schemaName] + if (!prevSchemaVersion || schemaVersion > prevSchemaVersion) { + currentSchemaVersions[schemaName] = schemaVersion + } + + protoTypeDefs.push({ + schemaVersion, + schemaName, + typeName: message.name, + }) + } + + return { + dataTypeIds, + currentSchemaVersions, + protoTypeDefs, + } +} diff --git a/scripts/lib/read-json-schema.js b/scripts/lib/read-json-schema.js new file mode 100644 index 00000000..45d5bad0 --- /dev/null +++ b/scripts/lib/read-json-schema.js @@ -0,0 +1,92 @@ +// @ts-check + +import fs from 'node:fs' +import path from 'path' +import glob from 'glob-promise' +import { PROJECT_ROOT } from './utils.js' + +/** @param {string} relativeFilepath filepath relative to project root */ +function readJSON(relativeFilepath) { + const filepath = path.resolve(PROJECT_ROOT, relativeFilepath) + return JSON.parse(fs.readFileSync(filepath, 'utf-8')) +} + +/** + * Returns the most recent version of JSONSchema files in `schema/**` with + * properties from schema/common/v1.json merged + * + * @param {ReturnType} config + */ +export function readJSONSchema({ currentSchemaVersions }) { + const jsonSchemaFiles = glob.sync(`schema/!(common)/*.json`, { + cwd: PROJECT_ROOT, + absolute: true, + }) + + const common = readJSON('./schema/common/v1.json') + + const jsonSchemaDefs = jsonSchemaFiles.map((filepath) => { + /** @type {import('json-schema').JSONSchema7} */ + const jsonSchema = readJSON(filepath) + const { dir, name } = path.parse(filepath) + const folderName = path.basename(dir) + // @ts-ignore - enum not defined on JSONSchema v7 + const schemaName = jsonSchema.properties?.schemaName?.enum[0] + if (folderName !== schemaName) { + throw new Error(`Unexpected schemaName '${schemaName}' in ${filepath}`) + } + const schemaVersion = Number.parseInt(name.replace(/^v/, '')) + // TODO: Check $id is correct + return { + schemaName, + schemaVersion, + jsonSchema: mergeCommon(jsonSchema, common), + } + }) + + /** @type {Record} schemaName: JSONSchema */ + const jsonSchemas = {} + + for (const { schemaName, schemaVersion, jsonSchema } of jsonSchemaDefs) { + if (schemaVersion !== currentSchemaVersions[schemaName]) continue + jsonSchemas[schemaName] = jsonSchema + } + + for (const schemaName of Object.keys(currentSchemaVersions)) { + if (!jsonSchemas[schemaName]) { + throw new Error( + `Missing JSON schema def for ${schemaName} v${currentSchemaVersions[schemaName]}` + ) + } + } + + return jsonSchemas +} + +function removeDuplicates(arr, elem) { + if (!arr.includes(elem)) { + arr.push(elem) + } + return arr +} + +/** + * + * @param {import('json-schema').JSONSchema7} schema + * @param {import('json-schema').JSONSchema7} common + * @returns {import('json-schema').JSONSchema7} + */ +function mergeCommon(schema, common) { + const required = [ + ...(schema.required || []), + ...(common.required || []), + ].reduce(removeDuplicates, []) + return { + ...schema, + required, + properties: { + ...common.properties, + ...schema.properties, + }, + } +} diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js new file mode 100644 index 00000000..614b833f --- /dev/null +++ b/scripts/lib/utils.js @@ -0,0 +1,11 @@ +import path from 'path' + +/** @param {string} str */ +export function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1) +} + +export const PROJECT_ROOT = path.resolve( + path.dirname(new URL(import.meta.url).pathname), + '../..' +) diff --git a/src/decode.ts b/src/decode.ts new file mode 100644 index 00000000..e2db2f89 --- /dev/null +++ b/src/decode.ts @@ -0,0 +1,151 @@ +import { type ProtoTypeNames, ProtoTypes } from '../types/proto/types' +import { + type JsonSchemaTypes, + type ProtoTypesWithSchemaInfo, + type VersionObj, + type SchemaName, + type DataTypeId, + type ValidSchemaDef, +} from './types' + +import * as ProtobufEncodeDecode from '../types/proto/index.mapeo' +import { dataTypeIds, knownSchemaVersions } from '../config' +import { + convertProject, + convertField, + convertObservation, +} from './lib/decode-conversions' +// @ts-ignore +import * as cenc from 'compact-encoding' + +const dataTypeIdSize = 6 +const schemaVersionSize = 2 + +/** Map of dataTypeIds to schema names for quick lookups */ +const dataTypeIdToSchemaName: Record = {} +for (const [schemaName, dataTypeId] of Object.entries(dataTypeIds) as Array< + [SchemaName, DataTypeId] +>) { + dataTypeIdToSchemaName[dataTypeId] = schemaName +} + +/** + * Decode a Buffer as an object validated against the corresponding schema + * + * @param buf Buffer to be decoded + * @param versionObj public key (coreId) of the core where this block is stored, and the index (seq) of the block in the core. + * */ +export function decode(buf: Buffer, versionObj: VersionObj): JsonSchemaTypes { + const schemaDef = decodeBlockPrefix(buf) + + const encodedMsg = buf.subarray( + dataTypeIdSize + schemaVersionSize, + buf.length + ) + + const messageWithSchemaInfo = + ProtobufEncodeDecode[getProtoTypeName(schemaDef)].decode(encodedMsg) + + const message = mutatingSetSchemaDef(messageWithSchemaInfo, schemaDef) + + switch (message.schemaName) { + case 'project': + return convertProject(message, versionObj) + case 'observation': + return convertObservation(message, versionObj) + case 'field': + return convertField(message, versionObj) + default: + const _exhaustiveCheck: never = message + return message + } +} + +/** + * @private - exported for unit tests + * Given a buffer, return a (valid) schemaVersion and schemaName + * Will throw if dataTypeId and schema version is unknown + */ +export function decodeBlockPrefix(buf: Buffer): ValidSchemaDef { + if (buf.length < dataTypeIdSize + schemaVersionSize) { + throw new Error('Invalid block prefix - unexpected prefix length') + } + const state = cenc.state() + state.buffer = buf + state.start = 0 + state.end = dataTypeIdSize + const dataTypeId = cenc.hex.fixed(dataTypeIdSize).decode(state) + + if (typeof dataTypeId !== 'string') { + throw new Error('Invalid block prefix, could not decode dataTypeId') + } + + state.start = dataTypeIdSize + state.end = dataTypeIdSize + schemaVersionSize + const schemaVersion = cenc.uint16.decode(state) + + if (typeof schemaVersion !== 'number') { + throw new Error('Invalid block prefix, could not decode schemaVersion') + } + + const schemaName = dataTypeIdToSchemaName[dataTypeId] + + if (!schemaName) { + throw new Error(`Unknown dataTypeId '${dataTypeId}'`) + } + + const schemaDef = { schemaName, schemaVersion } + assertKnownSchemaDef(schemaDef) + return schemaDef +} + +/** + * Adds schemaName and schemaVersion to a decoded prototype message. + * __MUTATES__ the passed parameter, for performance reasons. Ok because we + * do not use the passed paramter anywhere else. + * Not strictly type checked, but the returns strict types which can be trusted + */ +function mutatingSetSchemaDef( + obj: T, + props: K +): ProtoTypesWithSchemaInfo { + for (const prop of Object.keys(props)) { + ;(obj as any)[prop] = (props as any)[prop] + } + return obj as any +} + +/** + * Assert that a given schemaName and schemaVersion is "known", e.g. do we know how to process it? + * TODO: Accept "future" schema versions, because protobuf decoding should be forward-compatible + */ +function assertKnownSchemaDef(schemaDef: { + schemaName: SchemaName + schemaVersion: number +}): asserts schemaDef is ValidSchemaDef { + const { schemaName, schemaVersion } = schemaDef + if (knownSchemaVersions[schemaName].includes(schemaDef.schemaVersion)) { + throw new Error( + `Unknown schema version '${schemaVersion}' for schema '${schemaName}'` + ) + } +} + +/** + * Get the name of the type, e.g. `Observation_5` for schemaName `observation` + * and schemaVersion `1` + */ +function getProtoTypeName(schemaDef: ValidSchemaDef): ProtoTypeNames { + return (capitalize(schemaDef.schemaName) + + '_' + + schemaDef.schemaVersion) as ProtoTypeNames +} + +function capitalize(str: T): Capitalize { + return (str.charAt(0).toUpperCase() + str.slice(1)) as any +} + +// function mutatingOmit(obj: T, key: K): OmitUnion { +// delete (obj as any)[key] +// return obj as any +// } diff --git a/src/lib/decode-conversions.ts b/src/lib/decode-conversions.ts new file mode 100644 index 00000000..55433199 --- /dev/null +++ b/src/lib/decode-conversions.ts @@ -0,0 +1,179 @@ +// This file exports a function for converting each supported schema type (all +// known versions) to the latest JSONSchema type +import { + type TagValue_1, + type TagValue_1_PrimitiveValue, +} from '../../types/proto/tags/v1' +import { + type JsonSchemaTypes, + type ProtoTypesWithSchemaInfo, + type VersionObj, + type SchemaName, + type FilterBySchemaName, +} from '../types' + +/** The `tags` field supports only a subset of JSON values - we don't support nested tags, just primitives or arrays of primitives */ +type TagValuePrimitive = number | string | boolean | null | undefined +type JsonTagValue = + | TagValuePrimitive + | Array> + +/** Just the common (shared) props from JSON schema types */ +type JsonSchemaCommon = Pick + +/** Union of keys from the common prop on Proto types */ +type ProtoTypeCommonKeys = keyof Exclude< + ProtoTypesWithSchemaInfo['common'], + undefined +> + +/** Function type for converting a protobuf type of any version for a particular + * schema name, and returning the most recent JSONSchema type */ +type ConvertFunction = ( + message: Extract, + versionObj: VersionObj +) => FilterBySchemaName + +export const convertProject: ConvertFunction<'project'> = ( + message, + versionObj +) => { + const { common, schemaVersion, ...rest } = message + const jsonSchemaCommon = convertCommon(common, versionObj) + return { + ...jsonSchemaCommon, + ...rest, + } +} + +export const convertObservation: ConvertFunction<'observation'> = ( + message, + versionObj +) => { + const { common, schemaVersion, ...rest } = message + const jsonSchemaCommon = convertCommon(common, versionObj) + + return { + ...jsonSchemaCommon, + ...rest, + refs: message.refs?.map(({ id }) => ({ id: id.toString('hex') })), + attachments: message.attachments?.map(({ driveId, name, type }) => { + return { driveId: driveId.toString('hex'), name, type } + }), + tags: convertTags(message.tags), + } +} + +type FieldOptions = FilterBySchemaName['options'] + +export const convertField: ConvertFunction<'field'> = (message, versionObj) => { + const { common, schemaVersion, ...rest } = message + const jsonSchemaCommon = convertCommon(common, versionObj) + if (!message.tagKey) { + // We can't do anything with a field without a tag key, so we ignore these + throw new Error('Missing tagKey on field') + } else { + return { + ...jsonSchemaCommon, + ...rest, + type: message.type == 'UNRECOGNIZED' ? 'text' : message.type, + tagKey: message.tagKey, + label: message.label || message.tagKey, + appearance: + message.appearance === 'UNRECOGNIZED' + ? 'multiline' + : message.appearance, + options: message.options.reduce>( + (acc, { label, value }) => { + // Filter out any options where value is undefined (this would still be valid protobuf, but not valid for our code) + if (!value) return acc + const convertedValue = convertTagPrimitive(value) + if (typeof convertedValue === 'undefined') return acc + acc.push({ label, value: convertedValue }) + return acc + }, + [] + ), + } + } +} + +function convertTags(tags: { [key: string]: TagValue_1 } | undefined): { + [key: string]: Exclude +} { + if (!tags) return {} + return Object.keys(tags).reduce<{ + [key: string]: Exclude + }>((acc, key) => { + // Ignore (filter out) undefined entries in an array + const convertedValue = tags[key] && convertTagValue(tags[key]) + if (typeof convertedValue !== 'undefined') { + acc[key] = convertedValue + } + return acc + }, {}) +} + +function convertTagValue({ kind }: TagValue_1): JsonTagValue { + if (!kind) return undefined + switch (kind.$case) { + case 'list_value': + return kind.list_value.list_value.reduce< + Exclude[] + >((acc, value) => { + const convertedValue = convertTagPrimitive(value) + if (typeof convertedValue !== 'undefined') { + acc.push(convertedValue) + } + return acc + }, []) + case 'primitive_value': + return convertTagPrimitive(kind.primitive_value) + default: + const _exhaustiveCheck: never = kind + return kind + } +} + +function convertTagPrimitive({ + kind, +}: TagValue_1_PrimitiveValue): TagValuePrimitive { + if (!kind) return undefined + switch (kind.$case) { + case 'null_value': + return null + case 'boolean_value': + return kind.boolean_value + case 'number_value': + return kind.number_value + case 'string_value': + return kind.string_value + default: + const _exhaustiveCheck: never = kind + return _exhaustiveCheck + } +} + +function convertCommon( + common: ProtoTypesWithSchemaInfo['common'], + versionObj: VersionObj +): JsonSchemaCommon { + if (!common || !common.id || !common.createdAt || !common.updatedAt) { + throw new Error('Missing required common properties') + } + + return { + id: common.id.toString('hex'), + version: versionObjToString(versionObj), + links: common.links.map(versionObjToString), + createdAt: common.createdAt, + updatedAt: common.updatedAt, + } +} + +/** + * Turn coreId and seq to a version string of ${hex-encoded coreId}/${seq} + */ +function versionObjToString({ coreId, seq }: VersionObj) { + return `${coreId.toString('hex')}/${seq}` +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..86dd720a --- /dev/null +++ b/src/types.ts @@ -0,0 +1,50 @@ +// Shared types +import { type ProtoTypesWithSchemaInfo as AllProtoTypesWithSchemaInfo } from '../types/proto/types' +import { type JsonSchemaTypes as AllJsonSchemaTypes } from '../types/schema' +import { dataTypeIds } from '../config' + +/** Temporary: once we have completed this module everything should be supported */ +type SupportedSchemaNames = 'project' | 'observation' | 'field' + +export type SchemaName = Extract + +/** Union of all valid schemaName and schemaVersion combinations (not just current versions) */ +export type ValidSchemaDef = PickUnion< + ProtoTypesWithSchemaInfo, + 'schemaName' | 'schemaVersion' +> + +/** Filter a union of objects to only include those that have a prop `schemaName` that matches U */ +export type FilterBySchemaName< + T extends { schemaName: string }, + U extends string +> = Extract + +/** Uniquely identifies a block in a core */ +export type VersionObj = { coreId: Buffer; seq: number } + +/** Only proto types we currently support (whilst in dev) */ +export type ProtoTypesWithSchemaInfo = FilterBySchemaName< + AllProtoTypesWithSchemaInfo, + SupportedSchemaNames +> + +/** Only jsonschema types we currently support (whilst in dev) */ +export type JsonSchemaTypes = FilterBySchemaName< + AllJsonSchemaTypes, + SupportedSchemaNames +> + +/** Union of all valid data type ids */ +export type DataTypeId = Values + +// HELPER TYPES +/** + * This is a Pick over a union, that keeps it as a distributive type + * (normal pick will loose the distributive type) + */ +type PickUnion = T extends any ? Pick : never +/** Omit over a union, that keeps it as a distributive type */ +type OmitUnion = T extends any ? Omit : never +/** Return a union of object values */ +type Values = T[keyof T] diff --git a/tsconfig.json b/tsconfig.json index 57c5bf21..cfb80f1c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,11 @@ "declaration": true, "declarationMap": false }, - "include": ["types/proto/*.ts", "types/schema/*.d.ts"], + "include": [ + "types/proto/*.ts", + "types/schema/*.ts", + "src/**/*.ts", + "index.js" + ], "exclude": ["node_modules"] } diff --git a/utils.js b/utils.js index 150b78a3..40779e55 100644 --- a/utils.js +++ b/utils.js @@ -1,3 +1,5 @@ +import schemasPrefix from './schemasPrefix.js' + /** * Format schema type string to match protobuf/schema prefix type lookups * @param {String} str @@ -9,18 +11,48 @@ export const formatSchemaType = (str) => /** * Turn a schema type and version into `${type}_${schemaVersion}` * @param {String | undefined} type - * @param {Number | undefined} schemaVersion */ -export const formatSchemaKey = (type, schemaVersion) => - `${formatSchemaType(type)}_${schemaVersion}` +export const formatSchemaKey = (type) => + `${formatSchemaType(type)}_${getLastVersionForSchema(type).toString()}` + +/** + * Turn a hex-encoded string to a byte buffer + * @param {String} hexStr + * @returns {Buffer} + */ +export const hexStringToBuffer = (hexStr) => Buffer.from(hexStr, 'hex') + +/** + * Turn a hex-encoded string to a byte buffer + * @param {Buffer} buf + * @returns {String} + */ +export const bufferToHexString = (buf) => buf.toString('hex') + +/** + * Get the last version of a schema given a schemaType + * @param {String} schemaType + * @returns {number} schemaVersion + */ +export const getLastVersionForSchema = (schemaType) => { + const versions = schemasPrefix[schemaType]['schemaVersions'] + return versions.reduce((a, b) => Math.max(a, b), -Infinity) +} + +/** + * Turn a an object with {coreId:Buffer, seq:Number} to hex-encoded string of coreId/seq to + * @param {String} hexStr + * @returns {{coreId:Buffer, seq: Number}} + */ +export const hexStringToCoreVersion = (hexStr) => { + const [id, seq] = hexStr.split('/') + return { coreId: Buffer.from(id, 'hex'), seq: Number(seq) } +} /** - * Checks if the type of record inherits from a common one - * @param {String} key - type of doc build from ${type}_${schemaVersion} - * @returns {boolean} + * Turn a an object with {coreId:Buffer, seq:Number} to hex-encoded string of coreId/seq to + * @param {{coreId:Buffer, seq: Number}} coreVersionObj + * @returns {String} */ -export const inheritsFromCommon = (key) => - key !== 'Observation_4' && - key !== 'Preset_1' && - key !== 'Filter_1' && - key !== 'Field_1' +export const coreVersionToHexString = (coreVersionObj) => + `${coreVersionObj.coreId.toString('hex')}/${coreVersionObj.seq}`