diff --git a/.changeset/pre.json b/.changeset/pre.json index f5a3b16..5cb01cf 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -25,6 +25,7 @@ "popular-spies-enjoy", "proud-pens-hammer", "quick-pets-itch", + "sharp-clocks-tell", "shiny-pans-fix", "shy-cougars-boil", "shy-falcons-sip", diff --git a/.changeset/sharp-clocks-tell.md b/.changeset/sharp-clocks-tell.md new file mode 100644 index 0000000..a09e4bb --- /dev/null +++ b/.changeset/sharp-clocks-tell.md @@ -0,0 +1,5 @@ +--- +'@lens-protocol/metadata': patch +--- + +**Added** metadata builder functions diff --git a/CHANGELOG.md b/CHANGELOG.md index 475c424..c8be1f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @lens-protocol/metadata +## 0.1.0-alpha.16 + +### Patch Changes + +- b111da8: **Added** metadata builder functions + ## 0.1.0-alpha.15 ### Patch Changes diff --git a/README.md b/README.md index d8e1d2c..5790834 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,35 @@ PublicationMetadataSchema.safeParse(invalid); // => { success: false, error: ZodError } ``` +You can also create compliant `PublicationMetadata` objects via the following builder functions: + +```typescript +import { + article, + audio, + checkingIn, + embed, + event, + image, + link, + livestream, + mint, + space, + story, + textOnly, + threeD, + transaction, + video, +} from '@lens-protocol/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. + ### Mirror metadata Mirror metadata schema serve the purpose allowing mirrors be associated to a given Lens app (via the `appId`) as well as specify some operational flags (e.g. `hideFromFeed` and `globalReach`). @@ -99,6 +128,19 @@ MirrorMetadataSchema.safeParse(invalid); // => { success: false, error: ZodError } ``` +You can also create compliant `MirrorMetadata` objects via the following builder function: + +```typescript +import { mirror } from '@lens-protocol/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 ```typescript @@ -115,6 +157,21 @@ ProfileMetadataSchema.safeParse(invalid); // => { success: false, error: ZodError } ``` +You can also create compliant `ProfileMetadata` objects via the following builder function: + +```typescript +import { profile } from '@lens-protocol/metadata'; + +const json = profile({ + name: 'Bob', + + bio: 'I am a Lens 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. + ### Extract version number A convenience `extractVersion` function is available to extract the version from a parsed `PublicationMetadata` or `ProfileMetadata`. diff --git a/jest.config.ts b/jest.config.ts index 3b7b906..336d6a4 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -3,8 +3,8 @@ import type { Config } from 'jest'; const config: Config = { preset: 'ts-jest', testEnvironment: 'node', - testRegex: '/__tests__/.*|(\\.|/)spec\\.ts?$', - testPathIgnorePatterns: ['/node_modules/', '/dist/'], + testRegex: '/__tests__/.*|(\\.|/)spec\\.ts$', + testPathIgnorePatterns: ['\\.snap$', '/node_modules/', '/dist/'], moduleNameMapper: { // see https://github.com/jestjs/jest/issues/9430#issuecomment-1676252021 '^(\\.{1,2}/.*)\\.js$': '$1', diff --git a/package.json b/package.json index 3f020db..e483ce2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lens-protocol/metadata", - "version": "0.1.0-alpha.15", + "version": "0.1.0-alpha.16", "description": "Lens Protocol Metadata Standards", "type": "module", "main": "./dist/index.cjs", @@ -64,6 +64,7 @@ "@jest/globals": "^29.6.2", "@types/fs-extra": "^11.0.1", "@types/node": "^20.5.0", + "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^6.4.0", "eslint": "^8.47.0", @@ -96,5 +97,8 @@ "pnpm": "^8.0.0", "npm": "^8.19.2" }, - "packageManager": "pnpm@8.2.0" + "packageManager": "pnpm@8.2.0", + "dependencies": { + "uuid": "^9.0.1" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e40fae..45e6e11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,10 @@ lockfileVersion: '6.0' +dependencies: + uuid: + specifier: ^9.0.1 + version: 9.0.1 + devDependencies: '@changesets/cli': specifier: ^2.26.2 @@ -16,6 +21,9 @@ devDependencies: '@types/node': specifier: ^20.5.0 version: 20.5.0 + '@types/uuid': + specifier: ^9.0.4 + version: 9.0.4 '@typescript-eslint/eslint-plugin': specifier: ^6.4.0 version: 6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.1.6) @@ -1347,6 +1355,10 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true + /@types/uuid@9.0.4: + resolution: {integrity: sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==} + dev: true + /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true @@ -4946,6 +4958,11 @@ packages: punycode: 2.3.0 dev: true + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false + /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true diff --git a/scripts/build.ts b/scripts/build.ts index 133020c..a0b50e7 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -33,7 +33,7 @@ import { SpaceSchema, StorySchema, TextOnlySchema, - ThreeDMetadataSchema, + ThreeDSchema, TransactionSchema, VideoSchema, ProfileIdSchema, @@ -55,7 +55,7 @@ await fs.ensureDir(outputDir); // Publication schemas const schemas = new Map>([ - ['publications/3D/3.0.0.json', ThreeDMetadataSchema], + ['publications/3D/3.0.0.json', ThreeDSchema], ['publications/article/3.0.0.json', ArticleSchema], ['publications/audio/3.0.0.json', AudioSchema], ['publications/checking-in/3.0.0.json', CheckingInSchema], diff --git a/src/__tests__/__snapshots__/builders.spec.ts.snap b/src/__tests__/__snapshots__/builders.spec.ts.snap new file mode 100644 index 0000000..988aa0b --- /dev/null +++ b/src/__tests__/__snapshots__/builders.spec.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Given the publication metadata builders when using the article builder should return a valid TextOnlyMetadata 1`] = ` +{ + "$schema": "https://json-schemas.lens.dev/publications/article/3.0.0.json", + "lens": { + "content": "GM!", + "id": Any, + "locale": "en", + "mainContentFocus": "ARTICLE", + }, +} +`; + +exports[`Given the publication metadata builders when using the textOnly builder should return a valid TextOnlyMetadata 1`] = ` +{ + "$schema": "https://json-schemas.lens.dev/publications/text-only/3.0.0.json", + "lens": { + "content": "GM!", + "id": Any, + "locale": "en", + "mainContentFocus": "TEXT_ONLY", + }, +} +`; diff --git a/src/__tests__/builders.spec.ts b/src/__tests__/builders.spec.ts new file mode 100644 index 0000000..e044d8d --- /dev/null +++ b/src/__tests__/builders.spec.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from '@jest/globals'; + +import { ValidationError, article, textOnly } from '../builders.js'; + +describe(`Given the publication metadata builders`, () => { + describe(`when using the ${article.name} builder`, () => { + it('should return a valid TextOnlyMetadata', () => { + const metadata = article({ content: 'GM!' }); + + expect(metadata).toMatchSnapshot({ + lens: { + id: expect.any(String), + }, + }); + }); + + it(`should throw a "${ValidationError.name}" in case of invalid input`, () => { + expect(() => article({ content: '' })).toThrowError(ValidationError); + }); + }); + + describe(`when using the ${textOnly.name} builder`, () => { + it('should return a valid TextOnlyMetadata', () => { + const metadata = textOnly({ content: 'GM!' }); + + expect(metadata).toMatchSnapshot({ + lens: { + id: expect.any(String), + }, + }); + }); + }); +}); diff --git a/src/builders.ts b/src/builders.ts new file mode 100644 index 0000000..8d499fc --- /dev/null +++ b/src/builders.ts @@ -0,0 +1,555 @@ +import { v4 } from 'uuid'; +import { z } from 'zod'; + +import { formatZodError } from './formatters'; +import { ProfileMetadata, ProfileMetadataSchema, ProfileSchemaId } from './profile'; +import { + ThreeDMetadata, + ThreeDSchema, + ArticleMetadata, + ArticleSchema, + AudioMetadata, + AudioSchema, + CheckingInMetadata, + CheckingInSchema, + EmbedMetadata, + EmbedSchema, + EventMetadata, + EventSchema, + ImageMetadata, + ImageSchema, + LinkMetadata, + LinkSchema, + LivestreamMetadata, + LivestreamSchema, + MintMetadata, + MintSchema, + MirrorMetadata, + MirrorMetadataSchema, + PublicationMainFocus, + PublicationSchemaId, + SpaceMetadata, + SpaceSchema, + StoryMetadata, + StorySchema, + TextOnlyMetadata, + TextOnlySchema, + TransactionMetadata, + TransactionSchema, + VideoMetadata, + VideoSchema, + MarketplaceMetadata, + PublicationMetadata, + MirrorSchemaId, +} from './publication'; +import { Brand, Overwrite, Prettify } from './utils.js'; + +export class ValidationError extends Error { + name = 'ValidationError' as const; +} + +type BrandOf = [A] extends [Brand] ? R : never; + +type UnbrandAll = T extends Brand> + ? R + : { + [K in keyof T]: UnbrandAll; + }; + +type ExtractLensSpec = T['lens']; + +type OmitInferredPublicationFields = Omit; + +type PublicationDefaults
> = Overwrite< + Details, + { + /** + * A unique identifier that in storages like IPFS ensures the uniqueness of the metadata URI. + * + * @defaultValue a UUID + */ + id?: string; + /** + * The language of the publication. + * + * It's a locale string in the format of `-` or just ``, where: + * - `language` is a two-letter ISO 639-1 language code, e.g. `en` or `it` + * - `region` is a two-letter ISO 3166-1 alpha-2 region code, e.g. `US` or `IT` + * + * You can just pass in the language tag if you do not know the region or don't need to be specific. + * + * @defaultValue `en` + */ + locale?: string; + } +>; + +type PublicationMetadataOptions< + Metadata extends PublicationMetadata, + Details extends ExtractLensSpec = ExtractLensSpec, +> = Prettify< + UnbrandAll>> & { + marketplace?: MarketplaceMetadata; + } +>; + +function process(result: z.SafeParseReturnType): Output { + if (result.success) { + return result.data; + } + throw new ValidationError(formatZodError(result.error)); +} + +export type ArticleOptions = PublicationMetadataOptions; +/** + * Creates a valid ArticleMetadata. + * + * @param args - {@link ArticleOptions} + */ +export function article({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: ArticleOptions): PublicationMetadata { + return process( + ArticleSchema.safeParse({ + $schema: PublicationSchemaId.ARTICLE_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.ARTICLE, + ...others, + }, + }), + ); +} + +export type AudioOptions = PublicationMetadataOptions; +/** + * Creates a valid AudioMetadata. + * + * @param args - {@link AudioOptions} + */ +export function audio({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: AudioOptions): AudioMetadata { + return process( + AudioSchema.safeParse({ + $schema: PublicationSchemaId.AUDIO_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.AUDIO, + ...others, + }, + }), + ); +} + +export type CheckingInOptions = PublicationMetadataOptions; +/** + * Creates a valid CheckingInMetadata. + * + * @param args - {@link CheckingInOptions} + */ +export function checkingIn({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: CheckingInOptions): CheckingInMetadata { + return process( + CheckingInSchema.safeParse({ + $schema: PublicationSchemaId.CHECKING_IN_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.CHECKING_IN, + ...others, + }, + }), + ); +} + +export type EmbedOptions = PublicationMetadataOptions; +/** + * Creates a valid EmbedMetadata. + * + * @param args - {@link EmbedOptions} + */ +export function embed({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: EmbedOptions): EmbedMetadata { + return process( + EmbedSchema.safeParse({ + $schema: PublicationSchemaId.EMBED_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.EMBED, + ...others, + }, + }), + ); +} + +export type EventOptions = PublicationMetadataOptions; +/** + * Creates a valid EventMetadata. + * + * @param args - {@link EventOptions} + */ +export function event({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: EventOptions): EventMetadata { + return process( + EventSchema.safeParse({ + $schema: PublicationSchemaId.EVENT_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.EVENT, + ...others, + }, + }), + ); +} + +export type ImageOptions = PublicationMetadataOptions; +/** + * Creates a valid ImageMetadata. + * + * @param args - {@link ImageOptions} + */ +export function image({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: ImageOptions): ImageMetadata { + return process( + ImageSchema.safeParse({ + $schema: PublicationSchemaId.IMAGE_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.IMAGE, + ...others, + }, + }), + ); +} + +export type LinkOptions = PublicationMetadataOptions; +/** + * Creates a valid LinkMetadata. + * + * @param args - {@link LinkOptions} + */ +export function link({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: LinkOptions): LinkMetadata { + return process( + LinkSchema.safeParse({ + $schema: PublicationSchemaId.LINK_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.LINK, + ...others, + }, + }), + ); +} + +export type LivestreamOptions = PublicationMetadataOptions; +/** + * Creates a valid LivestreamMetadata. + * + * @param args - {@link LivestreamOptions} + */ +export function livestream({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: LivestreamOptions): LivestreamMetadata { + return process( + LivestreamSchema.safeParse({ + $schema: PublicationSchemaId.LIVESTREAM_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.LIVESTREAM, + ...others, + }, + }), + ); +} + +export type MintOptions = PublicationMetadataOptions; +/** + * Creates a valid MintMetadata. + * + * @param args - {@link MintOptions} + */ +export function mint({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: MintOptions): MintMetadata { + return process( + MintSchema.safeParse({ + $schema: PublicationSchemaId.MINT_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.MINT, + ...others, + }, + }), + ); +} + +export type SpaceOptions = PublicationMetadataOptions; +/** + * Creates a valid SpaceMetadata. + * + * @param args - {@link SpaceOptions} + */ +export function space({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: SpaceOptions): SpaceMetadata { + return process( + SpaceSchema.safeParse({ + $schema: PublicationSchemaId.SPACE_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.SPACE, + ...others, + }, + }), + ); +} + +export type StoryOptions = PublicationMetadataOptions; +/** + * Creates a valid StoryMetadata. + * + * @param args - {@link StoryOptions} + */ +export function story({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: StoryOptions): StoryMetadata { + return process( + StorySchema.safeParse({ + $schema: PublicationSchemaId.STORY_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.STORY, + ...others, + }, + }), + ); +} + +export type TextOnlyOptions = PublicationMetadataOptions; +/** + * Creates a valid TextOnlyMetadata. + * + * @param args - {@link TextOnlyOptions} + */ +export function textOnly({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: TextOnlyOptions): TextOnlyMetadata { + return process( + TextOnlySchema.safeParse({ + $schema: PublicationSchemaId.TEXT_ONLY_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.TEXT_ONLY, + ...others, + }, + }), + ); +} + +export type ThreeDOptions = PublicationMetadataOptions; +/** + * Creates a valid ThreeDMetadata. + * + * @param args - {@link ThreeDOptions} + */ +export function threeD({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: ThreeDOptions): ThreeDMetadata { + return process( + ThreeDSchema.safeParse({ + $schema: PublicationSchemaId.THREE_D_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.THREE_D, + ...others, + }, + }), + ); +} + +export type TransactionOptions = PublicationMetadataOptions; +/** + * Creates a valid TransactionMetadata. + * + * @param args - {@link TransactionOptions} + */ +export function transaction({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: TransactionOptions): TransactionMetadata { + return process( + TransactionSchema.safeParse({ + $schema: PublicationSchemaId.TRANSACTION_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.TRANSACTION, + ...others, + }, + }), + ); +} + +export type VideoOptions = PublicationMetadataOptions; +/** + * Creates a valid VideoMetadata. + * + * @param args - {@link VideoOptions} + */ +export function video({ + marketplace, + locale = 'en', + id = v4(), + ...others +}: VideoOptions): VideoMetadata { + return process( + VideoSchema.safeParse({ + $schema: PublicationSchemaId.VIDEO_LATEST, + ...marketplace, + lens: { + id, + locale, + mainContentFocus: PublicationMainFocus.VIDEO, + ...others, + }, + }), + ); +} + +export type MirrorOptions = Prettify< + UnbrandAll< + Overwrite< + ExtractLensSpec, + { + /** + * A unique identifier that in storages like IPFS ensures the uniqueness of the metadata URI. + * + * @defaultValue a UUID + */ + id?: string; + } + > + > +>; +/** + * Creates a valid MirrorMetadata. + * + * @param args - {@link MirrorOptions} + */ +export function mirror({ id = v4(), ...others }: MirrorOptions): MirrorMetadata { + return process( + MirrorMetadataSchema.safeParse({ + $schema: MirrorSchemaId.LATEST, + lens: { + id, + ...others, + }, + }), + ); +} + +export type ProfileOptions = Prettify< + UnbrandAll< + Overwrite< + ExtractLensSpec, + { + /** + * A unique identifier that in storages like IPFS ensures the uniqueness of the metadata URI. + * + * @defaultValue a UUID + */ + id?: string; + } + > + > +>; +/** + * Creates a valid ProfileMetadata. + * + * @param args - {@link ProfileOptions} + */ +export function profile({ id = v4(), ...others }: ProfileOptions): ProfileMetadata { + return process( + ProfileMetadataSchema.safeParse({ + $schema: ProfileSchemaId.LATEST, + lens: { + id, + ...others, + }, + }), + ); +} diff --git a/src/index.ts b/src/index.ts index 4c4f53a..e81b606 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './constants.js'; +export * from './builders.js'; export * from './formatters.js'; export * from './MetadataAttribute.js'; export * from './primitives.js'; diff --git a/src/profile/ProfileMetadataSchema.ts b/src/profile/ProfileMetadataSchema.ts index 6d130db..45e2724 100644 --- a/src/profile/ProfileMetadataSchema.ts +++ b/src/profile/ProfileMetadataSchema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; +import { ProfileSchemaId } from './ProfileSchemaId.js'; import { MetadataAttributeSchema } from '../MetadataAttribute.js'; -import { SchemasRoot } from '../constants.js'; import { AppIdSchema, SignatureSchema, @@ -15,6 +15,10 @@ import { */ export const ProfileMetadataDetailsSchema = z.object( { + id: nonEmptyStringSchema( + 'A unique identifier that in storages like IPFS ensures the uniqueness of the metadata URI. Use a UUID if unsure.', + ), + name: nonEmptyStringSchema('The profile display name.').optional(), bio: markdownSchema('The profile bio as markdown.').optional(), @@ -62,7 +66,7 @@ export type ProfileMetadataDetails = z.infer { }), ).toMatchInlineSnapshot(` "fix the following issues + · "lens.id": Required · "lens.name": Expected string, received number · "lens.bio": Expected string, received boolean · "lens.picture": Should be a valid URI diff --git a/src/profile/index.ts b/src/profile/index.ts index d156435..17209b1 100644 --- a/src/profile/index.ts +++ b/src/profile/index.ts @@ -1 +1,2 @@ export * from './ProfileMetadataSchema.js'; +export * from './ProfileSchemaId.js'; diff --git a/src/publication/3D.ts b/src/publication/3D.ts index 8088866..6f2ff08 100644 --- a/src/publication/3D.ts +++ b/src/publication/3D.ts @@ -32,7 +32,7 @@ export const ThreeDAssetSchema = z.object({ }); export type ThreeDAsset = z.infer; -export const ThreeDMetadataSchema = publicationWith({ +export const ThreeDSchema = publicationWith({ $schema: z.literal(PublicationSchemaId.THREE_D_LATEST), lens: metadataDetailsWith({ @@ -46,4 +46,4 @@ export const ThreeDMetadataSchema = publicationWith({ .describe('The other attachments you want to include with it.'), }), }); -export type ThreeDMetadata = z.infer; +export type ThreeDMetadata = z.infer; diff --git a/src/publication/MirrorSchema.ts b/src/publication/MirrorSchema.ts index 2a4b516..57572fa 100644 --- a/src/publication/MirrorSchema.ts +++ b/src/publication/MirrorSchema.ts @@ -1,9 +1,9 @@ import { z } from 'zod'; +import { MirrorSchemaId } from './MirrorSchemaId.js'; import { MetadataCoreSchema } from './common/index.js'; -import { SchemasRoot } from '../constants.js'; -export /** +/** * Mirror metadata schema. * * @example @@ -24,9 +24,8 @@ export /** * // => { success: false, error: ZodError } * ``` */ - -const MirrorMetadataSchema = z.object({ - $schema: z.literal(`${SchemasRoot}/publications/mirror/1.0.0.json`), +export const MirrorMetadataSchema = z.object({ + $schema: z.literal(MirrorSchemaId.LATEST), lens: MetadataCoreSchema, }); diff --git a/src/publication/MirrorSchemaId.ts b/src/publication/MirrorSchemaId.ts new file mode 100644 index 0000000..b729597 --- /dev/null +++ b/src/publication/MirrorSchemaId.ts @@ -0,0 +1,5 @@ +import { SchemasRoot } from '../constants.js'; + +export enum MirrorSchemaId { + LATEST = `${SchemasRoot}/publications/mirror/1.0.0.json`, +} diff --git a/src/publication/index.ts b/src/publication/index.ts index 89f6498..1f1e219 100644 --- a/src/publication/index.ts +++ b/src/publication/index.ts @@ -11,6 +11,7 @@ export * from './ImageSchema.js'; export * from './LinkSchema.js'; export * from './LivestreamSchema.js'; export * from './MintSchema.js'; +export * from './MirrorSchemaId.js'; export * from './MirrorSchema.js'; export * from './PublicationMainFocus.js'; export * from './PublicationSchemaId.js'; @@ -20,7 +21,7 @@ export * from './TextOnlySchema.js'; export * from './TransactionSchema.js'; export * from './VideoSchema.js'; -import { ThreeDMetadataSchema } from './3D.js'; +import { ThreeDSchema } from './3D.js'; import { ArticleSchema } from './ArticleSchema.js'; import { AudioSchema } from './AudioSchema.js'; import { CheckingInSchema } from './CheckingInSchema.js'; @@ -71,7 +72,7 @@ export const PublicationMetadataSchema = z.discriminatedUnion('$schema', [ TextOnlySchema, StorySchema, TransactionSchema, - ThreeDMetadataSchema, + ThreeDSchema, VideoSchema, ]); diff --git a/src/utils.ts b/src/utils.ts index 3ab43ef..9c8e77a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,6 +10,23 @@ export type Brand = T & { [K in ReservedName]: TBrand; }; +/** + * Omits properties from an union type, preserving the union. + * @internal + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type DistributiveOmit = T extends any ? Omit : never; + +/** + * Overwrites properties from T1 with one from T2 + * @internal + * @example + * ```ts + * Overwrite<{ foo: boolean, bar: string }, { foo: number }> // { foo: number, bar: string } + * ``` + */ +export type Overwrite = DistributiveOmit & T2; + /** * An error that occurs when a task violates a logical condition that is assumed to be true at all times. */ @@ -98,3 +115,15 @@ export function extractVersion(metadata: ProfileMetadata | PublicationMetadata): return result[0]; } + +/** + * Beautify the readout of all of the members of that intersection. + * + * As seen on tv: https://twitter.com/mattpocockuk/status/1622730173446557697 + * + * @internal + */ +export type Prettify = { + [K in keyof T]: T[K]; + // eslint-disable-next-line @typescript-eslint/ban-types +} & {};