npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@digiv3rse/metadata

v1.1.1

Published

DiGiV3rse Protocol Metadata Standards

Downloads

3

Readme

DiGiV3rse Protocol Metadata Standards

Schema validation and TS types for LIP-2 DiGiV3rse Protocol Metadata Standards.

Features

  • Zod schema definitions
  • JSON Schema definitions
  • TypeScript type definitions

Installation

# npm:
npm install @digiv3rse/metadata zod

# yarn:
yarn add @digiv3rse/metadata zod

# pnpm:
pnpm add @digiv3rse/metadata zod

[!NOTE]
zod is marked as optional peer dependency, so if you all you need is the JSON Schema definitions, you can install @digiv3rse/metadata without zod.

Usage

Compose

Publication metadata

You can create compliant PublicationMetadata objects via the following builder functions:

import {
  article,
  audio,
  checkingIn,
  embed,
  event,
  image,
  link,
  livestream,
  mint,
  space,
  story,
  textOnly,
  threeD,
  transaction,
  video,
} from '@digiv3rse/metadata';

const json = article({
  content: 'The content of the article',
});

[!NOTE] Use the type definitions to explore the available properties and their types. The builders will throw a ValidationError with instructions on how to fix the error if the object is not compliant with the schema.

We also provided a set of builder function for specific metadata sub-types (list to be expanded):

import { geoUri } from '@digiv3rse/metadata';

const uri = geoUri({
  lat: 51.5074,
  lng: 0.1278,
});

Mirror metadata

You can create compliant MirrorMetadata objects via the following builder function:

import { mirror } from '@digiv3rse/metadata';

const json = mirror({
  appId: 'foobar-app',
});

[!NOTE] Use the type definitions to explore the available properties and their types. The builder will throw a ValidationError with instructions on how to fix the error if the object is not compliant with the schema.

Profile metadata

You can create compliant ProfileMetadata objects via the following builder function:

import { profile } from '@digiv3rse/metadata';

const json = profile({
  name: 'Bob',

  bio: 'I am a DiGi user',
});

[!NOTE] Use the type definitions to explore the available properties and their types. The builder will throw a ValidationError with instructions on how to fix the error if the object is not compliant with the schema.

Parse

Assuming we have 2 JS objects:

const valid = {
  /** example of valid metadata **/
};
const invalid = {
  /** example of invalid metadata **/
};

Publication metadata

Publication metadata schema is a union of all content schemas (e.g. ArticleMetadata, AudioMetadata, etc. but NOT MirrorMetadata).

Use it to parse the metadata referenced by contentURI of Comment, Mirror, and Quote publications.

import { PublicationMetadataSchema } from '@digiv3rse/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 }

Mirror metadata

Mirror metadata schema serve the purpose allowing mirrors be associated to a given DiGi app (via the appId) as well as specify some operational flags (e.g. hideFromFeed and globalReach).

Use it to parse the metadata referenced by metadataURI of Mirror publications.

import { MirrorMetadataSchema } from '@digiv3rse/metadata';

MirrorMetadataSchema.parse(valid); // => MirrorMetadata
MirrorMetadataSchema.parse(invalid); // => throws ZodError

// OR

MirrorMetadataSchema.safeParse(valid);
// => { success: true, data: MirrorMetadata }
MirrorMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }

Profile metadata

import { ProfileMetadataSchema } from '@digiv3rse/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 }

Extract version number

A convenience extractVersion function is available to extract the version from a parsed PublicationMetadata or ProfileMetadata.

import {
  extractVersion,
  PublicationMetadataSchema,
  ProfileMetadataSchema,
} from '@digiv3rse/metadata';

const publicationMetadata = PublicationMetadataSchema.parse(valid);

extractVersion(publicationMetadata); // => '3.0.0'

const profileMetadata = ProfileMetadataSchema.parse(valid);

extractVersion(profileMetadata); // => '2.0.0'

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 '@digiv3rse/metadata';

const result = PublicationMetadataSchema.safeParse(invalid);

if (!result.success) {
  console.log(formatZodError(result.error));
}

Types

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 '@digiv3rse/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;

  // ...
}

AccessCondition

import { AccessCondition, ConditionType, PublicationMetadataSchema } from '@digiv3rse/metadata';

const publicationMetadata = PublicationMetadataSchema.parse(valid);

switch (publicationMetadata.encryptedWith?.accessCondition.type) {
  case ConditionType.AND:
    // accessCondition is AndCondition
    break;
  case ConditionType.OR:
    // accessCondition is OrCondition
    break;
  case ConditionType.NFT_OWNERSHIP:
    // accessCondition is NftOwnershipCondition
    break;
  case ConditionType.EOA_OWNERSHIP:
    // accessCondition is EoaOwnershipCondition
    break;

  // ...
}

MetadataAttribute

import { MetadataAttribute, MetadataAttributeType } from '@digiv3rse/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;
}

Other 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 '@digiv3rse/metadata';

Legacy metadata formats

The package also exports parsers for legacy metadata formats via the @digiv3rse/metadata/legacy entrypoint.

[!WARNING] DO NOT mix and match legacy and new metadata TS types and enums. Although they share some similarities they are not meant to be interoperable. For example if you are checking mainContentFocus of PublicationMetadataV2 use the PublicationMainFocus exported from @digiv3rse/metadata/legacy and NOT the one from the main @digiv3rse/metadata entrypoint.

You can parse legacy Publication Metadata v1 and v2 via:

import { PublicationMetadataSchema } from '@digiv3rse/metadata/legacy';

PublicationMetadataSchema.parse(valid); // => PublicationMetadata
PublicationMetadataSchema.parse(invalid); // => throws ZodError

// OR

PublicationMetadataSchema.safeParse(valid);
// => { success: true, data: PublicationMetadata }
PublicationMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }

Legacy PublicationMetadata is a discriminated union of PublicationMetadataV1 and PublicationMetadataV2 where the version property is the discriminant.

In turn legacy.PublicationMetadataV2 is a discriminated union of:

  • PublicationMetadataV2Article
  • PublicationMetadataV2Audio
  • PublicationMetadataV2Embed
  • PublicationMetadataV2Image
  • PublicationMetadataV2Link
  • PublicationMetadataV2TextOnly
  • PublicationMetadataV2Video

where the mainContentFocus property is the discriminant.

import {
  PublicationMetadataSchema,
  PublicationMetadataVersion,
  PublicationMainFocus,
} from '@digiv3rse/metadata/legacy';

const publicationMetadata = PublicationMetadataSchema.parse(valid);

switch (publicationMetadata.version) {
  case PublicationMetadataVersion.V1:
    // publicationMetadata is PublicationMetadataV1
    break;
  case PublicationMetadataVersion.V2:
    // publicationMetadata is PublicationMetadataV2

    switch (publicationMetadata.mainContentFocus) {
      case PublicationMainFocus.ARTICLE:
        // publicationMetadata is PublicationMetadataV2Article
        break;
      case PublicationMainFocus.VIDEO:
        // publicationMetadata is PublicationMetadataV2Video
        break;

      // ...
    }
    break;
}

You can also parse legacy Profile Metadata (aka v1) via:

import { ProfileMetadataSchema } from '@digiv3rse/metadata/legacy';

ProfileMetadataSchema.parse(valid); // => ProfileMetadata
ProfileMetadataSchema.parse(invalid); // => throws ZodError

// OR

ProfileMetadataSchema.safeParse(valid);
// => { success: true, data: ProfileMetadata }
ProfileMetadataSchema.safeParse(invalid);
// => { success: false, error: ZodError }

Similarly to the main entrypoint the @digiv3rse/metadata/legacy entrypoint also exports all the types and enums that you might need to work with the legacy metadata (some examples below).

import {
  // enums
  AudioMimeType,
  ImageMimeType,
  PublicationMainFocus,
  PublicationMetadataVersion,
  VideoMimeType,

  // main types
  ProfileMetadata,
  PublicationMetadata,
  PublicationMetadataV1,
  PublicationMetadataV2,
  PublicationMetadataV2Article,
  PublicationMetadataV2Audio,
  PublicationMetadataV2Embed,
  PublicationMetadataV2Image,
  PublicationMetadataV2Link,
  PublicationMetadataV2TextOnly,
  PublicationMetadataV2Video,

  // others
  AccessCondition,
  AndCondition,
  CollectCondition,
  EncryptedFields,
  EncryptedMedia,
  EoaOwnership,
  Erc20Ownership,
  FollowCondition,
  MarketplaceMetadata,
  MarketplaceMetadataAttribute,
  Media,
  NftOwnership,
  OrCondition,
  ProfileMetadataAttribute,
  ProfileOwnership,

  // branded aliases
  Locale,
  Markdown,
  Signature,
  URI,
  AppId,
  Datetime,
} from '@digiv3rse/metadata/legacy';

[!NOTE] If you find yourself in a position of importing from both @digiv3rse/metadata and @digiv3rse/metadata/legacy entrypoints in the same module. You can you can use ESModule aliasing to avoid conflicts: import * as legacy from '@digiv3rse/metadata/legacy' and then use the legacy types, enums, and parsers under legacy.*.

JSON schemas

Importing JSON schema in TypeScript is a simple as:

import audio from '@digiv3rse/metadata/jsonschemas/publications/audio/3.0.0.json' assert { type: 'json' };

import audio from '@digiv3rse/metadata/jsonschemas/publications/article/3.0.0.json' assert { type: 'json' };

import mirror from '@digiv3rse/metadata/jsonschemas/publications/mirror/1.0.0.json' assert { type: 'json' };

import profile from '@digiv3rse/metadata/jsonschemas/profile/2.0.0.json' assert { type: 'json' };

You can the use them in your JSON Schema validator of choice, for example ajv.

Versioning

The DiGiV3rse 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.digiv3rse.xyz/publications/article/3.0.0.json",

  "digi": {
    "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 PublicationSchemaId enum entry with value of PublicationSchemaId.AUDIO_LATEST. Name it after the current schema version (e.g. AUDIO_V1_0_0).
  • rename the existing AudioSchema into AudioV1_0_0Schema and update the $schema value to PublicationSchemaId.AUDIO_V1_0_0
  • increase the version number of the PublicationSchemaId.AUDIO_LATEST based on the nature of your changes. Remember to follow semver rules.
  • create a new AudioSchema with the new schema definition and use the PublicationSchemaId.AUDIO_LATEST as $schema value
  • update the scripts/build.ts script to include the new schema and old schema files under the correct version file name in the jsonschemas/publications/audio folder
  • 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),
  digi: AudioMetadataDetailsSchema,
});
export type AudioMetadata = z.infer<typeof AudioSchema>;

export const AudioV1Schema = publicationWith({
  $schema: z.literal(PublicationSchemaId.AUDIO_V1_0_0),
  digi: 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.digi is AudioMetadataDetails
    break;
  // ...
}

Contributing

To contribute to the DiGiV3rse Protocol Metadata Standards, please fork this repository and submit a pull request with your changes.

To run the unit tests, run:

pnpm test:unit

Pro-tip: you can run pnpm test:unit --watch to run the tests in watch mode.

To build the project, run:

pnpm build

Generate and include up to date documentation with:

pnpm typedoc:docs

Add changeset with:

pnpm changeset add

Use keepachangelog format for the changeset message.

Releasing

Release flow is managed by changesets.

To release a new version follow the steps below:

  1. Create a new branch from main with the name release/<version>
  2. Build the project
pnpm install && pnpm build && pnpm typedoc:docs
  1. Update relevant package.json's versions and update CHANGELOG.md with:
pnpm changeset version
  1. Review, commit and push the changes
  2. Create a PR from release/<version> to main
  3. Once approved, publish with (you need to be logged in to npm authorized to publish under @digiv3rse):
pnpm changeset publish
  1. Push the tags
git push origin release/<version> --follow-tags
  1. Merge the PR with a merge commit

License

DiGiV3rse Protocol Metadata Standards is MIT licensed

Support

See the DiGi API and SDK channel on our Discord