diff --git a/.changeset/hip-cherries-roll.md b/.changeset/hip-cherries-roll.md new file mode 100644 index 0000000..14a105f --- /dev/null +++ b/.changeset/hip-cherries-roll.md @@ -0,0 +1,5 @@ +--- +'@lens-protocol/metadata': patch +--- + +**Fixed** TS4023 error while using `legacy` namespace by moving content under `@lens-protocol/metadata/legacy` entry point diff --git a/.changeset/large-pots-bow.md b/.changeset/large-pots-bow.md new file mode 100644 index 0000000..7af946d --- /dev/null +++ b/.changeset/large-pots-bow.md @@ -0,0 +1,5 @@ +--- +'@lens-protocol/metadata': patch +--- + +**Fixed** missing `EncryptionParams.encryptedFields` rules and typedef diff --git a/.changeset/pre.json b/.changeset/pre.json index 52e9c1e..62afe69 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -12,10 +12,13 @@ "grumpy-hairs-carry", "grumpy-jokes-drop", "healthy-radios-lick", + "hip-cherries-roll", "hot-jobs-collect", + "large-pots-bow", "light-ligers-exist", "popular-spies-enjoy", "quick-pets-itch", + "shy-cougars-boil", "shy-falcons-sip", "three-lamps-develop", "tiny-glasses-unite", diff --git a/.changeset/shy-cougars-boil.md b/.changeset/shy-cougars-boil.md new file mode 100644 index 0000000..1b4c4fe --- /dev/null +++ b/.changeset/shy-cougars-boil.md @@ -0,0 +1,5 @@ +--- +'@lens-protocol/metadata': patch +--- + +**Fixed** missing properties in `TransactionMetadata` and `SpaceSchema` diff --git a/CHANGELOG.md b/CHANGELOG.md index 1704497..229e6b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # @lens-protocol/metadata +## 0.1.0-alpha.11 + +### Patch Changes + +- eec20a1: **Fixed** TS4023 error while using `legacy` namespace by moving content under `@lens-protocol/metadata/legacy` entry point +- 691847a: **Fixed** missing `EncryptionParams.encryptedFields` rules and typedef +- b72c6e4: **Fixed** missing properties in `TransactionMetadata` and `SpaceSchema` + ## 0.1.0-alpha.10 ### Patch Changes diff --git a/README.md b/README.md index 9c90a7a..bad86e7 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,27 @@ Schema validation and TS types for [LIP-2](https://github.com/lens-protocol/LIPs/pull/5/) Lens Protocol Metadata Standards. +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) + - [Publication metadata](#publication-metadata) + - [Profile metadata](#profile-metadata) + - [Extract version number](#extract-version-number) + - [Format validation error](#format-validation-error) + - [Narrowing types](#narrowing-types) + - [`PublicationMetadata`](#publicationmetadata) + - [`AccessCondition`](#accesscondition) + - [`MetadataAttribute`](#metadataattribute) + - [Useful types](#useful-types) + - [Legacy metadata formats](#legacy-metadata-formats) +- [JSON schemas](#json-schemas) +- [Versioning](#versioning) + - [Adding a new schema](#adding-a-new-schema) +- [Contributing](#contributing) +- [Releasing](#releasing) +- [License](#license) +- [Support](#support) + ## Features - [Zod](https://zod.dev/) schema definitions @@ -53,25 +74,6 @@ PublicationMetadataSchema.safeParse(invalid); // => { success: false, error: ZodError } ``` -You can also parse legacy Publication Metadata v2 and v1 via: - -```typescript -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 the `legacy` namespace make sure to use enums from tha same namespace, e.g. `legacy.PublicationMainFocus` instead of `PublicationMainFocus` to avoid confusion. - ### Profile metadata ```typescript @@ -88,22 +90,6 @@ ProfileMetadataSchema.safeParse(invalid); // => { success: false, error: ZodError } ``` -You can also parse legacy Profile Metadata (aka v1) via: - -```typescript -import { legacy } from '@lens-protocol/metadata'; - -legacy.ProfileMetadataSchema.parse(valid); // => legacy.ProfileMetadata -legacy.ProfileMetadataSchema.parse(invalid); // => throws ZodError - -// OR - -legacy.ProfileMetadataSchema.safeParse(valid); -// => { success: true, data: legacy.ProfileMetadata } -legacy.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`. @@ -142,7 +128,7 @@ if (!result.success) { Every time you have a discriminated union, you can use the discriminant to narrow the type. See few examples below. -**`PublicationMetadata`** +#### `PublicationMetadata` ```typescript import { @@ -171,49 +157,7 @@ switch (publicationMetadata.$schema) { } ``` -**`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.PublicationMetadataV2Article` -- `legacy.PublicationMetadataV2Audio` -- `legacy.PublicationMetadataV2Embed` -- `legacy.PublicationMetadataV2Image` -- `legacy.PublicationMetadataV2Link` -- `legacy.PublicationMetadataV2TextOnly` -- `legacy.PublicationMetadataV2Video` - -where the `mainContentFocus` property is the discriminant. - -```typescript -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; -} -``` - -**`AccessCondition`** +#### `AccessCondition` ```typescript import { AccessCondition, ConditionType, PublicationMetadataSchema } from '@lens-protocol/metadata'; @@ -238,7 +182,7 @@ switch (publicationMetadata.encryptedWith?.accessCondition.type) { } ``` -**`MetadataAttribute`** +#### `MetadataAttribute` ```typescript import { MetadataAttribute, MetadataAttributeType } from '@lens-protocol/metadata'; @@ -328,6 +272,144 @@ import { } from '@lens-protocol/metadata'; ``` +### Legacy metadata formats + +The package also exports parsers for legacy metadata formats via the `@lens-protocol/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 `@lens-protocol/metadata/legacy` and NOT the one from the main `@lens-protocol/metadata` entrypoint. + +You can parse legacy Publication Metadata v1 and v2 via: + +```typescript +import { PublicationMetadataSchema } from '@lens-protocol/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. + +```typescript +import { + PublicationMetadataSchema, + PublicationMetadataVersion, + PublicationMainFocus, +} from '@lens-protocol/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: + +```typescript +import { ProfileMetadataSchema } from '@lens-protocol/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 `@lens-protocol/metadata/legacy` entrypoint also exports all the types and enums that you might need to work with the legacy metadata (some examples below). + +```typescript +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 '@lens-protocol/metadata/legacy'; +``` + +> [!NOTE] +> If you find yourself in a position of importing from both `@lens-protocol/metadata` and `@lens-protocol/metadata/legacy` entrypoints in the same module. You can you can use ESModule aliasing to avoid conflicts: `import * as legacy from '@lens-protocol/metadata/legacy'` and then use the legacy types, enums, and parsers under `legacy.*`. + ## JSON schemas Importing JSON schema in TypeScript is a simple as: diff --git a/package.json b/package.json index dda8a15..24c93bf 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,32 @@ { "name": "@lens-protocol/metadata", - "version": "0.1.0-alpha.10", + "version": "0.1.0-alpha.11", "description": "Lens Protocol Metadata Standards", "type": "module", "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "default": "./dist/index.cjs" + }, + "./legacy": { + "import": "./dist/legacy/index.js", + "default": "./dist/legacy/index.cjs" + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "*": [ + "dist/index.d.ts" + ], + "legacy": [ + "dist/legacy/index.d.ts" + ] + } + }, "sideEffects": false, "scripts": { "build": "pnpm build:jsonschemas && pnpm build:dist", diff --git a/src/formatters.ts b/src/formatters.ts index 383fc2b..7289255 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -1,7 +1,7 @@ // Heavily customized and simplified version of https://www.npmjs.com/package/zod-validation-error import { z } from 'zod'; -import { NonEmptyArray, assertNonEmptyArray, hasAtLeastOne } from './utils'; +import { NonEmptyArray, assertNonEmptyArray, hasAtLeastOne } from './utils.js'; const maxIssuesInMessage = 99; const issueSeparator = '\n'; diff --git a/src/index.ts b/src/index.ts index 4a3d4c6..4c4f53a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,8 @@ -export * as legacy from './legacy'; export * from './constants.js'; export * from './formatters.js'; export * from './MetadataAttribute.js'; export * from './primitives.js'; -export * from './profile'; -export * from './publication'; +export * from './profile/index.js'; +export * from './publication/index.js'; export type { Brand, TwoAtLeastArray } from './utils.js'; export { extractVersion } from './utils.js'; diff --git a/src/__tests__/legacy.ProfileMetadataSchema.spec.ts b/src/legacy/__tests__/ProfileMetadataSchema.spec.ts similarity index 86% rename from src/__tests__/legacy.ProfileMetadataSchema.spec.ts rename to src/legacy/__tests__/ProfileMetadataSchema.spec.ts index ed66043..4c4b3de 100644 --- a/src/__tests__/legacy.ProfileMetadataSchema.spec.ts +++ b/src/legacy/__tests__/ProfileMetadataSchema.spec.ts @@ -1,12 +1,12 @@ import { describe, it } from '@jest/globals'; -import { expectSchema } from '../__helpers__/assertions.js'; -import { legacy } from '../index.js'; +import { expectSchema } from '../../__helpers__/assertions.js'; +import { ProfileMetadataSchema } from '../index.js'; -describe(`Given the legacy.ProfileMetadataSchema`, () => { +describe(`Given the ProfileMetadataSchema`, () => { describe(`when parsing an invalid object`, () => { it(`then it should flag the missing fields`, () => { - expectSchema(() => legacy.ProfileMetadataSchema.safeParse({})).toMatchInlineSnapshot(` + expectSchema(() => ProfileMetadataSchema.safeParse({})).toMatchInlineSnapshot(` "fix the following issues · "version": Invalid literal value, expected "1.0.0" · "metadata_id": Required @@ -19,7 +19,7 @@ describe(`Given the legacy.ProfileMetadataSchema`, () => { it(`then it should handle attributes in a backwards compatible way`, () => { expectSchema(() => - legacy.ProfileMetadataSchema.safeParse({ + ProfileMetadataSchema.safeParse({ version: '1.0.0', metadata_id: 'foo', diff --git a/src/__tests__/legacy.PublicationMetadataSchema.spec.ts b/src/legacy/__tests__/PublicationMetadataSchema.spec.ts similarity index 72% rename from src/__tests__/legacy.PublicationMetadataSchema.spec.ts rename to src/legacy/__tests__/PublicationMetadataSchema.spec.ts index f3877ef..fd9e916 100644 --- a/src/__tests__/legacy.PublicationMetadataSchema.spec.ts +++ b/src/legacy/__tests__/PublicationMetadataSchema.spec.ts @@ -1,19 +1,19 @@ import { describe, it } from '@jest/globals'; -import { expectSchema } from '../__helpers__/assertions.js'; -import { legacy } from '../index.js'; +import { expectSchema } from '../../__helpers__/assertions.js'; +import { PublicationMainFocus, PublicationMetadataSchema } from '../index.js'; -describe(`Given the legacy.PublicationMetadataSchema`, () => { +describe(`Given the PublicationMetadataSchema`, () => { describe(`when parsing an empty object`, () => { it(`then it should complain about the missing version`, () => { - expectSchema(() => legacy.PublicationMetadataSchema.safeParse({})).toMatchInlineSnapshot(` + expectSchema(() => PublicationMetadataSchema.safeParse({})).toMatchInlineSnapshot(` "fix the following issues · "version": Required" `); }); it(`then it should complain about invalid version`, () => { - expectSchema(() => legacy.PublicationMetadataSchema.safeParse({ version: '42' })) + expectSchema(() => PublicationMetadataSchema.safeParse({ version: '42' })) .toMatchInlineSnapshot(` "fix the following issues · "version": Invalid enum value. Expected '1.0.0' | '2.0.0', received '42'" @@ -24,7 +24,7 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { describe(`when parsing an invalid v1 object`, () => { it(`then it should complain about missing basic mandatory fields`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '1.0.0', }), ).toMatchInlineSnapshot(` @@ -37,7 +37,7 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { it(`then it should check at least one between content, image, and media is present`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '1.0.0', metadata_id: '123', name: '123', @@ -53,7 +53,7 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { it('then it should complain about empty content', () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '1.0.0', metadata_id: '123', name: '123', @@ -70,7 +70,7 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { it('then it should complain about too long content', () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '1.0.0', metadata_id: '123', name: '123', @@ -85,7 +85,7 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { it('then it should complain about invalid media items', () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '1.0.0', metadata_id: '123', name: '123', @@ -109,9 +109,9 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { describe(`when parsing an invalid v2 object`, () => { it(`then it should complain about missing basic mandatory fields`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', - mainContentFocus: legacy.PublicationMainFocus.TEXT_ONLY, + mainContentFocus: PublicationMainFocus.TEXT_ONLY, }), ).toMatchInlineSnapshot(` "fix the following issues @@ -125,7 +125,7 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { it('then it should complain about invalid v2 fields', () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', metadata_id: '123', name: '123', @@ -133,7 +133,7 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { content: 'a', locale: '', contentWarning: 'NOVALID', - mainContentFocus: legacy.PublicationMainFocus.TEXT_ONLY, + mainContentFocus: PublicationMainFocus.TEXT_ONLY, tags: ['a'.repeat(51), '', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'], }), ).toMatchInlineSnapshot(` @@ -146,15 +146,15 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { `); }); - it(`then it should complain about invalid ${legacy.PublicationMainFocus.ARTICLE} metadata`, () => { + it(`then it should complain about invalid ${PublicationMainFocus.ARTICLE} metadata`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', metadata_id: '123', name: '123', attributes: [], locale: 'en', - mainContentFocus: legacy.PublicationMainFocus.ARTICLE, + mainContentFocus: PublicationMainFocus.ARTICLE, }), ).toMatchInlineSnapshot(` "fix the following issues @@ -162,15 +162,15 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { `); }); - it(`then it should complain about invalid ${legacy.PublicationMainFocus.AUDIO} metadata`, () => { + it(`then it should complain about invalid ${PublicationMainFocus.AUDIO} metadata`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', metadata_id: '123', name: '123', attributes: [], locale: 'en', - mainContentFocus: legacy.PublicationMainFocus.AUDIO, + mainContentFocus: PublicationMainFocus.AUDIO, media: [ { item: 'https://example.com/image.png', @@ -184,15 +184,15 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { `); }); - it(`then it should complain about invalid ${legacy.PublicationMainFocus.EMBED} metadata`, () => { + it(`then it should complain about invalid ${PublicationMainFocus.EMBED} metadata`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', metadata_id: '123', name: '123', attributes: [], locale: 'en', - mainContentFocus: legacy.PublicationMainFocus.EMBED, + mainContentFocus: PublicationMainFocus.EMBED, }), ).toMatchInlineSnapshot(` "fix the following issues @@ -200,15 +200,15 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { `); }); - it(`then it should complain about invalid ${legacy.PublicationMainFocus.IMAGE} metadata`, () => { + it(`then it should complain about invalid ${PublicationMainFocus.IMAGE} metadata`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', metadata_id: '123', name: '123', attributes: [], locale: 'en', - mainContentFocus: legacy.PublicationMainFocus.IMAGE, + mainContentFocus: PublicationMainFocus.IMAGE, media: [ { item: 'https://example.com/dunno.42', @@ -221,16 +221,16 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { `); }); - it(`then it should complain about invalid ${legacy.PublicationMainFocus.LINK} metadata`, () => { + it(`then it should complain about invalid ${PublicationMainFocus.LINK} metadata`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', metadata_id: '123', name: '123', attributes: [], locale: 'en', content: 'somehting without a URL', - mainContentFocus: legacy.PublicationMainFocus.LINK, + mainContentFocus: PublicationMainFocus.LINK, }), ).toMatchInlineSnapshot(` "fix the following issues @@ -238,15 +238,15 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { `); }); - it(`then it should complain about invalid ${legacy.PublicationMainFocus.TEXT_ONLY} metadata`, () => { + it(`then it should complain about invalid ${PublicationMainFocus.TEXT_ONLY} metadata`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', metadata_id: '123', name: '123', attributes: [], locale: 'en', - mainContentFocus: legacy.PublicationMainFocus.TEXT_ONLY, + mainContentFocus: PublicationMainFocus.TEXT_ONLY, media: [ { item: 'https://example.com/image.png', @@ -260,15 +260,15 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { `); }); - it(`then it should complain about invalid ${legacy.PublicationMainFocus.VIDEO} metadata`, () => { + it(`then it should complain about invalid ${PublicationMainFocus.VIDEO} metadata`, () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', metadata_id: '123', name: '123', attributes: [], locale: 'en', - mainContentFocus: legacy.PublicationMainFocus.VIDEO, + mainContentFocus: PublicationMainFocus.VIDEO, media: [ { item: 'https://example.com/dunno.42', @@ -281,15 +281,35 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { `); }); + it('then it should complain about missing encryptionParams', () => { + expectSchema(() => + PublicationMetadataSchema.safeParse({ + version: '2.0.0', + metadata_id: '123', + name: '123', + attributes: [], + locale: 'en', + mainContentFocus: PublicationMainFocus.TEXT_ONLY, + content: 'a', + encryptionParams: {}, + }), + ).toMatchInlineSnapshot(` + "fix the following issues + · "encryptionParams.accessCondition": Required + · "encryptionParams.encryptionKey": Required + · "encryptionParams.encryptedFields": Required" + `); + }); + it('then it should complain about invalid encryptionParams', () => { expectSchema(() => - legacy.PublicationMetadataSchema.safeParse({ + PublicationMetadataSchema.safeParse({ version: '2.0.0', metadata_id: '123', name: '123', attributes: [], locale: 'en', - mainContentFocus: legacy.PublicationMainFocus.TEXT_ONLY, + mainContentFocus: PublicationMainFocus.TEXT_ONLY, content: 'a', encryptionParams: { accessCondition: { @@ -310,6 +330,9 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { }, }, encryptionKey: '0x...', + encryptedFields: { + content: 42, + }, }, }), ).toMatchInlineSnapshot(` @@ -317,7 +340,8 @@ describe(`Given the legacy.PublicationMetadataSchema`, () => { · "encryptionParams.accessCondition.or.criteria[0]": Unrecognized key(s) in object: 'collect' · "encryptionParams.accessCondition.or.criteria": Invalid OR condition: should have at least 2 conditions · "encryptionParams.accessCondition": Unrecognized key(s) in object: 'and' - · "encryptionParams.encryptionKey": Encryption key should be 368 characters long." + · "encryptionParams.encryptionKey": Encryption key should be 368 characters long. + · "encryptionParams.encryptedFields.content": Expected string, received number" `); }); }); diff --git a/src/legacy/publication.ts b/src/legacy/publication.ts index 844b612..cee8334 100644 --- a/src/legacy/publication.ts +++ b/src/legacy/publication.ts @@ -19,6 +19,12 @@ import { } from '../publication/common'; import { hasTwoOrMore } from '../utils.js'; +// re-export under legacy namespace +export { ConditionComparisonOperator, NftContractType, PublicationContentWarning }; +export { MarketplaceMetadataAttributeDisplayType } from '../publication/common'; +export type { MarketplaceMetadataAttribute, MarketplaceMetadata } from '../publication/common'; +export type * from '../primitives.js'; + export enum PublicationMetadataVersion { V1 = '1.0.0', V2 = '2.0.0', @@ -109,13 +115,20 @@ const OpenSeaSchema = z.object({ version: z.nativeEnum(PublicationMetadataVersion), }); -const PublicationMetadataMediaSchema = z.object({ +/** + * @internal + */ +export const MediaSchema = z.object({ item: uriSchema('Marketplaces will store any NFT image here.'), altTag: z.string().optional().nullable().describe('The alt tag for accessibility.'), cover: uriSchema('The cover for any video or audio media.').optional().nullable(), type: z.string().optional().nullable().describe('This is the mime type of the media.'), }); -export type PublicationMetadataMedia = z.infer; +export type Media = z.infer; +/** + * @deprecated Use `Media` instead. + */ +export type PublicationMetadataMedia = Media; const ContentSchema = z .string({ @@ -140,7 +153,7 @@ const PublicationCommonSchema = OpenSeaSchema.extend({ .optional() .nullable(), - media: PublicationMetadataMediaSchema.array() + media: MediaSchema.array() .optional() .nullable() .describe('This is lens supported attached media items to the publication.'), @@ -310,6 +323,36 @@ const AccessConditionSchema = orCondition([ ]); export type AccessCondition = z.infer; +/** + * @internal + */ +export const EncryptedMediaSchema = z.object({ + item: uriSchema('Marketplaces will store any NFT image here.'), + altTag: z.string().optional().nullable().describe('The alt tag for accessibility.'), + cover: uriSchema('The cover for any video or audio media.').optional().nullable(), + type: z.string().optional().nullable().describe('This is the mime type of the media.'), +}); +export type EncryptedMedia = z.infer; + +const EncryptedFieldsSchema = z.object({ + content: z.string().optional().nullable(), + media: EncryptedMediaSchema.array().optional().nullable(), + image: z.string().optional().nullable(), + animation_url: z.string().optional().nullable(), + external_url: z.string().optional().nullable(), +}); +export type EncryptedFields = z.infer; + +/** + * @internal + */ +export const EncryptionParamsSchema = z.object({ + accessCondition: AccessConditionSchema, + encryptionKey: z.string().length(368, 'Encryption key should be 368 characters long.'), + encryptedFields: EncryptedFieldsSchema, +}); +export type EncryptionParams = z.infer; + const PublicationMetadataV2CommonSchema = PublicationCommonSchema.extend({ version: z.literal(PublicationMetadataVersion.V2, { description: 'The metadata version.' }), @@ -329,12 +372,7 @@ const PublicationMetadataV2CommonSchema = PublicationCommonSchema.extend({ .nullable() .describe('Ability to tag your publication.'), - encryptionParams: z - .object({ - accessCondition: AccessConditionSchema, - encryptionKey: z.string().length(368, 'Encryption key should be 368 characters long.'), - }) - .optional(), + encryptionParams: EncryptionParamsSchema.optional(), }); const PublicationMetadataV2ArticleSchema = PublicationMetadataV2CommonSchema.extend({ @@ -347,7 +385,7 @@ export type PublicationMetadataV2Article = z.infer value.some((media) => media.type && media.type in AudioMimeType), @@ -366,7 +404,7 @@ export type PublicationMetadataV2Embed = z.infer value.some((media) => media.type && media.type in ImageMimeType), @@ -404,7 +442,7 @@ export type PublicationMetadataV2TextOnly = z.infer value.some((media) => media.type && media.type in VideoMimeType), diff --git a/src/primitives.ts b/src/primitives.ts index 3e146a6..53bf670 100644 --- a/src/primitives.ts +++ b/src/primitives.ts @@ -24,7 +24,7 @@ export type Locale = Brand; /** * @internal */ -export function locale(value: string): Locale { +export function toLocale(value: string): Locale { return value as Locale; } /** @@ -36,7 +36,7 @@ export const LocaleSchema: z.Schema = z }) .min(2) .max(5) - .transform(locale); + .transform(toLocale); /** * An arbitrary tag. @@ -45,7 +45,7 @@ export type Tag = Brand; /** * @internal */ -export function tag(value: string): Tag { +export function toTag(value: string): Tag { return value as Tag; } /** @@ -53,7 +53,7 @@ export function tag(value: string): Tag { */ export const TagSchema: z.Schema = notEmptyString('An arbitrary tag.') .max(50) - .transform((value) => tag(value.toLowerCase())); + .transform((value) => toTag(value.toLowerCase())); /** * A Lens App identifier. @@ -62,14 +62,14 @@ export type AppId = Brand; /** * @internal */ -export function appId(value: string): AppId { +export function toAppId(value: string): AppId { return value as AppId; } /** * @internal */ export const AppIdSchema: z.Schema = - notEmptyString('A Lens App identifier.').transform(appId); + notEmptyString('A Lens App identifier.').transform(toAppId); /** * A cryptographic signature. @@ -78,7 +78,7 @@ export type Signature = Brand; /** * @internal */ -export function signature(value: string): Signature { +export function toSignature(value: string): Signature { return value as Signature; } /** @@ -86,7 +86,7 @@ export function signature(value: string): Signature { */ export const SignatureSchema: z.Schema = notEmptyString( 'A cryptographic signature of the Lens metadata.', -).transform(signature); +).transform(toSignature); /** * A markdown text. @@ -95,14 +95,14 @@ export type Markdown = Brand; /** * @internal */ -export function markdown(value: string): Markdown { +export function toMarkdown(value: string): Markdown { return value as Markdown; } /** * @internal */ export function markdownSchema(description: string): z.Schema { - return notEmptyString(description).transform(markdown); + return notEmptyString(description).transform(toMarkdown); } /** @@ -115,7 +115,7 @@ export type URI = Brand; /** * @internal */ -export function uri(value: string): URI { +export function toUri(value: string): URI { return value as URI; } /** @@ -128,7 +128,7 @@ export function uriSchema( .string({ description }) .min(6) // [ar://.] .url() // reads url() but works well with URIs too and uses format: 'uri' in the JSON schema - .transform(uri); + .transform(toUri); } /** @@ -150,14 +150,14 @@ export type Datetime = Brand; /** * @internal */ -export function datetime(value: string): Datetime { +export function toDatetime(value: string): Datetime { return value as Datetime; } /** * @internal */ export function datetimeSchema(description: string): z.Schema { - return z.string({ description }).datetime().transform(datetime); + return z.string({ description }).datetime().transform(toDatetime); } /** @@ -167,7 +167,7 @@ export type EvmAddress = Brand; /** * @internal */ -export function evmAddress(value: string): EvmAddress { +export function toEvmAddress(value: string): EvmAddress { return value as EvmAddress; } /** @@ -175,7 +175,7 @@ export function evmAddress(value: string): EvmAddress { */ export const EvmAddressSchema: z.Schema = notEmptyString( 'An EVM compatible address.', -).transform(evmAddress); +).transform(toEvmAddress); /** * An EVM compatible Chain Id. @@ -184,7 +184,7 @@ export type ChainId = Brand; /** * @internal */ -export function chainId(value: number): ChainId { +export function toChainId(value: number): ChainId { return value as ChainId; } /** @@ -193,7 +193,7 @@ export function chainId(value: number): ChainId { export const ChainIdSchema: z.Schema = z .number() .positive() - .transform(chainId); + .transform(toChainId); /** * @internal @@ -219,14 +219,14 @@ export type TokenId = Brand; /** * @internal */ -export function tokenId(value: string): TokenId { +export function toTokenId(value: string): TokenId { return value as TokenId; } /** * @internal */ export const TokenIdSchema: z.Schema = - notEmptyString().transform(tokenId); + notEmptyString().transform(toTokenId); /** * @internal @@ -270,14 +270,14 @@ export type ProfileId = Brand; /** * @internal */ -export function profileId(value: string): ProfileId { +export function toProfileId(value: string): ProfileId { return value as ProfileId; } /** * @internal */ export const ProfileIdSchema: z.Schema = - notEmptyString().transform(profileId); + notEmptyString().transform(toProfileId); /** * A publication identifier. @@ -286,11 +286,11 @@ export type PublicationId = Brand; /** * @internal */ -export function publicationId(value: string): PublicationId { +export function toPublicationId(value: string): PublicationId { return value as PublicationId; } /** * @internal */ export const PublicationIdSchema: z.Schema = - notEmptyString().transform(publicationId); + notEmptyString().transform(toPublicationId); diff --git a/src/publication/SpaceSchema.ts b/src/publication/SpaceSchema.ts index ab7f9ad..5e6dffc 100644 --- a/src/publication/SpaceSchema.ts +++ b/src/publication/SpaceSchema.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; import { PublicationMainFocus } from './PublicationMainFocus.js'; import { PublicationSchemaId } from './PublicationSchemaId.js'; import { AnyMediaSchema, mainContentFocus, metadataDetailsWith, publicationWith } from './common'; -import { datetimeSchema, uriSchema } from '../primitives.js'; +import { datetimeSchema, notEmptyString, uriSchema } from '../primitives.js'; /** * @internal @@ -14,6 +14,8 @@ export const SpaceSchema = publicationWith({ lens: metadataDetailsWith({ mainContentFocus: mainContentFocus(PublicationMainFocus.SPACE), + title: notEmptyString().describe('The space title.'), + link: uriSchema('The space join link.'), startsAt: datetimeSchema('The space start time (ISO 8601 `YYYY-MM-DDTHH:mm:ss.sssZ`).'), diff --git a/src/publication/TransactionSchema.ts b/src/publication/TransactionSchema.ts index 733c203..3985e0e 100644 --- a/src/publication/TransactionSchema.ts +++ b/src/publication/TransactionSchema.ts @@ -5,6 +5,12 @@ import { PublicationSchemaId } from './PublicationSchemaId.js'; import { AnyMediaSchema, mainContentFocus, metadataDetailsWith, publicationWith } from './common'; import { notEmptyString } from '../primitives.js'; +export enum TransactionMetadataType { + ERC721 = 'ERC721', + ERC20 = 'ERC20', + OTHER = 'OTHER', +} + /** * @internal */ @@ -15,6 +21,8 @@ export const TransactionSchema = publicationWith({ txHash: notEmptyString('The transaction hash.'), + type: z.nativeEnum(TransactionMetadataType).describe('The type of transaction.'), + chainId: z.number().positive().int().describe('The Chain Id.'), attachments: AnyMediaSchema.array() diff --git a/src/publication/__tests__/PublicationMetadataSchema.spec.ts b/src/publication/__tests__/PublicationMetadataSchema.spec.ts index 246e76c..5eb8a57 100644 --- a/src/publication/__tests__/PublicationMetadataSchema.spec.ts +++ b/src/publication/__tests__/PublicationMetadataSchema.spec.ts @@ -163,6 +163,7 @@ describe(`Given the PublicationMetadataSchema`, () => { · "lens.id": Required · "lens.locale": Required · "lens.mainContentFocus": Invalid literal value, expected "SPACE" + · "lens.title": Required · "lens.link": Required · "lens.startsAt": Required" `); @@ -232,6 +233,7 @@ describe(`Given the PublicationMetadataSchema`, () => { · "lens.locale": Required · "lens.mainContentFocus": Invalid literal value, expected "TRANSACTION" · "lens.txHash": Required + · "lens.type": Required · "lens.chainId": Required" `); }); diff --git a/src/publication/common/encryption.ts b/src/publication/common/encryption.ts index deec07e..b1ad62d 100644 --- a/src/publication/common/encryption.ts +++ b/src/publication/common/encryption.ts @@ -225,7 +225,7 @@ export type LitEncryptionKey = Brand; /** * @internal */ -export function litEncryptionKey(value: string): LitEncryptionKey { +export function toLitEncryptionKey(value: string): LitEncryptionKey { return value as LitEncryptionKey; } /** @@ -234,7 +234,7 @@ export function litEncryptionKey(value: string): LitEncryptionKey { export const LitEncryptionKeySchema: z.Schema = notEmptyString('A symmetric encryption key.') .length(368, 'Encryption key should be 368 characters long.') - .transform(litEncryptionKey); + .transform(toLitEncryptionKey); /** * @internal diff --git a/tsup.config.ts b/tsup.config.ts index d9b6a73..ed5d801 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'tsup'; export default defineConfig((options) => ({ - entry: ['src/index.ts'], + entry: ['src/index.ts', 'src/legacy/index.ts'], outDir: 'dist', splitting: false, sourcemap: true,