diff --git a/jsonschemas/posts/custom/3.0.0.json b/jsonschemas/posts/custom/3.0.0.json new file mode 100644 index 0000000..973e10c --- /dev/null +++ b/jsonschemas/posts/custom/3.0.0.json @@ -0,0 +1,592 @@ +{ + "type": "object", + "properties": { + "description": { + "anyOf": [ + { + "$ref": "#/$defs/NonEmptyString", + "description": "A human-readable description of the item. It could be plain text or markdown." + }, + { + "type": "null" + } + ], + "description": "A human-readable description of the item. It could be plain text or markdown." + }, + "external_url": { + "anyOf": [ + { + "$ref": "#/$defs/URI", + "description": "This is the URL that will appear below the asset's image on OpenSea and others etc. and will allow users to leave OpenSea and view the item on the site." + }, + { + "type": "null" + } + ], + "description": "This is the URL that will appear below the asset's image on OpenSea and others etc. and will allow users to leave OpenSea and view the item on the site." + }, + "name": { + "type": "string", + "description": "Name of the NFT item." + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/$defs/MarketplaceMetadataAttribute" + }, + "description": "These are the attributes for the item, which will show up on the OpenSea and others NFT trading websites on the item." + }, + "image": { + "anyOf": [ + { + "$ref": "#/$defs/URI", + "description": "NFT will store any image here." + }, + { + "type": "null" + } + ], + "description": "NFT will store any image here." + }, + "animation_url": { + "anyOf": [ + { + "$ref": "#/$defs/URI", + "description": "A URL to a multi-media attachment for the item. The file extensions GLTF, GLB, WEBM, MP4, M4V, OGV, and OGG are supported, along with the audio-only extensions MP3, WAV, and OGA. Animation_url also supports HTML pages, allowing you to build rich experiences and interactive NFTs using JavaScript canvas, WebGL, and more. Scripts and relative paths within the HTML page are now supported. However, access to browser extensions is not supported." + }, + { + "type": "null" + } + ], + "description": "A URL to a multi-media attachment for the item. The file extensions GLTF, GLB, WEBM, MP4, M4V, OGV, and OGG are supported, along with the audio-only extensions MP3, WAV, and OGA. Animation_url also supports HTML pages, allowing you to build rich experiences and interactive NFTs using JavaScript canvas, WebGL, and more. Scripts and relative paths within the HTML page are now supported. However, access to browser extensions is not supported." + }, + "signature": { + "type": "string", + "minLength": 1, + "description": "A cryptographic signature of the Lens metadata." + }, + "$schema": { + "type": "string", + "const": "https://json-schemas.lens.dev/posts/custom/3.0.0.json" + }, + "lens": { + "type": "object", + "properties": { + "id": { + "$ref": "#/$defs/NonEmptyString", + "description": "A unique identifier that in storages like IPFS ensures the uniqueness of the metadata URI. Use a UUID if unsure." + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/$defs/MetadataAttribute" + }, + "minItems": 1, + "maxItems": 20, + "description": "A bag of attributes that can be used to store any kind of metadata that is not currently supported by the standard. Over time, common attributes will be added to the standard and their usage as arbitrary attributes will be discouraged." + }, + "locale": { + "type": "string", + "pattern": "^[a-z]{2}(?:-[a-zA-Z]{2})?$", + "description": "A Locale Identifier in the `[language]` OR `[language]-[region]` format (e.g. `en`, `en-GB`, `it`). [language] MUST be in the ISO 639-1 format. [region], if provided, MUST be in the ISO 3166-1 alpha-2 format." + }, + "tags": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/Tag" + }, + "maxItems": 20, + "description": "An arbitrary list of tags." + }, + "contentWarning": { + "type": "string", + "enum": [ + "NSFW", + "SENSITIVE", + "SPOILER" + ], + "description": "Specify a content warning." + }, + "mainContentFocus": { + "type": "string", + "const": "CUSTOM", + "description": "The main focus of the post." + }, + "name": { + "$ref": "#/$defs/NonEmptyString", + "description": "A human-readable name for the custom post type." + }, + "value": { + "$ref": "#/$defs/NonEmptyString", + "description": "A JSON string containing any custom data." + } + }, + "required": [ + "id", + "locale", + "mainContentFocus", + "name", + "value" + ], + "additionalProperties": false + } + }, + "required": [ + "$schema", + "lens" + ], + "additionalProperties": true, + "$defs": { + "NonEmptyString": { + "type": "string", + "minLength": 1 + }, + "Markdown": { + "$ref": "#/$defs/NonEmptyString" + }, + "URI": { + "type": "string", + "minLength": 6, + "format": "uri", + "description": "A Uniform Resource Identifier." + }, + "AnyMedia": { + "anyOf": [ + { + "$ref": "#/$defs/MediaAudio" + }, + { + "$ref": "#/$defs/MediaImage" + }, + { + "$ref": "#/$defs/MediaVideo" + } + ] + }, + "ChainId": { + "type": "number", + "exclusiveMinimum": 0 + }, + "EvmAddress": { + "type": "string", + "minLength": 42, + "maxLength": 42, + "description": "An EVM compatible address." + }, + "GeoURI": { + "type": "string", + "pattern": "^geo:(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*)$", + "description": "A Geographic coordinate as subset of Geo URI (RFC 5870). Currently only supports the `geo:lat,lng` format." + }, + "MarketplaceMetadataAttribute": { + "type": "object", + "properties": { + "display_type": { + "type": "string", + "enum": [ + "number", + "string", + "date" + ] + }, + "trait_type": { + "$ref": "#/$defs/NonEmptyString", + "description": "The name of the trait." + }, + "value": { + "type": [ + "string", + "number" + ] + } + }, + "additionalProperties": true + }, + "MediaAudio": { + "type": "object", + "properties": { + "item": { + "$ref": "#/$defs/URI" + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/$defs/MetadataAttribute" + }, + "minItems": 1, + "description": "A bag of attributes that can be used to store any kind of metadata that is not currently supported by the standard." + }, + "type": { + "type": "string", + "enum": [ + "audio/wav", + "audio/vnd.wave", + "audio/mpeg", + "audio/ogg", + "audio/mp4", + "audio/aac", + "audio/webm", + "audio/flac" + ], + "description": "The mime type of the audio file." + }, + "cover": { + "$ref": "#/$defs/URI", + "description": "A Uniform Resource Identifier." + }, + "duration": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "How long the the audio is in seconds." + }, + "license": { + "$ref": "#/$defs/MetadataLicenseType", + "description": "The license for the audio." + }, + "credits": { + "$ref": "#/$defs/NonEmptyString", + "description": "The credits for the audio." + }, + "artist": { + "$ref": "#/$defs/NonEmptyString", + "description": "The name of the artist." + }, + "genre": { + "$ref": "#/$defs/NonEmptyString", + "description": "The genre of the audio" + }, + "recordLabel": { + "$ref": "#/$defs/NonEmptyString", + "description": "The record label for the audio." + }, + "kind": { + "type": "string", + "enum": [ + "MUSIC", + "PODCAST", + "AUDIOBOOK", + "VOICE_NOTE", + "SOUND", + "OTHER" + ], + "description": "The type of audio." + }, + "lyrics": { + "$ref": "#/$defs/URI", + "description": "A Uniform Resource Identifier." + } + }, + "required": [ + "item", + "type" + ], + "additionalProperties": false + }, + "MediaImage": { + "type": "object", + "properties": { + "item": { + "$ref": "#/$defs/URI" + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/$defs/MetadataAttribute" + }, + "minItems": 1, + "description": "A bag of attributes that can be used to store any kind of metadata that is not currently supported by the standard." + }, + "type": { + "type": "string", + "enum": [ + "image/avif", + "image/bmp", + "image/gif", + "image/heic", + "image/jpeg", + "image/png", + "image/svg+xml", + "image/tiff", + "image/webp", + "image/x-ms-bmp" + ], + "description": "The mime type of the image" + }, + "altTag": { + "$ref": "#/$defs/NonEmptyString", + "description": "The alt tag for accessibility" + }, + "license": { + "$ref": "#/$defs/MetadataLicenseType", + "description": "The license for the image" + } + }, + "required": [ + "item", + "type" + ], + "additionalProperties": false + }, + "MediaVideo": { + "type": "object", + "properties": { + "item": { + "$ref": "#/$defs/URI" + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/$defs/MetadataAttribute" + }, + "minItems": 1, + "description": "A bag of attributes that can be used to store any kind of metadata that is not currently supported by the standard." + }, + "type": { + "type": "string", + "enum": [ + "model/gltf+json", + "model/gltf-binary", + "video/x-m4v", + "video/mov", + "video/mp4", + "video/mpeg", + "video/ogg", + "video/ogv", + "video/quicktime", + "video/webm" + ], + "description": "The mime type of the video" + }, + "altTag": { + "$ref": "#/$defs/NonEmptyString", + "description": "The alt tag for accessibility" + }, + "cover": { + "$ref": "#/$defs/URI", + "description": "A Uniform Resource Identifier." + }, + "duration": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "How long the the video is in seconds" + }, + "license": { + "$ref": "#/$defs/MetadataLicenseType", + "description": "The license for the video" + } + }, + "required": [ + "item", + "type" + ], + "additionalProperties": false + }, + "MetadataAttribute": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "Boolean" + }, + "key": { + "$ref": "#/$defs/NonEmptyString", + "description": "The attribute's unique identifier." + }, + "value": { + "type": "string", + "enum": [ + "true", + "false" + ], + "description": "A JS boolean value serialized as string. It's consumer responsibility to parse it." + } + }, + "required": [ + "type", + "key", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "Date" + }, + "key": { + "$ref": "#/$defs/NonEmptyString", + "description": "The attribute's unique identifier." + }, + "value": { + "type": "string", + "format": "date-time", + "description": "A valid ISO 8601 date string. It's consumer responsibility to parse it." + } + }, + "required": [ + "type", + "key", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "Number" + }, + "key": { + "$ref": "#/$defs/NonEmptyString", + "description": "The attribute's unique identifier." + }, + "value": { + "$ref": "#/$defs/NonEmptyString", + "description": "A valid JS number serialized as string. It's consumer responsibility to parse it." + } + }, + "required": [ + "type", + "key", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "String" + }, + "key": { + "$ref": "#/$defs/NonEmptyString", + "description": "The attribute's unique identifier." + }, + "value": { + "$ref": "#/$defs/NonEmptyString", + "description": "Any string value." + } + }, + "required": [ + "type", + "key", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "JSON" + }, + "key": { + "$ref": "#/$defs/NonEmptyString", + "description": "The attribute's unique identifier." + }, + "value": { + "$ref": "#/$defs/NonEmptyString", + "description": "A JSON string. It's consumer responsibility to validate and parse it." + } + }, + "required": [ + "type", + "key", + "value" + ], + "additionalProperties": false + } + ] + }, + "MetadataLicenseType": { + "type": "string", + "enum": [ + "CCO", + "CC BY", + "CC BY-ND", + "CC BY-NC", + "TBNL-C-D-PL-Legal", + "TBNL-C-DT-PL-Legal", + "TBNL-C-ND-PL-Legal", + "TBNL-C-D-NPL-Legal", + "TBNL-C-DT-NPL-Legal", + "TBNL-C-DTSA-PL-Legal", + "TBNL-C-DTSA-NPL-Legal", + "TBNL-C-ND-NPL-Legal", + "TBNL-C-D-PL-Ledger", + "TBNL-C-DT-PL-Ledger", + "TBNL-C-ND-PL-Ledger", + "TBNL-C-D-NPL-Ledger", + "TBNL-C-DT-NPL-Ledger", + "TBNL-C-DTSA-PL-Ledger", + "TBNL-C-DTSA-NPL-Ledger", + "TBNL-C-ND-NPL-Ledger", + "TBNL-NC-D-PL-Legal", + "TBNL-NC-DT-PL-Legal", + "TBNL-NC-ND-PL-Legal", + "TBNL-NC-D-NPL-Legal", + "TBNL-NC-DT-NPL-Legal", + "TBNL-NC-DTSA-PL-Legal", + "TBNL-NC-DTSA-NPL-Legal", + "TBNL-NC-ND-NPL-Legal", + "TBNL-NC-D-PL-Ledger", + "TBNL-NC-DT-PL-Ledger", + "TBNL-NC-ND-PL-Ledger", + "TBNL-NC-D-NPL-Ledger", + "TBNL-NC-DT-NPL-Ledger", + "TBNL-NC-DTSA-PL-Ledger", + "TBNL-NC-DTSA-NPL-Ledger", + "TBNL-NC-ND-NPL-Ledger" + ] + }, + "PhysicalAddress": { + "type": "object", + "properties": { + "formatted": { + "$ref": "#/$defs/NonEmptyString", + "description": "The full mailing address formatted for display." + }, + "streetAddress": { + "$ref": "#/$defs/NonEmptyString", + "description": "The street address including house number, street name, P.O. Box, apartment or unit number and extended multi-line address information." + }, + "locality": { + "$ref": "#/$defs/NonEmptyString", + "description": "The city or locality." + }, + "region": { + "$ref": "#/$defs/NonEmptyString", + "description": "The state or region." + }, + "postalCode": { + "$ref": "#/$defs/NonEmptyString", + "description": "The zip or postal code." + }, + "country": { + "$ref": "#/$defs/NonEmptyString", + "description": "The country name component." + } + }, + "required": [ + "locality", + "country" + ], + "additionalProperties": false + }, + "Tag": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "description": "An arbitrary tag." + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} diff --git a/jsonschemas/schema.json b/jsonschemas/schema.json index 915093e..0cf6af6 100644 --- a/jsonschemas/schema.json +++ b/jsonschemas/schema.json @@ -27,6 +27,9 @@ { "$ref": "#/$defs/MintMetadata" }, + { + "$ref": "#/$defs/CustomMetadata" + }, { "$ref": "#/$defs/SpaceMetadata" }, @@ -78,7 +81,8 @@ "SHORT_VIDEO", "3D", "STORY", - "SPACE" + "SPACE", + "CUSTOM" ] }, "AnyMedia": { @@ -2306,6 +2310,137 @@ ], "additionalProperties": true }, + "CustomMetadata": { + "type": "object", + "properties": { + "description": { + "anyOf": [ + { + "$ref": "#/$defs/NonEmptyString", + "description": "A human-readable description of the item. It could be plain text or markdown." + }, + { + "type": "null" + } + ], + "description": "A human-readable description of the item. It could be plain text or markdown." + }, + "external_url": { + "anyOf": [ + { + "$ref": "#/$defs/URI", + "description": "This is the URL that will appear below the asset's image on OpenSea and others etc. and will allow users to leave OpenSea and view the item on the site." + }, + { + "type": "null" + } + ], + "description": "This is the URL that will appear below the asset's image on OpenSea and others etc. and will allow users to leave OpenSea and view the item on the site." + }, + "name": { + "type": "string", + "description": "Name of the NFT item." + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/$defs/MarketplaceMetadataAttribute" + }, + "description": "These are the attributes for the item, which will show up on the OpenSea and others NFT trading websites on the item." + }, + "image": { + "anyOf": [ + { + "$ref": "#/$defs/URI", + "description": "NFT will store any image here." + }, + { + "type": "null" + } + ], + "description": "NFT will store any image here." + }, + "animation_url": { + "anyOf": [ + { + "$ref": "#/$defs/URI", + "description": "A URL to a multi-media attachment for the item. The file extensions GLTF, GLB, WEBM, MP4, M4V, OGV, and OGG are supported, along with the audio-only extensions MP3, WAV, and OGA. Animation_url also supports HTML pages, allowing you to build rich experiences and interactive NFTs using JavaScript canvas, WebGL, and more. Scripts and relative paths within the HTML page are now supported. However, access to browser extensions is not supported." + }, + { + "type": "null" + } + ], + "description": "A URL to a multi-media attachment for the item. The file extensions GLTF, GLB, WEBM, MP4, M4V, OGV, and OGG are supported, along with the audio-only extensions MP3, WAV, and OGA. Animation_url also supports HTML pages, allowing you to build rich experiences and interactive NFTs using JavaScript canvas, WebGL, and more. Scripts and relative paths within the HTML page are now supported. However, access to browser extensions is not supported." + }, + "signature": { + "$ref": "#/$defs/Signature", + "description": "A cryptographic signature of the Lens metadata." + }, + "$schema": { + "type": "string", + "const": "https://json-schemas.lens.dev/posts/custom/3.0.0.json" + }, + "lens": { + "type": "object", + "properties": { + "id": { + "$ref": "#/$defs/MetadataId" + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/$defs/MetadataAttribute" + }, + "minItems": 1, + "maxItems": 20, + "description": "A bag of attributes that can be used to store any kind of metadata that is not currently supported by the standard. Over time, common attributes will be added to the standard and their usage as arbitrary attributes will be discouraged." + }, + "locale": { + "$ref": "#/$defs/Locale" + }, + "tags": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/Tag" + }, + "maxItems": 20, + "description": "An arbitrary list of tags." + }, + "contentWarning": { + "$ref": "#/$defs/ContentWarning", + "description": "Specify a content warning." + }, + "mainContentFocus": { + "type": "string", + "const": "CUSTOM", + "description": "The main focus of the post." + }, + "name": { + "$ref": "#/$defs/NonEmptyString", + "description": "A human-readable name for the custom post type." + }, + "value": { + "$ref": "#/$defs/NonEmptyString", + "description": "A JSON string containing any custom data." + } + }, + "required": [ + "id", + "locale", + "mainContentFocus", + "name", + "value" + ], + "additionalProperties": false + } + }, + "required": [ + "$schema", + "lens" + ], + "additionalProperties": true + }, "SpaceMetadata": { "type": "object", "properties": { diff --git a/scripts/build.ts b/scripts/build.ts index 99ad0f1..6556f37 100755 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -20,6 +20,7 @@ import { EmbedSchema, EventSchema, EvmAddressSchema, + ExperimentalPostSchema, FeedMetadataSchema, FeedRuleMetadataSchema, GeoURISchema, @@ -77,6 +78,7 @@ const schemas = new Map>([ ['posts/link/3.0.0.json', LinkSchema], ['posts/livestream/3.0.0.json', LiveStreamSchema], ['posts/mint/3.0.0.json', MintSchema], + ['posts/experimental/3.0.0.json', ExperimentalPostSchema], ['posts/space/3.0.0.json', SpaceSchema], ['posts/story/3.0.0.json', StorySchema], ['posts/text-only/3.0.0.json', TextOnlySchema], @@ -216,6 +218,7 @@ async function generateUmbrellaSchema() { LinkMetadata: LinkSchema, LiveStreamMetadata: LiveStreamSchema, MintMetadata: MintSchema, + ExperimentalPostMetadata: ExperimentalPostSchema, SpaceMetadata: SpaceSchema, TextOnlyMetadata: TextOnlySchema, StoryMetadata: StorySchema, diff --git a/src/builders/posts.ts b/src/builders/posts.ts index 580addb..73dc329 100644 --- a/src/builders/posts.ts +++ b/src/builders/posts.ts @@ -16,6 +16,9 @@ import { type EventMetadata, type EventMetadataDetails, EventSchema, + type ExperimentalPostMetadata, + type ExperimentalPostMetadataDetails, + ExperimentalPostSchema, type ImageMetadata, type ImageMetadataDetails, ImageSchema, @@ -652,6 +655,62 @@ export function mint({ ); } +/** + * @private + * @privateRemarks MUST stay very @private to produce usable docs + */ +type CustomDetails = InputForPostMetadataDetails; +/** + * All {@link ExperimentalPostMetadataDetails} fields with: + * - `id` defaults to a UUID + * - `locale` defaults to `en` + * - `mainContentFocus` automatically set to `PostSchemaId.CUSTOM_LATEST` + */ +export type CustomOptions = CustomDetails & { + /** + * All the {@link NftMetadata} fields. + */ + nft?: NftDetails; +}; +/** + * Creates a valid CustomMetadata. + * + * @category Compose + * @param input - Use your IDE suggestions for an enhanced development experience + * + * @example + * ```ts + * const metadata = custom({ + * name: 'My Custom Post Type', + * value: JSON.stringify({ + * customField1: 'value1', + * customField2: 42, + * customField3: ['item1', 'item2'] + * }), + * tags: ['custom', 'special'], + * }); + * ``` + */ +export function custom({ + nft, + locale = DEFAULT_LOCALE, + id = v4(), + ...others +}: CustomOptions): ExperimentalPostMetadata { + return evaluate( + ExperimentalPostSchema.safeParse({ + $schema: PostMetadataSchemaId.CUSTOM_LATEST, + ...nft, + lens: { + id, + locale, + mainContentFocus: PostMainFocus.CUSTOM, + ...others, + }, + }), + ); +} + /** * @private * @privateRemarks MUST stay very @private to produce usable docs diff --git a/src/post/ExperimentalPostSchema.ts b/src/post/ExperimentalPostSchema.ts new file mode 100644 index 0000000..e7eeb9b --- /dev/null +++ b/src/post/ExperimentalPostSchema.ts @@ -0,0 +1,51 @@ +import { z } from 'zod'; + +import { NonEmptyStringSchema, type Signature } from '../primitives.js'; +import type { NftMetadata } from '../tokens/eip721.js'; +import { PostMainFocus } from './PostMainFocus.js'; +import { PostMetadataSchemaId } from './PostMetadataSchemaId.js'; +import { type PostMetadataCommon, metadataDetailsWith, postWith } from './common/index.js'; + +export type ExperimentalPostMetadataDetails = PostMetadataCommon & { + /** + * The main focus of the post. + */ + mainContentFocus: PostMainFocus; + /** + * A sub-identifier for the custom post type. + * This is used to differentiate between different custom post types. + */ + name: string; +}; + +const ExperimentalPostMetadataDetailsSchema: z.ZodType< + ExperimentalPostMetadataDetails, + z.ZodTypeDef, + object +> = metadataDetailsWith({ + mainContentFocus: z.nativeEnum(PostMainFocus), + + name: NonEmptyStringSchema.describe( + 'A sub-identifier for the custom post type. This is used to differentiate between different custom post types.', + ), +}); + +export type ExperimentalPostMetadata = NftMetadata & { + /** + * The schema id. + */ + $schema: PostMetadataSchemaId.EXPERIMENTAL_LATEST; + /** + * The metadata details. + */ + lens: ExperimentalPostMetadataDetails; + /** + * A cryptographic signature of the `lens` data. + */ + signature?: Signature; +}; + +export const ExperimentalPostSchema = postWith({ + $schema: z.literal(PostMetadataSchemaId.EXPERIMENTAL_LATEST), + lens: ExperimentalPostMetadataDetailsSchema, +}); diff --git a/src/post/PostMainFocus.ts b/src/post/PostMainFocus.ts index 3f57407..62ffea0 100644 --- a/src/post/PostMainFocus.ts +++ b/src/post/PostMainFocus.ts @@ -20,6 +20,7 @@ export enum PostMainFocus { THREE_D = '3D', STORY = 'STORY', SPACE = 'SPACE', + OTHER = 'OTHER', } export const PostMainFocusSchema = z.nativeEnum(PostMainFocus); diff --git a/src/post/PostMetadataSchemaId.ts b/src/post/PostMetadataSchemaId.ts index a5ee6a8..fd9bd7c 100644 --- a/src/post/PostMetadataSchemaId.ts +++ b/src/post/PostMetadataSchemaId.ts @@ -21,4 +21,5 @@ export enum PostMetadataSchemaId { TRANSACTION_LATEST = `${location}/transaction/3.0.0.json`, TEXT_ONLY_LATEST = `${location}/text-only/3.0.0.json`, VIDEO_LATEST = `${location}/video/3.0.0.json`, + EXPERIMENTAL_LATEST = `${location}/experimental/3.0.0.json`, } diff --git a/src/post/__tests__/PostMetadataSchema.spec.ts b/src/post/__tests__/PostMetadataSchema.spec.ts index 795d390..8623f68 100644 --- a/src/post/__tests__/PostMetadataSchema.spec.ts +++ b/src/post/__tests__/PostMetadataSchema.spec.ts @@ -8,7 +8,7 @@ describe('Given the PostMetadataSchema', () => { it('then it should complain about the missing $schema', () => { expectResult(() => PostMetadataSchema.safeParse({})).toMatchInlineSnapshot(` "fix the following issues - · "$schema": Invalid discriminator value. Expected 'https://json-schemas.lens.dev/posts/article/3.0.0.json' | 'https://json-schemas.lens.dev/posts/audio/3.0.0.json' | 'https://json-schemas.lens.dev/posts/checking-in/3.0.0.json' | 'https://json-schemas.lens.dev/posts/embed/3.0.0.json' | 'https://json-schemas.lens.dev/posts/event/3.0.0.json' | 'https://json-schemas.lens.dev/posts/image/3.0.0.json' | 'https://json-schemas.lens.dev/posts/link/3.0.0.json' | 'https://json-schemas.lens.dev/posts/livestream/3.0.0.json' | 'https://json-schemas.lens.dev/posts/mint/3.0.0.json' | 'https://json-schemas.lens.dev/posts/space/3.0.0.json' | 'https://json-schemas.lens.dev/posts/text-only/3.0.0.json' | 'https://json-schemas.lens.dev/posts/story/3.0.0.json' | 'https://json-schemas.lens.dev/posts/transaction/3.0.0.json' | 'https://json-schemas.lens.dev/posts/3d/3.0.0.json' | 'https://json-schemas.lens.dev/posts/video/3.0.0.json'" + · "$schema": Invalid discriminator value. Expected 'https://json-schemas.lens.dev/posts/article/3.0.0.json' | 'https://json-schemas.lens.dev/posts/audio/3.0.0.json' | 'https://json-schemas.lens.dev/posts/checking-in/3.0.0.json' | 'https://json-schemas.lens.dev/posts/embed/3.0.0.json' | 'https://json-schemas.lens.dev/posts/event/3.0.0.json' | 'https://json-schemas.lens.dev/posts/image/3.0.0.json' | 'https://json-schemas.lens.dev/posts/link/3.0.0.json' | 'https://json-schemas.lens.dev/posts/livestream/3.0.0.json' | 'https://json-schemas.lens.dev/posts/mint/3.0.0.json' | 'https://json-schemas.lens.dev/posts/custom/3.0.0.json' | 'https://json-schemas.lens.dev/posts/space/3.0.0.json' | 'https://json-schemas.lens.dev/posts/text-only/3.0.0.json' | 'https://json-schemas.lens.dev/posts/story/3.0.0.json' | 'https://json-schemas.lens.dev/posts/transaction/3.0.0.json' | 'https://json-schemas.lens.dev/posts/3d/3.0.0.json' | 'https://json-schemas.lens.dev/posts/video/3.0.0.json'" `); }); }); @@ -262,4 +262,22 @@ describe('Given the PostMetadataSchema', () => { `); }); }); + + describe(`when parsing an invalid ${PostMetadataSchemaId.CUSTOM_LATEST}`, () => { + it('then it should flag the missing fields', () => { + expectResult(() => + PostMetadataSchema.safeParse({ + $schema: PostMetadataSchemaId.CUSTOM_LATEST, + lens: {}, + }), + ).toMatchInlineSnapshot(` + "fix the following issues + · "lens.id": Required + · "lens.locale": Required + · "lens.mainContentFocus": Invalid literal value, expected "CUSTOM" + · "lens.name": Required + · "lens.value": Required" + `); + }); + }); }); diff --git a/src/post/common/index.ts b/src/post/common/index.ts index 7e247d7..3e052a7 100644 --- a/src/post/common/index.ts +++ b/src/post/common/index.ts @@ -69,7 +69,8 @@ export function metadataDetailsWith< Augmentation extends { mainContentFocus: | z.ZodLiteral - | z.ZodUnion<[z.ZodLiteral, ...z.ZodLiteral[]]>; + | z.ZodUnion<[z.ZodLiteral, ...z.ZodLiteral[]]> + | z.ZodNativeEnum; }, >(augmentation: Augmentation) { return z diff --git a/src/post/index.ts b/src/post/index.ts index b2f274e..70e9331 100644 --- a/src/post/index.ts +++ b/src/post/index.ts @@ -11,6 +11,7 @@ export * from './ImageSchema.js'; export * from './LinkSchema.js'; export * from './LiveStreamSchema.js'; export * from './MintSchema.js'; +export * from './ExperimentalPostSchema.js'; export * from './PostMainFocus.js'; export * from './PostMetadataSchemaId.js'; export * from './SpaceSchema.js'; @@ -26,6 +27,7 @@ import { type AudioMetadata, AudioSchema } from './AudioSchema.js'; import { type CheckingInMetadata, CheckingInSchema } from './CheckingInSchema.js'; import { type EmbedMetadata, EmbedSchema } from './EmbedSchema'; import { type EventMetadata, EventSchema } from './EventSchema.js'; +import { type ExperimentalPostMetadata, ExperimentalPostSchema } from './ExperimentalPostSchema.js'; import { type ImageMetadata, ImageSchema } from './ImageSchema.js'; import { type LinkMetadata, LinkSchema } from './LinkSchema.js'; import { type LiveStreamMetadata, LiveStreamSchema } from './LiveStreamSchema.js'; @@ -82,6 +84,7 @@ export type PostMetadata = ShapeCheck< | LinkMetadata | LiveStreamMetadata | MintMetadata + | ExperimentalPostMetadata | SpaceMetadata | TextOnlyMetadata | StoryMetadata @@ -124,6 +127,7 @@ export const PostMetadataSchema: z.ZodType = LinkSchema, LiveStreamSchema, MintSchema, + ExperimentalPostSchema, SpaceSchema, TextOnlySchema, StorySchema,