Skip to content
This repository has been archived by the owner on Sep 3, 2023. It is now read-only.

Commit

Permalink
feat(nodes): Add Book node
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw committed Aug 19, 2022
1 parent 443f8a9 commit d1ec8e6
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -5,6 +5,7 @@ export * from './core'
export * from './nodes/AggregateOffer'
export * from './nodes/AggregateRating'
export * from './nodes/Article'
export * from './nodes/Book'
export * from './nodes/Breadcrumb'
export * from './nodes/Comment'
export * from './nodes/Course'
Expand Down
67 changes: 67 additions & 0 deletions src/nodes/Book/index.test.ts
@@ -0,0 +1,67 @@
import { expect } from 'vitest'
import { injectSchemaOrg, useSchemaOrg, useSetup } from '../../../.test'
import { defineBook, defineBookEdition, definePerson } from '#provider'

describe('defineCourse', () => {
it('can be registered', () => {
useSetup(() => {
useSchemaOrg([
defineBook({
url: 'http://example.com/work/the_catcher_in_the_rye',
name: 'The Catcher in the Rye',
author: definePerson({
name: 'J.D. Salinger',
}),
sameAs: 'https://en.wikipedia.org/wiki/The_Catcher_in_the_Rye',
workExample: [
defineBookEdition({
isbn: '9787543321724',
bookEdition: 'Mass Market Paperback',
bookFormat: 'https://schema.org/Paperback',
inLanguage: 'en',
url: 'http://example.com/edition/the_catcher_in_the_rye_paperback',
datePublished: '1991-05-01',
identifier: {
'@type': 'PropertyValue',
'propertyID': 'OCLC_NUMBER',
'value': '1057320822',
},
}),
],
}),
])

const { graphNodes } = injectSchemaOrg()

expect(graphNodes).toMatchInlineSnapshot(`
[
{
"@id": "https://example.com/#book",
"@type": "Book",
"author": {
"@type": "Person",
"name": "J.D. Salinger",
},
"name": "The Catcher in the Rye",
"sameAs": "https://en.wikipedia.org/wiki/The_Catcher_in_the_Rye",
"url": "http://example.com/work/the_catcher_in_the_rye",
"workExample": {
"@type": "Book",
"bookEdition": "Mass Market Paperback",
"bookFormat": "https://schema.org/Paperback",
"datePublished": "1991-4-1",
"identifier": {
"@type": "PropertyValue",
"propertyID": "OCLC_NUMBER",
"value": "1057320822",
},
"inLanguage": "en",
"isbn": "9787543321724",
"url": "http://example.com/edition/the_catcher_in_the_rye_paperback",
},
},
]
`)
})
})
})
153 changes: 153 additions & 0 deletions src/nodes/Book/index.ts
@@ -0,0 +1,153 @@
import { withBase } from 'ufo'
import type {
Arrayable,
Identity,
NodeRelation,
NodeRelations,
OptionalSchemaOrgPrefix,
ResolvableDate,
Thing,
} from '../../types'
import {
IdentityId,
idReference, resolvableDateToDate, setIfEmpty,
} from '../../utils'
import { defineSchemaOrgResolver, resolveRelation } from '../../core'
import type { ReadAction } from '../WebPage'

export interface BookLite extends Thing {
/**
* The title of the book.
*/
name: string
/**
* A description of the course. Display limit of 60 characters.
*/
description?: string
/**
* A reference to an Organization piece, representing brand associated with the Product.
*/
author?: NodeRelation<Identity>
/**
* The URL on your website where the book is introduced or described.
*/
url?: string
/**
* The URL of a reference page that identifies the work. For example, a Wikipedia, Wikidata, VIAF, or Library of Congress page for the book.
*/
sameAs?: Arrayable<string>
/**
* The edition(s) of the work.
*/
workExample: NodeRelations<BookEdition>
}

export interface Book extends BookLite {}

type BookFormat = OptionalSchemaOrgPrefix<'AudiobookFormat'> | OptionalSchemaOrgPrefix<'EBook'> | OptionalSchemaOrgPrefix<'Hardcover'> | OptionalSchemaOrgPrefix<'Paperback'>

export interface BookEditionLite extends Thing {
/**
* The title of the edition. Only use this when the title of the edition is different from the title of the work.
*/
name?: string
/**
* The format of the edition.
*/
bookFormat: BookFormat
/**
* The main language of the content in the edition. Use one of the two-letter codes from the list of ISO 639-1 alpha-2 codes.
*/
inLanguage?: string
/**
* The ISBN-13 of the edition. If you have ISBN-10, convert it into ISBN-13.
*/
isbn: string
/**
* The action to be triggered for users to purchase or download the book.
*/
potentialAction?: Arrayable<ReadAction | any>
/**
* The author(s) of the edition.
*/
author?: NodeRelations<Identity>
/**
* The edition information of the book. For example, 2nd Edition.
*/
bookEdition?: string
/**
* The date of publication of the edition in YYYY-MM-DD or YYYY format. This can be either a specific date or only a specific year.
*/
datePublished?: ResolvableDate
/**
* The external or other ID that unambiguously identifies this edition. Multiple identifiers are allowed. For more details, refer to PropertyValue (identifier).
*/
identifier?: unknown
/**
* The URL of a reference web page that unambiguously indicates the edition. For example, a Wikipedia page for this specific edition. Don't reuse the sameAs of the Work.
*/
sameAs?: Arrayable<string>
/**
* The URL on your website where the edition is introduced or described. It can be the same as workExample.target.urlTemplate.
*/
url?: string
}

export interface BookEdition extends BookEditionLite {}

export const bookEditionResolver = defineSchemaOrgResolver<BookEdition>({
defaults: {
'@type': 'Book',
},
inheritMeta: [
'inLanguage',
],
resolve(node, ctx) {
if (node.bookFormat)
node.bookFormat = withBase(node.bookFormat, 'https://schema.org/') as BookFormat
if (node.datePublished)
node.datePublished = resolvableDateToDate(node.datePublished)

node.author = resolveRelation(node.author, ctx)
return node
},
rootNodeResolve(node, { findNode }) {
const identity = findNode<Identity>(IdentityId)

if (identity)
setIfEmpty(node, 'provider', idReference(identity))

return node
},
})

export const PrimaryBookId = '#book'

export const bookResolver = defineSchemaOrgResolver<Book>({
defaults: {
'@type': 'Book',
},
inheritMeta: [
'description',
'url',
{ meta: 'title', key: 'name' },
],
idPrefix: ['url', PrimaryBookId],
resolve(node, ctx) {
// provide a default sku
node.workExample = resolveRelation(node.workExample, ctx, bookEditionResolver)

node.author = resolveRelation(node.author, ctx)

if (node.url)
withBase(node.url, ctx.meta.host)
return node
},
rootNodeResolve(node, { findNode }) {
const identity = findNode<Identity>(IdentityId)

if (identity)
setIfEmpty(node, 'author', idReference(identity))
return node
},
})
6 changes: 5 additions & 1 deletion src/runtime/base.ts
@@ -1,6 +1,7 @@
import type {
AggregateOffer, AggregateRating,
Article,
Book, BookEdition,
BreadcrumbList,
Comment,
Course,
Expand Down Expand Up @@ -32,7 +33,8 @@ import {
addressResolver,
aggregateOfferResolver,
aggregateRatingResolver,
articleResolver,
articleResolver, bookEditionResolver,
bookResolver,
breadcrumbResolver,
commentResolver,
courseResolver,
Expand Down Expand Up @@ -63,6 +65,8 @@ export const defineAddress = <T extends PostalAddress>(input?: T) => provideReso
export const defineAggregateOffer = <T extends AggregateOffer>(input?: T) => provideResolver(input, aggregateOfferResolver as SchemaOrgNodeDefinition<T>)
export const defineAggregateRating = <T extends AggregateRating>(input?: T) => provideResolver(input, aggregateRatingResolver as SchemaOrgNodeDefinition<T>)
export const defineArticle = <T extends Article>(input?: T) => provideResolver(input, articleResolver as SchemaOrgNodeDefinition<T>)
export const defineBook = <T extends Book>(input?: T) => provideResolver(input, bookResolver as SchemaOrgNodeDefinition<T>)
export const defineBookEdition = <T extends BookEdition>(input?: T) => provideResolver(input, bookEditionResolver as SchemaOrgNodeDefinition<T>)
export const defineBreadcrumb = <T extends BreadcrumbList>(input?: T) => provideResolver(input, breadcrumbResolver as SchemaOrgNodeDefinition<T>)
export const defineComment = <T extends Comment>(input?: T) => provideResolver(input, commentResolver as SchemaOrgNodeDefinition<T>)
export const defineCourse = <T extends Course>(input?: T) => provideResolver(input, courseResolver as SchemaOrgNodeDefinition<T>)
Expand Down

0 comments on commit d1ec8e6

Please sign in to comment.