Package Exports
- @lens-protocol/metadata
- @lens-protocol/metadata/dist/index.cjs
- @lens-protocol/metadata/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@lens-protocol/metadata) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Lens Protocol Metadata Standards
Schema vaidation and TS types for LIP-2 Lens Protocol Metadata Standards.
Features
- Zod schema definitions
- JSON Schema definitions
- TypeScript type definitions
Installation
# npm:
npm install @lens-protocol/metadata zod
# yarn:
yarn add @lens-protocol/metadata zod
# pnpm:
pnpm add @lens-protocol/metadata zod[!NOTE]
zodis marked as optional peer dependency, so if you all you need is the JSON Schema definitions, you can install@lens-protocol/metadatawithoutzod.
Usage
Assuming we have 2 JS objects:
const valid = {
/** example of valid metadata **/
};
const invalid = {
/** example of invalid metadata **/
};Publication metadata
import { PublicationMetadataSchema } from '@lens-protocol/metadata';
PublicationMetadataSchema.parse(valid); // => PublicationMetadata
PublicationMetadataSchema.parse(invalid); // => throws ZodError
// OR
PublicationMetadataSchema.safeParse(valid);
// => { success: true, data: PublicationMetadata }
PublicationMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }You can also parse legacy Publication Metadata v2 and v1 via:
import { legacy } from '@lens-protocol/metadata';
legacy.PublicationMetadataSchema.parse(valid); // => legacy.PublicationMetadata
legacy.PublicationMetadataSchema.parse(invalid); // => throws ZodError
// OR
legacy.PublicationMetadataSchema.safeParse(valid);
// => { success: true, data: legacy.PublicationMetadata }
legacy.PublicationMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }[!WARNING]
When working with thelegacynamespace make sure to use enums from tha same namespace, e.g.legacy.PublicationMainFocusinstead ofPublicationMainFocusto avoid confusion.
Profile metadata
import { ProfileMetadataSchema } from '@lens-protocol/metadata';
ProfileMetadataSchema.parse(valid); // => ProfileMetadata
ProfileMetadataSchema.parse(invalid); // => throws ZodError
// OR
ProfileMetadataSchema.safeParse(valid);
// => { success: true, data: ProfileMetadata }
ProfileMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }Format validation error
ZodError contains all the information needed to inform you about the validation error, but it's not very user friendly. You can use formatZodError to get a more readable error message.
import { PublicationMetadataSchema, formatZodError } from '@lens-protocol/metadata';
const result = PublicationMetadataSchema.safeParse(invalid);
if (!result.success) {
console.log(formatZodError(result.error));
}Narrowing types
Every time you have a discriminated union, you can use the discriminant to narrow the type. See few examples below.
PublicationMetadata
import {
PublicationMetadata,
PublicationMetadataSchema,
PublicationSchemaId,
} from '@lens-protocol/metadata';
const publicationMetadata = PublicationMetadataSchema.parse(valid);
switch (publicationMetadata.$schema) {
case PublicationSchemaId.ARTICLE_LATEST:
// publicationMetadata is ArticleMetadata
break;
case PublicationSchemaId.AUDIO_LATEST:
// publicationMetadata is AudioMetadata
break;
case PublicationSchemaId.IMAGE_LATEST:
// publicationMetadata is ImageMetadata
break;
case PublicationSchemaId.TEXT_ONLY_LATEST:
// publicationMetadata is TextOnlyMetadata
break;
// ...
}legacy.PublicationMetadata
legacy.PublicationMetadata is a discriminated union of legacy.PublicationMetadataV1 and legacy.PublicationMetadataV2 where the version property is the discriminant.
In turn legacy.PublicationMetadataV2 is a discriminated union of:
legacy.PublicationMetadataV2Articlelegacy.PublicationMetadataV2Audiolegacy.PublicationMetadataV2Embedlegacy.PublicationMetadataV2Imagelegacy.PublicationMetadataV2Linklegacy.PublicationMetadataV2TextOnlylegacy.PublicationMetadataV2Video
where the mainContentFocus property is the discriminant.
import { legacy } from '@lens-protocol/metadata';
const publicationMetadata = legacy.PublicationMetadataSchema.parse(valid);
switch (publicationMetadata.version) {
case legacy.PublicationMetadataVersion.V1:
// publicationMetadata is legacy.PublicationMetadataV1
break;
case legacy.PublicationMetadataVersion.V2:
// publicationMetadata is legacy.PublicationMetadataV2
switch (publicationMetadata.mainContentFocus) {
case legacy.PublicationMainFocus.ARTICLE:
// publicationMetadata is legacy.PublicationMetadataV2Article
break;
case legacy.PublicationMainFocus.VIDEO:
// publicationMetadata is legacy.PublicationMetadataV2Video
break;
// ...
}
break;
}MetadataAttribute
import { MetadataAttribute, MetadataAttributeType } from '@lens-protocol/metadata';
switch (attribute.type) {
case MetadataAttributeType.BOOLEAN:
// attribute is BooleanAttribute
// value is a string "true" or "false"
break;
case MetadataAttributeType.DATE:
// attribute is DateAttribute
// value is a string in ISO 8601 format
break;
case MetadataAttributeType.NUMBER:
// attribute is NumberAttribute
// value is a string containing a valid JS number
break;
case MetadataAttributeType.STRING:
// attribute is StringAttribute
// value is a string
break;
case MetadataAttributeType.JSON:
// attribute is JSONAttribute
// value is a string allegedly containing a valid JSON, consumers should validate it
break;
}Useful types
The package also exports all enums and types that you might need to work with the metadata.
Use your IDE's autocomplete to explore the available types.
Some examples:
import {
// enums
MediaAudioKind,
MediaAudioMimeType,
MediaImageMimeType,
MediaVideoMimeType,
MetadataAttributeType
PublicationMainFocus,
ThreeDFormat,
// main types
ArticleMetadata,
AudioMetadata,
CheckingInMetadata,
EmbedMetadata,
EventMetadata,
ImageMetadata,
LinkMetadata,
LivestreamMetadata,
MintMetadata,
ProfileMetadata,
PublicationMetadata,
SpaceMetadata,
StoryMetadata,
TextOnlyMetadata,
ThreeDMetadata,
TransactionMetadata,
VideoMetadata,
// others
MetadataAttribute,
MediaAudio,
MediaImage,
MediaVideo,
AnyMedia,
GeoLocation,
BooleanAttribute,
DateAttribute,
NumberAttribute,
StringAttribute,
JSONAttribute,
// branded aliases
Locale,
Markdown,
Signature,
URI,
AppId,
Datetime,
} from '@lens-protocol/metadata';JSON schemas
Importing JSON schema in TypeScript is a simple as:
import audio from '@lens-protocol/metadata/jsonschemas/publications/audio/1.0.0.json' assert { type: 'json' };
import audio from '@lens-protocol/metadata/jsonschemas/publications/article/1.0.0.json' assert { type: 'json' };
import embed from '@lens-protocol/metadata/jsonschemas/profile/1.0.0.json' assert { type: 'json' };You can the use them in your JSON Schema validator of choice, for example ajv.
Versioning
The Lens Protocol Metadata Standards use a self-describing JSON format. All metadata files that adopt this standard MUST have a $schema property that identifies the schema the file conforms to.
{
"$schema": "https://json-schemas.lens.dev/publications/article/1.0.0.json",
"lens": {
"id": "b3d7f1a0-1f75-11ec-9621-0242ac130002",
"content": "The content of the article",
"locale": "en"
}
}The $schema property is a URI that identify the schema type and its version.
Schemas are versioned using Semantic Versioning.
[!NOTE]
Even though schemas are identified by URIs, those identifiers are not necessarily network-addressable. They are just identifiers. Generally, JSON schema validators don’t make HTTP requests (https://) to fetch schemas. Instead, they provide a way to load schemas into an internal schema database. When a schema is referenced by its URI identifier, the schema is retrieved from the internal schema database.
Future changes should aim to be backwards compatible as much as possible.
When adding a new version of a schema, the previous version should be kept for a reasonable amount of time to allow consumers to migrate and to support existing publications.
Adding a new schema
In this example we will add a new version of the AudioSchema schema, but the same process applies to all the other schemas.
- create a new
PublicationSchemaIdenum entry with value ofPublicationSchemaId.AUDIO_LATEST. Name it after the current schema version (e.g.AUDIO_V1_0_0). - rename the existing
AudioSchemaintoAudioV1_0_0Schemaand update the$schemavalue toPublicationSchemaId.AUDIO_V1_0_0 - increase the version number of the
PublicationSchemaId.AUDIO_LATESTbased on the nature of your changes. Remember to follow semver rules. - create a new
AudioSchemawith the new schema definition and use thePublicationSchemaId.AUDIO_LATESTas$schemavalue - update the
scripts/build.tsscript to include the new schema and old schema files under the correct version file name in thejsonschemas/publications/audiofolder - release a new version of this package according to the nature of the changes (new major version of a schema = new major version of the package, etc.)
In case the changes are backwards compatible, you could create a single AudioMetadataDetailsSchema definition and just declare 2 schemas out of it, one for the old version and one for the new version. For example:
export const AudioMetadataDetailsSchema = metadataDetailsWith({
mainContentFocus: mainContentFocus(PublicationMainFocus.AUDIO),
audio: MediaAudioSchema,
attachments: AnyMediaSchema.array()
.min(1)
.optional()
.describe('The other attachments you want to include with it.'),
/** e.g. new optional fields */
});
export type AudioMetadataDetails = z.infer<typeof AudioMetadataDetailsSchema>;
export const AudioSchema = publicationWith({
$schema: z.literal(PublicationSchemaId.AUDIO_LATEST),
lens: AudioMetadataDetailsSchema,
});
export type AudioMetadata = z.infer<typeof AudioSchema>;
export const AudioV1Schema = publicationWith({
$schema: z.literal(PublicationSchemaId.AUDIO_V1_0_0),
lens: AudioMetadataDetailsSchema,
});
export type AudioV1Metadata = z.infer<typeof AudioV1Schema>;In this case consumers of this package can take advantage of the structural likeness and just do the following:
switch (publicationMetadata.$schema) {
case PublicationSchemaId.AUDIO_V1_0_0:
case PublicationSchemaId.AUDIO_LATEST:
// publicationMetadata.lens is AudioMetadataDetails
break;
// ...
}Contributing
To contribute to the Lens Protocol Metadata Standards, please fork this repository and submit a pull request with your changes.
To build the project, run:
pnpm buildAdd changeset with:
pnpm changeset addUse keepachangelog format for the changeset message.
Releasing
Release flow is managed by changesets.
To release a new version follow the steps below:
- Create a new branch from
mainwith the namerelease/<version> - Build the project
pnpm install && pnpm build- Update relevant
package.json's versions and updateCHANGELOG.mdfor each package:
pnpm changeset version- Review, commit and push the changes
- Create a PR from
release/<version>tomain - Once approved, publish with (you need to be logged in to npm authorized to publish under
@lens-protocol):
pnpm changeset publish- Push the tags
git push origin release/<version> --follow-tags- Merge the PR with a merge commit
License
Lens Protocol Metadata Standards is MIT licensed
Support
See the Lens API and SDK channel on our Discord