@answerable-kit/schemas 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +39 -0
  2. package/dist/_internal.d.ts +14 -0
  3. package/dist/_internal.d.ts.map +1 -0
  4. package/dist/_internal.js +2 -0
  5. package/dist/_internal.js.map +1 -0
  6. package/dist/_offers.d.ts +47 -0
  7. package/dist/_offers.d.ts.map +1 -0
  8. package/dist/_offers.js +78 -0
  9. package/dist/_offers.js.map +1 -0
  10. package/dist/article.d.ts +64 -0
  11. package/dist/article.d.ts.map +1 -0
  12. package/dist/article.js +139 -0
  13. package/dist/article.js.map +1 -0
  14. package/dist/breadcrumb.d.ts +22 -0
  15. package/dist/breadcrumb.d.ts.map +1 -0
  16. package/dist/breadcrumb.js +36 -0
  17. package/dist/breadcrumb.js.map +1 -0
  18. package/dist/faq-page.d.ts +20 -0
  19. package/dist/faq-page.d.ts.map +1 -0
  20. package/dist/faq-page.js +40 -0
  21. package/dist/faq-page.js.map +1 -0
  22. package/dist/how-to.d.ts +45 -0
  23. package/dist/how-to.d.ts.map +1 -0
  24. package/dist/how-to.js +106 -0
  25. package/dist/how-to.js.map +1 -0
  26. package/dist/index.d.ts +18 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +17 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/organization.d.ts +32 -0
  31. package/dist/organization.d.ts.map +1 -0
  32. package/dist/organization.js +44 -0
  33. package/dist/organization.js.map +1 -0
  34. package/dist/product.d.ts +28 -0
  35. package/dist/product.d.ts.map +1 -0
  36. package/dist/product.js +56 -0
  37. package/dist/product.js.map +1 -0
  38. package/dist/software-application.d.ts +41 -0
  39. package/dist/software-application.d.ts.map +1 -0
  40. package/dist/software-application.js +52 -0
  41. package/dist/software-application.js.map +1 -0
  42. package/dist/website.d.ts +29 -0
  43. package/dist/website.d.ts.map +1 -0
  44. package/dist/website.js +46 -0
  45. package/dist/website.js.map +1 -0
  46. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @answerable-kit/schemas
2
+
3
+ Type-safe JSON-LD generators for the [Answerable](https://github.com/Anuj7411/answerable) SEO toolkit. Each generator returns a fully-typed `WithContext<T>` object validated at the type level by [`schema-dts`](https://github.com/google/schema-dts).
4
+
5
+ > **Pre-alpha.** Only `organization()` and `webSite()` ship today. Eight generators land in Phase 1 — see the [project roadmap](../../docs/internal/ROADMAP.md).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @answerable-kit/schemas
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { organization, webSite } from '@answerable-kit/schemas';
17
+
18
+ const org = organization({
19
+ name: 'Acme',
20
+ url: 'https://acme.com',
21
+ logo: 'https://acme.com/logo.png',
22
+ sameAs: ['https://twitter.com/acme', 'https://github.com/acme'],
23
+ });
24
+
25
+ const site = webSite({
26
+ name: 'Acme',
27
+ url: 'https://acme.com',
28
+ searchUrlTemplate: 'https://acme.com/search?q={search_term_string}',
29
+ });
30
+
31
+ // Inject as JSON-LD in your Next.js layout:
32
+ // <script type="application/ld+json">{JSON.stringify(org)}</script>
33
+ ```
34
+
35
+ All URL inputs are validated as absolute `http(s)` URLs at runtime — invalid input throws `InvalidUrlError` from `@answerable-kit/core`.
36
+
37
+ ## License
38
+
39
+ [MIT](../../LICENSE) © 2026 Anuj Ojha
@@ -0,0 +1,14 @@
1
+ import type { Thing, WithContext } from 'schema-dts';
2
+ /**
3
+ * schema-dts models every schema.org class as `XLeaf | … | string`,
4
+ * where the trailing string is the JSON-LD `@id` reference form. This
5
+ * helper strips that reference variant so callers of our generators
6
+ * can access fields directly (e.g. `.logo`, `.mainEntity`) without
7
+ * first having to rule out the string branch.
8
+ *
9
+ * Constrained to `Thing` because `WithContext` requires it.
10
+ *
11
+ * Internal — not part of the public API.
12
+ */
13
+ export type Schema<T extends Thing> = WithContext<Exclude<T, string>>;
14
+ //# sourceMappingURL=_internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_internal.d.ts","sourceRoot":"","sources":["../src/_internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,KAAK,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=_internal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_internal.js","sourceRoot":"","sources":["../src/_internal.ts"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
1
+ import type { AggregateRating, Offer } from 'schema-dts';
2
+ /**
3
+ * Availability values accepted by schema.org's `ItemAvailability`
4
+ * enumeration. We accept the short form here and emit the full
5
+ * `https://schema.org/<value>` URI in JSON-LD output (Google's
6
+ * preferred form).
7
+ */
8
+ export type OfferAvailability = 'InStock' | 'OutOfStock' | 'PreOrder' | 'BackOrder' | 'Discontinued' | 'LimitedAvailability' | 'SoldOut';
9
+ export interface OffersInput {
10
+ /** Price as a number. `0` is valid (free app, free product). */
11
+ readonly price: number;
12
+ /** ISO 4217 three-letter currency code, e.g. `"USD"`, `"EUR"`. */
13
+ readonly priceCurrency: string;
14
+ readonly availability?: OfferAvailability | undefined;
15
+ /** Optional purchase / pricing-page URL. Must be absolute http(s). */
16
+ readonly url?: string | undefined;
17
+ }
18
+ export interface AggregateRatingInput {
19
+ /** Average rating. Must be between `worstRating` and `bestRating`. */
20
+ readonly ratingValue: number;
21
+ /** Number of ratings. Must be a non-negative integer. */
22
+ readonly ratingCount: number;
23
+ /** Defaults to 5 when omitted (the schema.org default). */
24
+ readonly bestRating?: number | undefined;
25
+ /** Defaults to 1 when omitted (the schema.org default). */
26
+ readonly worstRating?: number | undefined;
27
+ }
28
+ /**
29
+ * Collect every validation issue in an offers block. Returns an empty
30
+ * array on valid input; callers concat into one batched
31
+ * `SchemaValidationError`.
32
+ */
33
+ export declare function validateOffers(o: OffersInput, prefix: string): string[];
34
+ /**
35
+ * Collect every validation issue in an aggregate-rating block.
36
+ */
37
+ export declare function validateAggregateRating(r: AggregateRatingInput, prefix: string): string[];
38
+ /**
39
+ * Build the JSON-LD `Offer` nested object. URL validation runs here —
40
+ * call only after `validateOffers` has confirmed the other fields.
41
+ */
42
+ export declare function buildOffer(o: OffersInput): Exclude<Offer, string>;
43
+ /**
44
+ * Build the JSON-LD `AggregateRating` nested object.
45
+ */
46
+ export declare function buildAggregateRating(r: AggregateRatingInput): Exclude<AggregateRating, string>;
47
+ //# sourceMappingURL=_offers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_offers.d.ts","sourceRoot":"","sources":["../src/_offers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GACzB,SAAS,GACT,YAAY,GACZ,UAAU,GACV,WAAW,GACX,cAAc,GACd,qBAAqB,GACrB,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACtD,sEAAsE;IACtE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAKD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAavE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBzF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAajE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,EAAE,MAAM,CAAC,CAa9F"}
@@ -0,0 +1,78 @@
1
+ import { parseAbsoluteUrl } from '@answerable-kit/core';
2
+ /** ISO 4217 currency codes are exactly three uppercase letters. */
3
+ const ISO_4217 = /^[A-Z]{3}$/;
4
+ /**
5
+ * Collect every validation issue in an offers block. Returns an empty
6
+ * array on valid input; callers concat into one batched
7
+ * `SchemaValidationError`.
8
+ */
9
+ export function validateOffers(o, prefix) {
10
+ const issues = [];
11
+ if (!Number.isFinite(o.price)) {
12
+ issues.push(`${prefix}.price must be a finite number (got ${o.price})`);
13
+ }
14
+ else if (o.price < 0) {
15
+ issues.push(`${prefix}.price must be non-negative (got ${o.price})`);
16
+ }
17
+ if (!ISO_4217.test(o.priceCurrency)) {
18
+ issues.push(`${prefix}.priceCurrency must be a 3-letter ISO 4217 code (got "${o.priceCurrency}")`);
19
+ }
20
+ return issues;
21
+ }
22
+ /**
23
+ * Collect every validation issue in an aggregate-rating block.
24
+ */
25
+ export function validateAggregateRating(r, prefix) {
26
+ const issues = [];
27
+ const best = r.bestRating ?? 5;
28
+ const worst = r.worstRating ?? 1;
29
+ if (worst > best) {
30
+ issues.push(`${prefix}: worstRating (${worst}) cannot exceed bestRating (${best})`);
31
+ }
32
+ if (!Number.isFinite(r.ratingValue)) {
33
+ issues.push(`${prefix}.ratingValue must be a finite number (got ${r.ratingValue})`);
34
+ }
35
+ else if (r.ratingValue < worst || r.ratingValue > best) {
36
+ issues.push(`${prefix}.ratingValue must be between ${worst} and ${best} (got ${r.ratingValue})`);
37
+ }
38
+ if (!Number.isInteger(r.ratingCount) || r.ratingCount < 0) {
39
+ issues.push(`${prefix}.ratingCount must be a non-negative integer (got ${r.ratingCount})`);
40
+ }
41
+ return issues;
42
+ }
43
+ /**
44
+ * Build the JSON-LD `Offer` nested object. URL validation runs here —
45
+ * call only after `validateOffers` has confirmed the other fields.
46
+ */
47
+ export function buildOffer(o) {
48
+ const out = {
49
+ '@type': 'Offer',
50
+ price: o.price,
51
+ priceCurrency: o.priceCurrency,
52
+ };
53
+ if (o.availability !== undefined) {
54
+ out.availability = `https://schema.org/${o.availability}`;
55
+ }
56
+ if (o.url !== undefined) {
57
+ out.url = parseAbsoluteUrl(o.url);
58
+ }
59
+ return out;
60
+ }
61
+ /**
62
+ * Build the JSON-LD `AggregateRating` nested object.
63
+ */
64
+ export function buildAggregateRating(r) {
65
+ const out = {
66
+ '@type': 'AggregateRating',
67
+ ratingValue: r.ratingValue,
68
+ ratingCount: r.ratingCount,
69
+ };
70
+ if (r.bestRating !== undefined) {
71
+ out.bestRating = r.bestRating;
72
+ }
73
+ if (r.worstRating !== undefined) {
74
+ out.worstRating = r.worstRating;
75
+ }
76
+ return out;
77
+ }
78
+ //# sourceMappingURL=_offers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_offers.js","sourceRoot":"","sources":["../src/_offers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import type { Article, BlogPosting } from 'schema-dts';
2
+ import type { Schema } from './_internal.js';
3
+ /**
4
+ * The author of an article. A discriminated union so the type system
5
+ * knows whether `url` is required (Organization) or optional (Person)
6
+ * and whether `logo` is meaningful at all (Organization only).
7
+ */
8
+ export type AuthorInput = {
9
+ readonly type: 'Person';
10
+ readonly name: string;
11
+ readonly url?: string | undefined;
12
+ } | {
13
+ readonly type: 'Organization';
14
+ readonly name: string;
15
+ readonly url: string;
16
+ readonly logo?: string | undefined;
17
+ };
18
+ /**
19
+ * The publisher of an article — always an Organization per Google's
20
+ * structured-data guidelines for Article / BlogPosting.
21
+ */
22
+ export interface PublisherInput {
23
+ readonly name: string;
24
+ readonly url: string;
25
+ readonly logo?: string | undefined;
26
+ }
27
+ export interface ArticleInput {
28
+ readonly headline: string;
29
+ /** Canonical URL of the article. */
30
+ readonly url: string;
31
+ readonly description?: string | undefined;
32
+ /** Hero/cover image absolute URL. */
33
+ readonly image?: string | undefined;
34
+ /**
35
+ * ISO 8601 date string. Accepts date-only (`"2026-05-14"`) or
36
+ * date-time (`"2026-05-14T12:00:00Z"`, `"2026-05-14T12:00:00+05:30"`).
37
+ */
38
+ readonly datePublished: string;
39
+ /** Same format as `datePublished`. */
40
+ readonly dateModified?: string | undefined;
41
+ readonly author: AuthorInput;
42
+ readonly publisher: PublisherInput;
43
+ }
44
+ /**
45
+ * Generate a fully-typed JSON-LD `Article` object. Drives audit
46
+ * check **C5** and supplies the date/author signals that the
47
+ * E-E-A-T checks **D7** and **D8** look for.
48
+ *
49
+ * @throws SchemaValidationError batching every empty-string and
50
+ * bad-date issue found in `input`.
51
+ * @throws InvalidUrlError for the first malformed URL encountered
52
+ * (`url`, `image`, `author.url`, `author.logo`, `publisher.url`,
53
+ * `publisher.logo`).
54
+ */
55
+ export declare function article(input: ArticleInput): Schema<Article>;
56
+ /**
57
+ * Same shape as `article()` but emits `@type: 'BlogPosting'`. In
58
+ * schema.org, `BlogPosting` extends `Article`, so every field that
59
+ * works on `article()` works here too.
60
+ *
61
+ * @throws SchemaValidationError / InvalidUrlError — see `article()`.
62
+ */
63
+ export declare function blogPosting(input: ArticleInput): Schema<BlogPosting>;
64
+ //# sourceMappingURL=article.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"article.d.ts","sourceRoot":"","sources":["../src/article.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAwB,MAAM,YAAY,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;;;GAIG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACrF;IACE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEN;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oCAAoC;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,qCAAqC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,sCAAsC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;CACpC;AA8GD;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAO5D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC,CAOpE"}
@@ -0,0 +1,139 @@
1
+ import { SchemaValidationError, parseAbsoluteUrl } from '@answerable-kit/core';
2
+ /**
3
+ * ISO 8601 date (`YYYY-MM-DD`) or date-time with optional fractional
4
+ * seconds and timezone. Tighter than `Date.parse`, which (a) accepts
5
+ * loose formats like `"5/14/2026"` and (b) silently rolls over
6
+ * impossible dates like `"2026-02-30"` to March 2. We catch both.
7
+ */
8
+ const ISO_DATE_OR_DATETIME = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
9
+ function isValidIso8601(s) {
10
+ if (!ISO_DATE_OR_DATETIME.test(s))
11
+ return false;
12
+ if (Number.isNaN(Date.parse(s)))
13
+ return false;
14
+ // Date.parse rolls impossible calendar dates over silently
15
+ // ("2026-02-30" → March 2). Validate the date portion by
16
+ // round-tripping the year/month/day through a UTC Date.
17
+ const [yearStr, monthStr, dayStr] = s.slice(0, 10).split('-');
18
+ if (yearStr === undefined || monthStr === undefined || dayStr === undefined)
19
+ return false;
20
+ const year = Number(yearStr);
21
+ const month = Number(monthStr);
22
+ const day = Number(dayStr);
23
+ const d = new Date(Date.UTC(year, month - 1, day));
24
+ return d.getUTCFullYear() === year && d.getUTCMonth() === month - 1 && d.getUTCDate() === day;
25
+ }
26
+ function validateArticleInput(input) {
27
+ const issues = [];
28
+ if (input.headline.trim() === '') {
29
+ issues.push('headline is empty');
30
+ }
31
+ if (input.author.name.trim() === '') {
32
+ issues.push('author.name is empty');
33
+ }
34
+ if (input.publisher.name.trim() === '') {
35
+ issues.push('publisher.name is empty');
36
+ }
37
+ if (!isValidIso8601(input.datePublished)) {
38
+ issues.push(`datePublished is not a valid ISO 8601 date (got "${input.datePublished}")`);
39
+ }
40
+ if (input.dateModified !== undefined && !isValidIso8601(input.dateModified)) {
41
+ issues.push(`dateModified is not a valid ISO 8601 date (got "${input.dateModified}")`);
42
+ }
43
+ if (issues.length > 0) {
44
+ throw new SchemaValidationError(issues);
45
+ }
46
+ }
47
+ function buildAuthor(a) {
48
+ if (a.type === 'Person') {
49
+ const out = {
50
+ '@type': 'Person',
51
+ name: a.name,
52
+ };
53
+ if (a.url !== undefined) {
54
+ out.url = parseAbsoluteUrl(a.url);
55
+ }
56
+ return out;
57
+ }
58
+ const out = {
59
+ '@type': 'Organization',
60
+ name: a.name,
61
+ url: parseAbsoluteUrl(a.url),
62
+ };
63
+ if (a.logo !== undefined) {
64
+ out.logo = parseAbsoluteUrl(a.logo);
65
+ }
66
+ return out;
67
+ }
68
+ function buildPublisher(p) {
69
+ const out = {
70
+ '@type': 'Organization',
71
+ name: p.name,
72
+ url: parseAbsoluteUrl(p.url),
73
+ };
74
+ if (p.logo !== undefined) {
75
+ out.logo = parseAbsoluteUrl(p.logo);
76
+ }
77
+ return out;
78
+ }
79
+ /**
80
+ * Common body shared by `article()` and `blogPosting()`. Returns
81
+ * everything except `@context` and `@type` so each entry point can
82
+ * supply its own discriminator.
83
+ */
84
+ function buildArticleBody(input) {
85
+ const url = parseAbsoluteUrl(input.url);
86
+ const body = {
87
+ headline: input.headline,
88
+ url,
89
+ mainEntityOfPage: url,
90
+ datePublished: input.datePublished,
91
+ author: buildAuthor(input.author),
92
+ publisher: buildPublisher(input.publisher),
93
+ };
94
+ if (input.description !== undefined) {
95
+ body.description = input.description;
96
+ }
97
+ if (input.image !== undefined) {
98
+ body.image = parseAbsoluteUrl(input.image);
99
+ }
100
+ if (input.dateModified !== undefined) {
101
+ body.dateModified = input.dateModified;
102
+ }
103
+ return body;
104
+ }
105
+ /**
106
+ * Generate a fully-typed JSON-LD `Article` object. Drives audit
107
+ * check **C5** and supplies the date/author signals that the
108
+ * E-E-A-T checks **D7** and **D8** look for.
109
+ *
110
+ * @throws SchemaValidationError batching every empty-string and
111
+ * bad-date issue found in `input`.
112
+ * @throws InvalidUrlError for the first malformed URL encountered
113
+ * (`url`, `image`, `author.url`, `author.logo`, `publisher.url`,
114
+ * `publisher.logo`).
115
+ */
116
+ export function article(input) {
117
+ validateArticleInput(input);
118
+ return {
119
+ '@context': 'https://schema.org',
120
+ '@type': 'Article',
121
+ ...buildArticleBody(input),
122
+ };
123
+ }
124
+ /**
125
+ * Same shape as `article()` but emits `@type: 'BlogPosting'`. In
126
+ * schema.org, `BlogPosting` extends `Article`, so every field that
127
+ * works on `article()` works here too.
128
+ *
129
+ * @throws SchemaValidationError / InvalidUrlError — see `article()`.
130
+ */
131
+ export function blogPosting(input) {
132
+ validateArticleInput(input);
133
+ return {
134
+ '@context': 'https://schema.org',
135
+ '@type': 'BlogPosting',
136
+ ...buildArticleBody(input),
137
+ };
138
+ }
139
+ //# sourceMappingURL=article.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"article.js","sourceRoot":"","sources":["../src/article.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { BreadcrumbList } from 'schema-dts';
2
+ import type { Schema } from './_internal.js';
3
+ export interface BreadcrumbItemInput {
4
+ readonly name: string;
5
+ /** Absolute http(s) URL the breadcrumb item links to. */
6
+ readonly url: string;
7
+ }
8
+ export interface BreadcrumbInput {
9
+ readonly items: readonly BreadcrumbItemInput[];
10
+ }
11
+ /**
12
+ * Generate a fully-typed JSON-LD `BreadcrumbList` object with positions
13
+ * assigned automatically starting from 1. Drives audit check `C7`.
14
+ *
15
+ * @throws SchemaValidationError if `items` is empty or any `name` is
16
+ * blank after trimming.
17
+ * @throws InvalidUrlError if any `url` is not a valid http(s) URL.
18
+ * URL validation runs after the empty-list / empty-name guards so
19
+ * callers see the most useful failure first.
20
+ */
21
+ export declare function breadcrumb(input: BreadcrumbInput): Schema<BreadcrumbList>;
22
+ //# sourceMappingURL=breadcrumb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../src/breadcrumb.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAChD;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,cAAc,CAAC,CAyBzE"}
@@ -0,0 +1,36 @@
1
+ import { SchemaValidationError, parseAbsoluteUrl } from '@answerable-kit/core';
2
+ /**
3
+ * Generate a fully-typed JSON-LD `BreadcrumbList` object with positions
4
+ * assigned automatically starting from 1. Drives audit check `C7`.
5
+ *
6
+ * @throws SchemaValidationError if `items` is empty or any `name` is
7
+ * blank after trimming.
8
+ * @throws InvalidUrlError if any `url` is not a valid http(s) URL.
9
+ * URL validation runs after the empty-list / empty-name guards so
10
+ * callers see the most useful failure first.
11
+ */
12
+ export function breadcrumb(input) {
13
+ if (input.items.length === 0) {
14
+ throw new SchemaValidationError(['breadcrumb requires at least one item']);
15
+ }
16
+ const issues = [];
17
+ input.items.forEach((it, i) => {
18
+ if (it.name.trim() === '') {
19
+ issues.push(`items[${i}].name is empty`);
20
+ }
21
+ });
22
+ if (issues.length > 0) {
23
+ throw new SchemaValidationError(issues);
24
+ }
25
+ return {
26
+ '@context': 'https://schema.org',
27
+ '@type': 'BreadcrumbList',
28
+ itemListElement: input.items.map((it, i) => ({
29
+ '@type': 'ListItem',
30
+ position: i + 1,
31
+ name: it.name,
32
+ item: parseAbsoluteUrl(it.url),
33
+ })),
34
+ };
35
+ }
36
+ //# sourceMappingURL=breadcrumb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breadcrumb.js","sourceRoot":"","sources":["../src/breadcrumb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAc/E;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,qBAAqB,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QAC5B,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO;QACL,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,gBAAgB;QACzB,eAAe,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3C,OAAO,EAAE,UAAU;YACnB,QAAQ,EAAE,CAAC,GAAG,CAAC;YACf,IAAI,EAAE,EAAE,CAAC,IAAI;YACb,IAAI,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,CAAC;SAC/B,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { FAQPage } from 'schema-dts';
2
+ import type { Schema } from './_internal.js';
3
+ export interface FaqQuestionInput {
4
+ readonly question: string;
5
+ readonly answer: string;
6
+ }
7
+ export interface FaqPageInput {
8
+ readonly questions: readonly FaqQuestionInput[];
9
+ }
10
+ /**
11
+ * Generate a fully-typed JSON-LD `FAQPage` object. AI answer engines
12
+ * (Perplexity, Claude, ChatGPT) and Google's rich-result FAQs both
13
+ * key off this schema, making it one of the highest-leverage helpers.
14
+ *
15
+ * @throws SchemaValidationError if `questions` is empty or if any
16
+ * question / answer is blank after trimming whitespace. The error's
17
+ * `issues` array enumerates every problem entry, not just the first.
18
+ */
19
+ export declare function faqPage(input: FaqPageInput): Schema<FAQPage>;
20
+ //# sourceMappingURL=faq-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faq-page.d.ts","sourceRoot":"","sources":["../src/faq-page.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACjD;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CA8B5D"}
@@ -0,0 +1,40 @@
1
+ import { SchemaValidationError } from '@answerable-kit/core';
2
+ /**
3
+ * Generate a fully-typed JSON-LD `FAQPage` object. AI answer engines
4
+ * (Perplexity, Claude, ChatGPT) and Google's rich-result FAQs both
5
+ * key off this schema, making it one of the highest-leverage helpers.
6
+ *
7
+ * @throws SchemaValidationError if `questions` is empty or if any
8
+ * question / answer is blank after trimming whitespace. The error's
9
+ * `issues` array enumerates every problem entry, not just the first.
10
+ */
11
+ export function faqPage(input) {
12
+ if (input.questions.length === 0) {
13
+ throw new SchemaValidationError(['faqPage requires at least one question']);
14
+ }
15
+ const issues = [];
16
+ input.questions.forEach((q, i) => {
17
+ if (q.question.trim() === '') {
18
+ issues.push(`questions[${i}].question is empty`);
19
+ }
20
+ if (q.answer.trim() === '') {
21
+ issues.push(`questions[${i}].answer is empty`);
22
+ }
23
+ });
24
+ if (issues.length > 0) {
25
+ throw new SchemaValidationError(issues);
26
+ }
27
+ return {
28
+ '@context': 'https://schema.org',
29
+ '@type': 'FAQPage',
30
+ mainEntity: input.questions.map((q) => ({
31
+ '@type': 'Question',
32
+ name: q.question,
33
+ acceptedAnswer: {
34
+ '@type': 'Answer',
35
+ text: q.answer,
36
+ },
37
+ })),
38
+ };
39
+ }
40
+ //# sourceMappingURL=faq-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faq-page.js","sourceRoot":"","sources":["../src/faq-page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAa7D;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CAAC,KAAmB;IACzC,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,qBAAqB,CAAC,CAAC,wCAAwC,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC/B,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC;QACjD,CAAC;IACH,CAAC,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO;QACL,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,SAAS;QAClB,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACtC,OAAO,EAAE,UAAU;YACnB,IAAI,EAAE,CAAC,CAAC,QAAQ;YAChB,cAAc,EAAE;gBACd,OAAO,EAAE,QAAQ;gBACjB,IAAI,EAAE,CAAC,CAAC,MAAM;aACf;SACF,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { HowTo } from 'schema-dts';
2
+ import type { Schema } from './_internal.js';
3
+ export interface HowToStepInput {
4
+ readonly name: string;
5
+ readonly text: string;
6
+ /** Absolute URL to an illustrative image for this step. */
7
+ readonly image?: string | undefined;
8
+ /** Absolute URL anchored to this step (e.g. a video timestamp). */
9
+ readonly url?: string | undefined;
10
+ }
11
+ export interface HowToInput {
12
+ readonly name: string;
13
+ readonly description?: string | undefined;
14
+ /**
15
+ * ISO 8601 duration string. Accepts the standard formats:
16
+ * - Time-only: `"PT15M"`, `"PT1H30M"`, `"PT30S"`, `"PT2H"`
17
+ * - Date-only: `"P1D"`, `"P2W"`, `"P1Y2M3D"`
18
+ * - Date + time: `"P1DT2H"`, `"P1Y2DT3H30M"`
19
+ *
20
+ * Fractional seconds are allowed (`"PT30.5S"`).
21
+ */
22
+ readonly totalTime?: string | undefined;
23
+ /** Absolute URL to a hero / cover image for the whole how-to. */
24
+ readonly image?: string | undefined;
25
+ /** Ordered list of steps. Must contain at least one entry. */
26
+ readonly steps: readonly HowToStepInput[];
27
+ /** Consumables needed (ingredients, materials). */
28
+ readonly supply?: readonly string[] | undefined;
29
+ /** Reusable tools needed (knives, hammers, software). */
30
+ readonly tool?: readonly string[] | undefined;
31
+ }
32
+ /**
33
+ * Generate a fully-typed JSON-LD `HowTo` object. Drives audit
34
+ * check **C8**.
35
+ *
36
+ * Steps are auto-numbered via the `position` field starting at 1 —
37
+ * callers don't supply positions themselves.
38
+ *
39
+ * @throws SchemaValidationError batching every issue across the
40
+ * top-level fields and every step.
41
+ * @throws InvalidUrlError for the first malformed URL encountered
42
+ * (`image`, any `steps[*].image`, any `steps[*].url`).
43
+ */
44
+ export declare function howTo(input: HowToInput): Schema<HowTo>;
45
+ //# sourceMappingURL=how-to.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"how-to.d.ts","sourceRoot":"","sources":["../src/how-to.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAqC,MAAM,YAAY,CAAC;AAC3E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAChD,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAC/C;AA8ED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CA2BtD"}
package/dist/how-to.js ADDED
@@ -0,0 +1,106 @@
1
+ import { SchemaValidationError, parseAbsoluteUrl } from '@answerable-kit/core';
2
+ /**
3
+ * Strict ISO 8601 duration. Schema.org accepts both the "weeks alone"
4
+ * form (`P2W`) and the "Y/M/D + T H/M/S" form, but not a mix of the two.
5
+ */
6
+ const WEEK_ONLY = /^P(\d+)W$/;
7
+ const DATE_AND_TIME = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
8
+ function isValidIso8601Duration(s) {
9
+ // Quick rejects.
10
+ if (s.length < 3 || s.charAt(0) !== 'P')
11
+ return false;
12
+ // Weeks-only form (cannot combine with other components).
13
+ if (WEEK_ONLY.test(s))
14
+ return true;
15
+ const match = s.match(DATE_AND_TIME);
16
+ if (!match)
17
+ return false;
18
+ // A bare "T" with no time components is invalid (e.g. "P1DT").
19
+ if (s.includes('T') && !/T\d/.test(s))
20
+ return false;
21
+ // At least one component (Y/M/D/H/M/S) must be present —
22
+ // the regex itself would otherwise accept "P".
23
+ return match.slice(1).some((g) => g !== undefined);
24
+ }
25
+ function validateHowToInput(input) {
26
+ const issues = [];
27
+ if (input.name.trim() === '') {
28
+ issues.push('name is empty');
29
+ }
30
+ if (input.steps.length === 0) {
31
+ issues.push('steps must contain at least one step');
32
+ }
33
+ input.steps.forEach((step, i) => {
34
+ if (step.name.trim() === '') {
35
+ issues.push(`steps[${i}].name is empty`);
36
+ }
37
+ if (step.text.trim() === '') {
38
+ issues.push(`steps[${i}].text is empty`);
39
+ }
40
+ });
41
+ if (input.totalTime !== undefined && !isValidIso8601Duration(input.totalTime)) {
42
+ issues.push(`totalTime is not a valid ISO 8601 duration (got "${input.totalTime}"). Expected formats like "PT15M", "PT1H30M", or "P1D".`);
43
+ }
44
+ if (issues.length > 0) {
45
+ throw new SchemaValidationError(issues);
46
+ }
47
+ }
48
+ function buildStep(s, position) {
49
+ const out = {
50
+ '@type': 'HowToStep',
51
+ position,
52
+ name: s.name,
53
+ text: s.text,
54
+ };
55
+ if (s.image !== undefined) {
56
+ out.image = parseAbsoluteUrl(s.image);
57
+ }
58
+ if (s.url !== undefined) {
59
+ out.url = parseAbsoluteUrl(s.url);
60
+ }
61
+ return out;
62
+ }
63
+ function buildSupply(name) {
64
+ return { '@type': 'HowToSupply', name };
65
+ }
66
+ function buildTool(name) {
67
+ return { '@type': 'HowToTool', name };
68
+ }
69
+ /**
70
+ * Generate a fully-typed JSON-LD `HowTo` object. Drives audit
71
+ * check **C8**.
72
+ *
73
+ * Steps are auto-numbered via the `position` field starting at 1 —
74
+ * callers don't supply positions themselves.
75
+ *
76
+ * @throws SchemaValidationError batching every issue across the
77
+ * top-level fields and every step.
78
+ * @throws InvalidUrlError for the first malformed URL encountered
79
+ * (`image`, any `steps[*].image`, any `steps[*].url`).
80
+ */
81
+ export function howTo(input) {
82
+ validateHowToInput(input);
83
+ const out = {
84
+ '@context': 'https://schema.org',
85
+ '@type': 'HowTo',
86
+ name: input.name,
87
+ step: input.steps.map((s, i) => buildStep(s, i + 1)),
88
+ };
89
+ if (input.description !== undefined) {
90
+ out.description = input.description;
91
+ }
92
+ if (input.totalTime !== undefined) {
93
+ out.totalTime = input.totalTime;
94
+ }
95
+ if (input.image !== undefined) {
96
+ out.image = parseAbsoluteUrl(input.image);
97
+ }
98
+ if (input.supply !== undefined && input.supply.length > 0) {
99
+ out.supply = input.supply.map(buildSupply);
100
+ }
101
+ if (input.tool !== undefined && input.tool.length > 0) {
102
+ out.tool = input.tool.map(buildTool);
103
+ }
104
+ return out;
105
+ }
106
+ //# sourceMappingURL=how-to.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"how-to.js","sourceRoot":"","sources":["../src/how-to.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @answerable-kit/schemas — type-safe JSON-LD generators for the
3
+ * Answerable SEO toolkit. Each helper returns a `WithContext<T>`
4
+ * object from `schema-dts` (narrowed to remove the IRI-reference
5
+ * variant) that's ready to be embedded as
6
+ * `<script type="application/ld+json">{...}</script>`.
7
+ */
8
+ export declare const VERSION = "0.0.0";
9
+ export { article, blogPosting, type ArticleInput, type AuthorInput, type PublisherInput, } from './article.js';
10
+ export { breadcrumb, type BreadcrumbInput, type BreadcrumbItemInput } from './breadcrumb.js';
11
+ export { faqPage, type FaqPageInput, type FaqQuestionInput } from './faq-page.js';
12
+ export { howTo, type HowToInput, type HowToStepInput } from './how-to.js';
13
+ export { organization, type ContactPointInput, type OrganizationInput, } from './organization.js';
14
+ export { product, type ProductInput } from './product.js';
15
+ export { softwareApplication, type SoftwareApplicationInput, } from './software-application.js';
16
+ export { webSite, type WebSiteInput } from './website.js';
17
+ export type { AggregateRatingInput, OfferAvailability, OffersInput } from './_offers.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EACL,OAAO,EACP,WAAW,EACX,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE7F,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAElF,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE1E,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAE1D,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @answerable-kit/schemas — type-safe JSON-LD generators for the
3
+ * Answerable SEO toolkit. Each helper returns a `WithContext<T>`
4
+ * object from `schema-dts` (narrowed to remove the IRI-reference
5
+ * variant) that's ready to be embedded as
6
+ * `<script type="application/ld+json">{...}</script>`.
7
+ */
8
+ export const VERSION = '0.0.0';
9
+ export { article, blogPosting, } from './article.js';
10
+ export { breadcrumb } from './breadcrumb.js';
11
+ export { faqPage } from './faq-page.js';
12
+ export { howTo } from './how-to.js';
13
+ export { organization, } from './organization.js';
14
+ export { product } from './product.js';
15
+ export { softwareApplication, } from './software-application.js';
16
+ export { webSite } from './website.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EACL,OAAO,EACP,WAAW,GAIZ,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,UAAU,EAAkD,MAAM,iBAAiB,CAAC;AAE7F,OAAO,EAAE,OAAO,EAA4C,MAAM,eAAe,CAAC;AAElF,OAAO,EAAE,KAAK,EAAwC,MAAM,aAAa,CAAC;AAE1E,OAAO,EACL,YAAY,GAGb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,OAAO,EAAqB,MAAM,cAAc,CAAC;AAE1D,OAAO,EACL,mBAAmB,GAEpB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,OAAO,EAAqB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { Organization } from 'schema-dts';
2
+ import type { Schema } from './_internal.js';
3
+ export interface ContactPointInput {
4
+ readonly email?: string | undefined;
5
+ readonly telephone?: string | undefined;
6
+ /** Defaults to `"customer support"` when omitted. */
7
+ readonly contactType?: string | undefined;
8
+ }
9
+ export interface OrganizationInput {
10
+ readonly name: string;
11
+ /** Canonical home page URL. Must be an absolute http(s) URL. */
12
+ readonly url: string;
13
+ /** Absolute URL to the org's logo image. */
14
+ readonly logo?: string | undefined;
15
+ readonly description?: string | undefined;
16
+ /**
17
+ * URLs of official profiles on other authoritative sites
18
+ * (Twitter, LinkedIn, GitHub, Wikipedia, ...). Each entry must be
19
+ * an absolute http(s) URL.
20
+ */
21
+ readonly sameAs?: readonly string[] | undefined;
22
+ readonly contactPoint?: ContactPointInput | undefined;
23
+ }
24
+ /**
25
+ * Generate a fully-typed JSON-LD `Organization` object ready to be
26
+ * embedded in a `<script type="application/ld+json">` tag.
27
+ *
28
+ * @throws InvalidUrlError if `url`, `logo`, or any `sameAs` entry is
29
+ * not a valid http(s) URL.
30
+ */
31
+ export declare function organization(input: OrganizationInput): Schema<Organization>;
32
+ //# sourceMappingURL=organization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"organization.d.ts","sourceRoot":"","sources":["../src/organization.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,qDAAqD;IACrD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACvD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAAC,YAAY,CAAC,CAwB3E"}
@@ -0,0 +1,44 @@
1
+ import { parseAbsoluteUrl } from '@answerable-kit/core';
2
+ /**
3
+ * Generate a fully-typed JSON-LD `Organization` object ready to be
4
+ * embedded in a `<script type="application/ld+json">` tag.
5
+ *
6
+ * @throws InvalidUrlError if `url`, `logo`, or any `sameAs` entry is
7
+ * not a valid http(s) URL.
8
+ */
9
+ export function organization(input) {
10
+ const url = parseAbsoluteUrl(input.url);
11
+ const out = {
12
+ '@context': 'https://schema.org',
13
+ '@type': 'Organization',
14
+ name: input.name,
15
+ url,
16
+ };
17
+ if (input.logo !== undefined) {
18
+ out.logo = parseAbsoluteUrl(input.logo);
19
+ }
20
+ if (input.description !== undefined) {
21
+ out.description = input.description;
22
+ }
23
+ if (input.sameAs !== undefined && input.sameAs.length > 0) {
24
+ out.sameAs = input.sameAs.map((u) => parseAbsoluteUrl(u));
25
+ }
26
+ if (input.contactPoint !== undefined) {
27
+ out.contactPoint = buildContactPoint(input.contactPoint);
28
+ }
29
+ return out;
30
+ }
31
+ function buildContactPoint(input) {
32
+ const cp = {
33
+ '@type': 'ContactPoint',
34
+ contactType: input.contactType ?? 'customer support',
35
+ };
36
+ if (input.email !== undefined) {
37
+ cp.email = input.email;
38
+ }
39
+ if (input.telephone !== undefined) {
40
+ cp.telephone = input.telephone;
41
+ }
42
+ return cp;
43
+ }
44
+ //# sourceMappingURL=organization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"organization.js","sourceRoot":"","sources":["../src/organization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AA2BxD;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,GAAG,GAAW,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEhD,MAAM,GAAG,GAAyB;QAChC,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,cAAc;QACvB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG;KACJ,CAAC;IAEF,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,GAAG,CAAC,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACpC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1D,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACrC,GAAG,CAAC,YAAY,GAAG,iBAAiB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAwB;IACjD,MAAM,EAAE,GAAiB;QACvB,OAAO,EAAE,cAAc;QACvB,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,kBAAkB;KACrD,CAAC;IACF,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IACzB,CAAC;IACD,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAClC,EAAE,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IACjC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { Product } from 'schema-dts';
2
+ import type { Schema } from './_internal.js';
3
+ import { type AggregateRatingInput, type OffersInput } from './_offers.js';
4
+ export interface ProductInput {
5
+ readonly name: string;
6
+ /** Canonical product page URL. */
7
+ readonly url: string;
8
+ readonly description?: string | undefined;
9
+ /** Absolute URL to the primary product image. */
10
+ readonly image?: string | undefined;
11
+ /** Brand name. Emitted as `{ "@type": "Brand", "name": ... }`. */
12
+ readonly brand?: string | undefined;
13
+ /** Stock-keeping unit / unique product identifier. */
14
+ readonly sku?: string | undefined;
15
+ readonly offers?: OffersInput | undefined;
16
+ readonly aggregateRating?: AggregateRatingInput | undefined;
17
+ }
18
+ /**
19
+ * Generate a fully-typed JSON-LD `Product` object. Drives audit
20
+ * check **C6**.
21
+ *
22
+ * @throws SchemaValidationError batching every issue across the
23
+ * top-level fields, `offers`, and `aggregateRating`.
24
+ * @throws InvalidUrlError for the first malformed URL encountered
25
+ * (`url`, `image`, `offers.url`).
26
+ */
27
+ export declare function product(input: ProductInput): Schema<Product>;
28
+ //# sourceMappingURL=product.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product.d.ts","sourceRoot":"","sources":["../src/product.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAS,OAAO,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAKjB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,iDAAiD;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,sDAAsD;IACtD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,eAAe,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CA8C5D"}
@@ -0,0 +1,56 @@
1
+ import { SchemaValidationError, parseAbsoluteUrl } from '@answerable-kit/core';
2
+ import { buildAggregateRating, buildOffer, validateAggregateRating, validateOffers, } from './_offers.js';
3
+ /**
4
+ * Generate a fully-typed JSON-LD `Product` object. Drives audit
5
+ * check **C6**.
6
+ *
7
+ * @throws SchemaValidationError batching every issue across the
8
+ * top-level fields, `offers`, and `aggregateRating`.
9
+ * @throws InvalidUrlError for the first malformed URL encountered
10
+ * (`url`, `image`, `offers.url`).
11
+ */
12
+ export function product(input) {
13
+ const issues = [];
14
+ if (input.name.trim() === '') {
15
+ issues.push('name is empty');
16
+ }
17
+ if (input.offers !== undefined) {
18
+ issues.push(...validateOffers(input.offers, 'offers'));
19
+ }
20
+ if (input.aggregateRating !== undefined) {
21
+ issues.push(...validateAggregateRating(input.aggregateRating, 'aggregateRating'));
22
+ }
23
+ if (issues.length > 0) {
24
+ throw new SchemaValidationError(issues);
25
+ }
26
+ const out = {
27
+ '@context': 'https://schema.org',
28
+ '@type': 'Product',
29
+ name: input.name,
30
+ url: parseAbsoluteUrl(input.url),
31
+ };
32
+ if (input.description !== undefined) {
33
+ out.description = input.description;
34
+ }
35
+ if (input.image !== undefined) {
36
+ out.image = parseAbsoluteUrl(input.image);
37
+ }
38
+ if (input.brand !== undefined) {
39
+ const brand = {
40
+ '@type': 'Brand',
41
+ name: input.brand,
42
+ };
43
+ out.brand = brand;
44
+ }
45
+ if (input.sku !== undefined) {
46
+ out.sku = input.sku;
47
+ }
48
+ if (input.offers !== undefined) {
49
+ out.offers = buildOffer(input.offers);
50
+ }
51
+ if (input.aggregateRating !== undefined) {
52
+ out.aggregateRating = buildAggregateRating(input.aggregateRating);
53
+ }
54
+ return out;
55
+ }
56
+ //# sourceMappingURL=product.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product.js","sourceRoot":"","sources":["../src/product.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG/E,OAAO,EAGL,oBAAoB,EACpB,UAAU,EACV,uBAAuB,EACvB,cAAc,GACf,MAAM,cAAc,CAAC;AAiBtB;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CAAC,KAAmB;IACzC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,GAAG,uBAAuB,CAAC,KAAK,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACpF,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,GAAG,GAAoB;QAC3B,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC;KACjC,CAAC;IAEF,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACpC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,GAAG,CAAC,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,KAAK,GAA2B;YACpC,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE,KAAK,CAAC,KAAK;SAClB,CAAC;QACF,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IACD,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC5B,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;IACtB,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,GAAG,CAAC,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACxC,GAAG,CAAC,eAAe,GAAG,oBAAoB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { SoftwareApplication } from 'schema-dts';
2
+ import type { Schema } from './_internal.js';
3
+ import { type AggregateRatingInput, type OffersInput } from './_offers.js';
4
+ export interface SoftwareApplicationInput {
5
+ readonly name: string;
6
+ /** Canonical app page URL. */
7
+ readonly url: string;
8
+ readonly description?: string | undefined;
9
+ /**
10
+ * App category per schema.org's `ApplicationCategory`. Common values:
11
+ * `"GameApplication"`, `"BusinessApplication"`, `"DesignApplication"`,
12
+ * `"DeveloperApplication"`, `"EducationalApplication"`,
13
+ * `"UtilitiesApplication"`.
14
+ */
15
+ readonly applicationCategory: string;
16
+ /**
17
+ * Operating system support. Free-form per schema.org but conventional
18
+ * values are `"Web"`, `"iOS"`, `"Android"`, `"Windows"`, `"macOS"`,
19
+ * `"Linux"` — or a comma-separated list.
20
+ */
21
+ readonly operatingSystem?: string | undefined;
22
+ /** Absolute URL to the app's hero / screenshot image. */
23
+ readonly image?: string | undefined;
24
+ /**
25
+ * For free apps, pass `{ price: 0, priceCurrency: "USD" }` (or any
26
+ * currency). Required by Google's structured-data guidelines to
27
+ * appear as a SoftwareApplication rich result.
28
+ */
29
+ readonly offers?: OffersInput | undefined;
30
+ readonly aggregateRating?: AggregateRatingInput | undefined;
31
+ }
32
+ /**
33
+ * Generate a fully-typed JSON-LD `SoftwareApplication` object. Drives
34
+ * audit check **C2** for software products.
35
+ *
36
+ * @throws SchemaValidationError batching every issue across the
37
+ * top-level fields, `offers`, and `aggregateRating`.
38
+ * @throws InvalidUrlError for the first malformed URL encountered.
39
+ */
40
+ export declare function softwareApplication(input: SoftwareApplicationInput): Schema<SoftwareApplication>;
41
+ //# sourceMappingURL=software-application.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"software-application.d.ts","sourceRoot":"","sources":["../src/software-application.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAKjB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,eAAe,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC7D;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,MAAM,CAAC,mBAAmB,CAAC,CA2ChG"}
@@ -0,0 +1,52 @@
1
+ import { SchemaValidationError, parseAbsoluteUrl } from '@answerable-kit/core';
2
+ import { buildAggregateRating, buildOffer, validateAggregateRating, validateOffers, } from './_offers.js';
3
+ /**
4
+ * Generate a fully-typed JSON-LD `SoftwareApplication` object. Drives
5
+ * audit check **C2** for software products.
6
+ *
7
+ * @throws SchemaValidationError batching every issue across the
8
+ * top-level fields, `offers`, and `aggregateRating`.
9
+ * @throws InvalidUrlError for the first malformed URL encountered.
10
+ */
11
+ export function softwareApplication(input) {
12
+ const issues = [];
13
+ if (input.name.trim() === '') {
14
+ issues.push('name is empty');
15
+ }
16
+ if (input.applicationCategory.trim() === '') {
17
+ issues.push('applicationCategory is empty');
18
+ }
19
+ if (input.offers !== undefined) {
20
+ issues.push(...validateOffers(input.offers, 'offers'));
21
+ }
22
+ if (input.aggregateRating !== undefined) {
23
+ issues.push(...validateAggregateRating(input.aggregateRating, 'aggregateRating'));
24
+ }
25
+ if (issues.length > 0) {
26
+ throw new SchemaValidationError(issues);
27
+ }
28
+ const out = {
29
+ '@context': 'https://schema.org',
30
+ '@type': 'SoftwareApplication',
31
+ name: input.name,
32
+ url: parseAbsoluteUrl(input.url),
33
+ applicationCategory: input.applicationCategory,
34
+ };
35
+ if (input.description !== undefined) {
36
+ out.description = input.description;
37
+ }
38
+ if (input.operatingSystem !== undefined) {
39
+ out.operatingSystem = input.operatingSystem;
40
+ }
41
+ if (input.image !== undefined) {
42
+ out.image = parseAbsoluteUrl(input.image);
43
+ }
44
+ if (input.offers !== undefined) {
45
+ out.offers = buildOffer(input.offers);
46
+ }
47
+ if (input.aggregateRating !== undefined) {
48
+ out.aggregateRating = buildAggregateRating(input.aggregateRating);
49
+ }
50
+ return out;
51
+ }
52
+ //# sourceMappingURL=software-application.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"software-application.js","sourceRoot":"","sources":["../src/software-application.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG/E,OAAO,EAGL,oBAAoB,EACpB,UAAU,EACV,uBAAuB,EACvB,cAAc,GACf,MAAM,cAAc,CAAC;AA+BtB;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA+B;IACjE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,KAAK,CAAC,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,GAAG,uBAAuB,CAAC,KAAK,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACpF,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,GAAG,GAAgC;QACvC,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,qBAAqB;QAC9B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC;QAChC,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;KAC/C,CAAC;IAEF,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACpC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,CAAC;IACD,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACxC,GAAG,CAAC,eAAe,GAAG,KAAK,CAAC,eAAe,CAAC;IAC9C,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,GAAG,CAAC,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,GAAG,CAAC,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACxC,GAAG,CAAC,eAAe,GAAG,oBAAoB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { WebSite } from 'schema-dts';
2
+ import type { Schema } from './_internal.js';
3
+ export interface WebSiteInput {
4
+ readonly name: string;
5
+ /** Canonical site URL. Must be an absolute http(s) URL. */
6
+ readonly url: string;
7
+ readonly description?: string | undefined;
8
+ /**
9
+ * URL template for the sitelinks search box. Must contain
10
+ * `{search_term_string}` as the query placeholder.
11
+ *
12
+ * @example
13
+ * 'https://example.com/search?q={search_term_string}'
14
+ */
15
+ readonly searchUrlTemplate?: string | undefined;
16
+ }
17
+ /**
18
+ * Generate a fully-typed JSON-LD `WebSite` object.
19
+ *
20
+ * When `searchUrlTemplate` is supplied, a `potentialAction` of type
21
+ * `SearchAction` is emitted so search engines can render a sitelinks
22
+ * search box in results.
23
+ *
24
+ * @throws InvalidUrlError if `url` is not a valid http(s) URL.
25
+ * @throws SchemaValidationError if `searchUrlTemplate` is missing the
26
+ * `{search_term_string}` placeholder.
27
+ */
28
+ export declare function webSite(input: WebSiteInput): Schema<WebSite>;
29
+ //# sourceMappingURL=website.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"website.d.ts","sourceRoot":"","sources":["../src/website.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAoC5D"}
@@ -0,0 +1,46 @@
1
+ import { SchemaValidationError, parseAbsoluteUrl } from '@answerable-kit/core';
2
+ /**
3
+ * Generate a fully-typed JSON-LD `WebSite` object.
4
+ *
5
+ * When `searchUrlTemplate` is supplied, a `potentialAction` of type
6
+ * `SearchAction` is emitted so search engines can render a sitelinks
7
+ * search box in results.
8
+ *
9
+ * @throws InvalidUrlError if `url` is not a valid http(s) URL.
10
+ * @throws SchemaValidationError if `searchUrlTemplate` is missing the
11
+ * `{search_term_string}` placeholder.
12
+ */
13
+ export function webSite(input) {
14
+ const url = parseAbsoluteUrl(input.url);
15
+ const out = {
16
+ '@context': 'https://schema.org',
17
+ '@type': 'WebSite',
18
+ name: input.name,
19
+ url,
20
+ };
21
+ if (input.description !== undefined) {
22
+ out.description = input.description;
23
+ }
24
+ if (input.searchUrlTemplate !== undefined) {
25
+ if (!input.searchUrlTemplate.includes('{search_term_string}')) {
26
+ throw new SchemaValidationError([
27
+ 'searchUrlTemplate must contain the {search_term_string} placeholder',
28
+ ]);
29
+ }
30
+ // schema-dts removed the `query-input` field after Google deprecated the
31
+ // sitelinks search box in 2024. The field is still valid schema.org JSON-LD
32
+ // and consumed by other search engines and AI answer engines, so we keep
33
+ // emitting it. The `as unknown as` bypass is intentional and isolated.
34
+ const action = {
35
+ '@type': 'SearchAction',
36
+ target: {
37
+ '@type': 'EntryPoint',
38
+ urlTemplate: input.searchUrlTemplate,
39
+ },
40
+ 'query-input': 'required name=search_term_string',
41
+ };
42
+ out.potentialAction = action;
43
+ }
44
+ return out;
45
+ }
46
+ //# sourceMappingURL=website.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"website.js","sourceRoot":"","sources":["../src/website.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAmB/E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,OAAO,CAAC,KAAmB;IACzC,MAAM,GAAG,GAAW,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEhD,MAAM,GAAG,GAAoB;QAC3B,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG;KACJ,CAAC;IAEF,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACpC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,CAAC;IAED,IAAI,KAAK,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC;YAC9D,MAAM,IAAI,qBAAqB,CAAC;gBAC9B,qEAAqE;aACtE,CAAC,CAAC;QACL,CAAC;QACD,yEAAyE;QACzE,4EAA4E;QAC5E,yEAAyE;QACzE,uEAAuE;QACvE,MAAM,MAAM,GAAG;YACb,OAAO,EAAE,cAAc;YACvB,MAAM,EAAE;gBACN,OAAO,EAAE,YAAY;gBACrB,WAAW,EAAE,KAAK,CAAC,iBAAiB;aACrC;YACD,aAAa,EAAE,kCAAkC;SACzC,CAAC;QACX,GAAG,CAAC,eAAe,GAAG,MAAoE,CAAC;IAC7F,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@answerable-kit/schemas",
3
+ "version": "0.1.0",
4
+ "description": "Type-safe JSON-LD generators for the Answerable SEO toolkit (Organization, FAQPage, Article, ...).",
5
+ "license": "MIT",
6
+ "author": "Anuj Ojha",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/Anuj7411/answerable.git",
10
+ "directory": "packages/schemas"
11
+ },
12
+ "homepage": "https://github.com/Anuj7411/answerable/tree/main/packages/schemas#readme",
13
+ "bugs": "https://github.com/Anuj7411/answerable/issues",
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": ["dist", "README.md"],
24
+ "sideEffects": false,
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.build.json",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "typecheck": "tsc --noEmit",
33
+ "clean": "rimraf dist .tsbuildinfo"
34
+ },
35
+ "dependencies": {
36
+ "@answerable-kit/core": "workspace:*",
37
+ "schema-dts": "^1.1.5"
38
+ },
39
+ "devDependencies": {
40
+ "rimraf": "^6.0.1",
41
+ "typescript": "^5.7.3",
42
+ "vitest": "^3.0.5"
43
+ }
44
+ }