Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
592 changes: 592 additions & 0 deletions jsonschemas/posts/custom/3.0.0.json

Large diffs are not rendered by default.

137 changes: 136 additions & 1 deletion jsonschemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
{
"$ref": "#/$defs/MintMetadata"
},
{
"$ref": "#/$defs/CustomMetadata"
},
{
"$ref": "#/$defs/SpaceMetadata"
},
Expand Down Expand Up @@ -78,7 +81,8 @@
"SHORT_VIDEO",
"3D",
"STORY",
"SPACE"
"SPACE",
"CUSTOM"
]
},
"AnyMedia": {
Expand Down Expand Up @@ -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": {
Expand Down
3 changes: 3 additions & 0 deletions scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
EmbedSchema,
EventSchema,
EvmAddressSchema,
ExperimentalPostSchema,
FeedMetadataSchema,
FeedRuleMetadataSchema,
GeoURISchema,
Expand Down Expand Up @@ -77,6 +78,7 @@ const schemas = new Map<string, z.ZodSchema<unknown>>([
['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],
Expand Down Expand Up @@ -216,6 +218,7 @@ async function generateUmbrellaSchema() {
LinkMetadata: LinkSchema,
LiveStreamMetadata: LiveStreamSchema,
MintMetadata: MintSchema,
ExperimentalPostMetadata: ExperimentalPostSchema,
SpaceMetadata: SpaceSchema,
TextOnlyMetadata: TextOnlySchema,
StoryMetadata: StorySchema,
Expand Down
59 changes: 59 additions & 0 deletions src/builders/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {
type EventMetadata,
type EventMetadataDetails,
EventSchema,
type ExperimentalPostMetadata,
type ExperimentalPostMetadataDetails,
ExperimentalPostSchema,
type ImageMetadata,
type ImageMetadataDetails,
ImageSchema,
Expand Down Expand Up @@ -652,6 +655,62 @@ export function mint({
);
}

/**
* @private
* @privateRemarks MUST stay very @private to produce usable docs
*/
type CustomDetails = InputForPostMetadataDetails<ExperimentalPostMetadataDetails>;
/**
* 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
Expand Down
51 changes: 51 additions & 0 deletions src/post/ExperimentalPostSchema.ts
Original file line number Diff line number Diff line change
@@ -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,
});
1 change: 1 addition & 0 deletions src/post/PostMainFocus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum PostMainFocus {
THREE_D = '3D',
STORY = 'STORY',
SPACE = 'SPACE',
OTHER = 'OTHER',
}

export const PostMainFocusSchema = z.nativeEnum(PostMainFocus);
1 change: 1 addition & 0 deletions src/post/PostMetadataSchemaId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
}
20 changes: 19 additions & 1 deletion src/post/__tests__/PostMetadataSchema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'"
`);
});
});
Expand Down Expand Up @@ -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"
`);
});
});
});
3 changes: 2 additions & 1 deletion src/post/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export function metadataDetailsWith<
Augmentation extends {
mainContentFocus:
| z.ZodLiteral<PostMainFocus>
| z.ZodUnion<[z.ZodLiteral<PostMainFocus>, ...z.ZodLiteral<PostMainFocus>[]]>;
| z.ZodUnion<[z.ZodLiteral<PostMainFocus>, ...z.ZodLiteral<PostMainFocus>[]]>
| z.ZodNativeEnum<typeof PostMainFocus>;
},
>(augmentation: Augmentation) {
return z
Expand Down
4 changes: 4 additions & 0 deletions src/post/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -82,6 +84,7 @@ export type PostMetadata = ShapeCheck<
| LinkMetadata
| LiveStreamMetadata
| MintMetadata
| ExperimentalPostMetadata
| SpaceMetadata
| TextOnlyMetadata
| StoryMetadata
Expand Down Expand Up @@ -124,6 +127,7 @@ export const PostMetadataSchema: z.ZodType<PostMetadata, z.ZodTypeDef, object> =
LinkSchema,
LiveStreamSchema,
MintSchema,
ExperimentalPostSchema,
SpaceSchema,
TextOnlySchema,
StorySchema,
Expand Down