geo-semantic-layer 2.0.1

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.
@@ -0,0 +1,969 @@
1
+ import { z } from 'zod';
2
+
3
+ // src/core/schemas/organization.ts
4
+ var PostalAddressSchema = z.object({
5
+ "@type": z.literal("PostalAddress").default("PostalAddress"),
6
+ streetAddress: z.string().optional(),
7
+ addressLocality: z.string().optional(),
8
+ addressRegion: z.string().optional(),
9
+ postalCode: z.string().optional(),
10
+ addressCountry: z.string().length(2).optional()
11
+ // ISO 3166-1 alpha-2
12
+ });
13
+ var ContactPointSchema = z.object({
14
+ "@type": z.literal("ContactPoint").default("ContactPoint"),
15
+ telephone: z.string().optional(),
16
+ email: z.string().email().optional(),
17
+ contactType: z.enum(["customer service", "technical support", "sales", "billing"]).optional(),
18
+ areaServed: z.string().optional(),
19
+ availableLanguage: z.array(z.string()).optional()
20
+ });
21
+ var OrganizationSchema = z.object({
22
+ "@context": z.literal("https://schema.org").default("https://schema.org"),
23
+ "@type": z.literal("Organization").default("Organization"),
24
+ // Required fields
25
+ name: z.string().min(1, "Organization name is required"),
26
+ url: z.string().url("Must be a valid URL"),
27
+ // Recommended fields
28
+ logo: z.string().url("Logo must be a valid URL").optional(),
29
+ image: z.string().url("Image must be a valid URL").optional(),
30
+ description: z.string().min(10, "Description should be at least 10 characters").optional(),
31
+ /**
32
+ * CRITICAL for GEO: Entity disambiguation
33
+ * Should include at least one social profile or authority ID (Wikidata, Wikipedia, etc.)
34
+ */
35
+ sameAs: z.array(z.string().url()).min(1, "At least one sameAs URL is required for entity disambiguation").describe("Social profiles, Wikidata ID, Wikipedia URL for entity disambiguation"),
36
+ // Contact information
37
+ email: z.string().email().optional(),
38
+ telephone: z.string().optional(),
39
+ address: PostalAddressSchema.optional(),
40
+ contactPoint: z.union([ContactPointSchema, z.array(ContactPointSchema)]).optional(),
41
+ // Additional information
42
+ alternateName: z.string().optional(),
43
+ foundingDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Must be in YYYY-MM-DD format").optional(),
44
+ founder: z.array(z.string()).optional(),
45
+ // Relationships
46
+ parentOrganization: z.string().optional(),
47
+ subOrganization: z.array(z.string()).optional()
48
+ });
49
+ var PersonSchema = z.object({
50
+ "@context": z.literal("https://schema.org").default("https://schema.org"),
51
+ "@type": z.literal("Person").default("Person"),
52
+ // Required fields
53
+ name: z.string().min(1, "Person name is required"),
54
+ url: z.string().url("Must be a valid URL").optional(),
55
+ // Recommended fields
56
+ image: z.string().url("Image must be a valid URL").optional(),
57
+ description: z.string().min(10, "Description should be at least 10 characters").optional(),
58
+ /**
59
+ * CRITICAL for GEO: Entity disambiguation
60
+ * Should include social profiles, Wikidata ID, Wikipedia URL, etc.
61
+ */
62
+ sameAs: z.array(z.string().url()).min(1, "At least one sameAs URL is required for entity disambiguation").describe("Social profiles, Wikidata ID, Wikipedia URL for entity disambiguation"),
63
+ // Contact information
64
+ email: z.string().email().optional(),
65
+ telephone: z.string().optional(),
66
+ address: PostalAddressSchema.optional(),
67
+ // Professional information
68
+ jobTitle: z.string().optional(),
69
+ worksFor: z.object({
70
+ "@type": z.literal("Organization"),
71
+ name: z.string(),
72
+ url: z.string().url().optional()
73
+ }).optional(),
74
+ // Additional information
75
+ alternateName: z.string().optional(),
76
+ birthDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Must be in YYYY-MM-DD format").optional(),
77
+ nationality: z.string().optional(),
78
+ // Relationships
79
+ colleague: z.array(z.string()).optional(),
80
+ alumniOf: z.string().optional()
81
+ });
82
+ var OfferSchema = z.object({
83
+ "@type": z.literal("Offer").default("Offer"),
84
+ price: z.union([z.string(), z.number()]),
85
+ priceCurrency: z.string().length(3),
86
+ // ISO 4217 currency code
87
+ availability: z.string().url().optional().describe("e.g., https://schema.org/InStock"),
88
+ url: z.string().url().optional(),
89
+ priceValidUntil: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
90
+ seller: z.object({
91
+ "@type": z.literal("Organization"),
92
+ name: z.string()
93
+ }).optional()
94
+ });
95
+ var AggregateRatingSchema = z.object({
96
+ "@type": z.literal("AggregateRating").default("AggregateRating"),
97
+ ratingValue: z.union([z.string(), z.number()]),
98
+ reviewCount: z.number().int().positive(),
99
+ bestRating: z.union([z.string(), z.number()]).optional().default(5),
100
+ worstRating: z.union([z.string(), z.number()]).optional().default(1)
101
+ });
102
+ var ReviewSchema = z.object({
103
+ "@type": z.literal("Review").default("Review"),
104
+ author: z.object({
105
+ "@type": z.literal("Person"),
106
+ name: z.string()
107
+ }),
108
+ datePublished: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
109
+ reviewBody: z.string().min(10),
110
+ reviewRating: z.object({
111
+ "@type": z.literal("Rating"),
112
+ ratingValue: z.union([z.string(), z.number()]),
113
+ bestRating: z.union([z.string(), z.number()]).optional().default(5)
114
+ })
115
+ });
116
+ var ProductSchema = z.object({
117
+ "@context": z.literal("https://schema.org").default("https://schema.org"),
118
+ "@type": z.literal("Product").default("Product"),
119
+ // Required fields
120
+ name: z.string().min(1, "Product name is required"),
121
+ // Recommended fields
122
+ image: z.union([z.string().url(), z.array(z.string().url())]).optional(),
123
+ description: z.string().min(10, "Description should be at least 10 characters").optional(),
124
+ // SKU or other identifiers
125
+ sku: z.string().optional(),
126
+ gtin: z.string().optional(),
127
+ gtin8: z.string().optional(),
128
+ gtin12: z.string().optional(),
129
+ gtin13: z.string().optional(),
130
+ gtin14: z.string().optional(),
131
+ mpn: z.string().optional(),
132
+ // Brand
133
+ brand: z.object({
134
+ "@type": z.literal("Brand"),
135
+ name: z.string()
136
+ }).optional(),
137
+ // Offers (pricing)
138
+ offers: z.union([OfferSchema, z.array(OfferSchema)]).optional(),
139
+ // Ratings and Reviews
140
+ aggregateRating: AggregateRatingSchema.optional(),
141
+ review: z.union([ReviewSchema, z.array(ReviewSchema)]).optional(),
142
+ // Additional information
143
+ category: z.string().optional(),
144
+ color: z.union([z.string(), z.array(z.string())]).optional(),
145
+ material: z.string().optional(),
146
+ manufacturer: z.object({
147
+ "@type": z.literal("Organization"),
148
+ name: z.string()
149
+ }).optional()
150
+ });
151
+ var ArticleSchema = z.object({
152
+ "@context": z.literal("https://schema.org").default("https://schema.org"),
153
+ "@type": z.enum(["Article", "NewsArticle", "BlogPosting", "TechArticle"]).default("Article"),
154
+ // Required fields
155
+ headline: z.string().min(1).max(110, "Headline should be less than 110 characters for optimal SEO"),
156
+ image: z.union([z.string().url(), z.array(z.string().url())]),
157
+ // Recommended fields
158
+ author: z.union([
159
+ z.object({
160
+ "@type": z.enum(["Person", "Organization"]),
161
+ name: z.string(),
162
+ url: z.string().url().optional()
163
+ }),
164
+ z.array(
165
+ z.object({
166
+ "@type": z.enum(["Person", "Organization"]),
167
+ name: z.string(),
168
+ url: z.string().url().optional()
169
+ })
170
+ )
171
+ ]).describe("Author(s) of the article"),
172
+ datePublished: z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}Z)?$/, "Must be ISO 8601 format"),
173
+ dateModified: z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}Z)?$/, "Must be ISO 8601 format").optional(),
174
+ // Publisher (recommended for Google News)
175
+ publisher: z.object({
176
+ "@type": z.literal("Organization"),
177
+ name: z.string(),
178
+ logo: z.object({
179
+ "@type": z.literal("ImageObject"),
180
+ url: z.string().url(),
181
+ width: z.number().optional(),
182
+ height: z.number().optional()
183
+ }).optional()
184
+ }).optional(),
185
+ // Content
186
+ description: z.string().min(10).optional(),
187
+ articleBody: z.string().min(50, "Article body should be at least 50 characters").optional(),
188
+ wordCount: z.number().int().positive().optional(),
189
+ // Additional metadata
190
+ url: z.string().url().optional(),
191
+ mainEntityOfPage: z.string().url().optional(),
192
+ keywords: z.union([z.string(), z.array(z.string())]).optional(),
193
+ articleSection: z.string().optional(),
194
+ inLanguage: z.string().optional()
195
+ });
196
+ ArticleSchema.extend({
197
+ "@type": z.literal("BlogPosting").default("BlogPosting")
198
+ });
199
+ ArticleSchema.extend({
200
+ "@type": z.literal("NewsArticle").default("NewsArticle"),
201
+ dateline: z.string().optional()
202
+ });
203
+ var QuestionSchema = z.object({
204
+ "@type": z.literal("Question").default("Question"),
205
+ name: z.string().min(1, "Question text is required"),
206
+ acceptedAnswer: z.object({
207
+ "@type": z.literal("Answer").default("Answer"),
208
+ text: z.string().min(1, "Answer text is required")
209
+ })
210
+ });
211
+ var FAQPageSchema = z.object({
212
+ "@context": z.literal("https://schema.org").default("https://schema.org"),
213
+ "@type": z.literal("FAQPage").default("FAQPage"),
214
+ mainEntity: z.array(QuestionSchema).min(1, "At least one question is required for FAQ").describe("Array of questions and answers")
215
+ });
216
+ var ListItemSchema = z.object({
217
+ "@type": z.literal("ListItem").default("ListItem"),
218
+ position: z.number().int().positive(),
219
+ name: z.string().min(1),
220
+ item: z.string().url().optional()
221
+ });
222
+ var BreadcrumbListSchema = z.object({
223
+ "@context": z.literal("https://schema.org").default("https://schema.org"),
224
+ "@type": z.literal("BreadcrumbList").default("BreadcrumbList"),
225
+ itemListElement: z.array(ListItemSchema).min(2, "Breadcrumb list must have at least 2 items").describe("Ordered list of breadcrumb items")
226
+ });
227
+ var WebPageSchema = z.object({
228
+ "@context": z.literal("https://schema.org").default("https://schema.org"),
229
+ "@type": z.literal("WebPage").default("WebPage"),
230
+ name: z.string().min(1),
231
+ description: z.string().min(10).optional(),
232
+ url: z.string().url(),
233
+ // Optional fields
234
+ inLanguage: z.string().optional(),
235
+ isPartOf: z.object({
236
+ "@type": z.literal("WebSite"),
237
+ name: z.string(),
238
+ url: z.string().url()
239
+ }).optional(),
240
+ breadcrumb: z.object({
241
+ "@type": z.literal("BreadcrumbList")
242
+ }).optional(),
243
+ datePublished: z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}Z)?$/).optional(),
244
+ dateModified: z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}Z)?$/).optional(),
245
+ author: z.object({
246
+ "@type": z.enum(["Person", "Organization"]),
247
+ name: z.string()
248
+ }).optional()
249
+ });
250
+ var OpenGraphSchema = z.object({
251
+ title: z.string().min(1).max(60, "Title should be less than 60 characters for optimal display"),
252
+ description: z.string().min(1).max(160, "Description should be less than 160 characters"),
253
+ url: z.string().url(),
254
+ type: z.enum(["website", "article", "product", "book", "profile", "music", "video"]).default("website"),
255
+ image: z.string().url(),
256
+ imageAlt: z.string().optional(),
257
+ siteName: z.string().optional(),
258
+ locale: z.string().default("en_US"),
259
+ // Article-specific
260
+ publishedTime: z.string().optional(),
261
+ modifiedTime: z.string().optional(),
262
+ author: z.string().optional(),
263
+ section: z.string().optional(),
264
+ tags: z.array(z.string()).optional(),
265
+ // Product-specific
266
+ price: z.union([z.string(), z.number()]).optional(),
267
+ currency: z.string().length(3).optional()
268
+ });
269
+ var TwitterCardSchema = z.object({
270
+ card: z.enum(["summary", "summary_large_image", "app", "player"]).default("summary_large_image"),
271
+ site: z.string().optional(),
272
+ // @username
273
+ creator: z.string().optional(),
274
+ // @username
275
+ title: z.string().min(1).max(70, "Twitter title should be less than 70 characters"),
276
+ description: z.string().min(1).max(200, "Twitter description should be less than 200 characters"),
277
+ image: z.string().url(),
278
+ imageAlt: z.string().optional()
279
+ });
280
+ var GeoCoordinatesSchema = z.object({
281
+ "@type": z.literal("GeoCoordinates").default("GeoCoordinates"),
282
+ latitude: z.number().min(-90).max(90),
283
+ longitude: z.number().min(-180).max(180)
284
+ });
285
+ var OpeningHoursSchema = z.object({
286
+ "@type": z.literal("OpeningHoursSpecification").default("OpeningHoursSpecification"),
287
+ dayOfWeek: z.union([
288
+ z.enum(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]),
289
+ z.array(z.enum(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]))
290
+ ]),
291
+ opens: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Must be in HH:MM format"),
292
+ closes: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Must be in HH:MM format")
293
+ });
294
+ var LocalBusinessSchema = z.object({
295
+ "@context": z.literal("https://schema.org").default("https://schema.org"),
296
+ "@type": z.string().default("LocalBusiness"),
297
+ // Can be Restaurant, Store, etc.
298
+ // Required
299
+ name: z.string().min(1, "Business name is required"),
300
+ image: z.union([z.string().url(), z.array(z.string().url())]),
301
+ address: PostalAddressSchema,
302
+ // Recommended
303
+ "@id": z.string().url().optional(),
304
+ url: z.string().url().optional(),
305
+ telephone: z.string().optional(),
306
+ priceRange: z.string().optional(),
307
+ // e.g., "$$"
308
+ // Location
309
+ geo: GeoCoordinatesSchema.optional(),
310
+ // Opening hours
311
+ openingHoursSpecification: z.union([
312
+ OpeningHoursSchema,
313
+ z.array(OpeningHoursSchema)
314
+ ]).optional(),
315
+ // Ratings
316
+ aggregateRating: z.object({
317
+ "@type": z.literal("AggregateRating"),
318
+ ratingValue: z.union([z.string(), z.number()]),
319
+ reviewCount: z.number().int().positive()
320
+ }).optional(),
321
+ // Additional info
322
+ description: z.string().min(10).optional(),
323
+ servesCuisine: z.union([z.string(), z.array(z.string())]).optional(),
324
+ // For restaurants
325
+ menu: z.string().url().optional(),
326
+ // For restaurants
327
+ acceptsReservations: z.union([z.boolean(), z.string()]).optional(),
328
+ paymentAccepted: z.string().optional()
329
+ });
330
+ var PlaceSchema = z.object({
331
+ "@type": z.literal("Place").default("Place"),
332
+ name: z.string(),
333
+ address: z.union([
334
+ z.string(),
335
+ z.object({
336
+ "@type": z.literal("PostalAddress"),
337
+ streetAddress: z.string().optional(),
338
+ addressLocality: z.string().optional(),
339
+ addressRegion: z.string().optional(),
340
+ postalCode: z.string().optional(),
341
+ addressCountry: z.string().optional()
342
+ })
343
+ ]).optional()
344
+ });
345
+ var EventOfferSchema = z.object({
346
+ "@type": z.literal("Offer").default("Offer"),
347
+ url: z.string().url().optional(),
348
+ price: z.union([z.string(), z.number()]),
349
+ priceCurrency: z.string().length(3),
350
+ availability: z.string().url().optional(),
351
+ validFrom: z.string().optional()
352
+ });
353
+ var EventSchema = z.object({
354
+ "@context": z.literal("https://schema.org").default("https://schema.org"),
355
+ "@type": z.enum(["Event", "MusicEvent", "BusinessEvent", "SportsEvent", "TheaterEvent", "EducationEvent"]).default("Event"),
356
+ // Required
357
+ name: z.string().min(1, "Event name is required"),
358
+ startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2})?$/, "Must be ISO 8601 format"),
359
+ // Recommended
360
+ endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2})?$/, "Must be ISO 8601 format").optional(),
361
+ eventStatus: z.enum([
362
+ "https://schema.org/EventScheduled",
363
+ "https://schema.org/EventCancelled",
364
+ "https://schema.org/EventMovedOnline",
365
+ "https://schema.org/EventPostponed",
366
+ "https://schema.org/EventRescheduled"
367
+ ]).optional(),
368
+ eventAttendanceMode: z.enum([
369
+ "https://schema.org/OfflineEventAttendanceMode",
370
+ "https://schema.org/OnlineEventAttendanceMode",
371
+ "https://schema.org/MixedEventAttendanceMode"
372
+ ]).optional(),
373
+ location: z.union([PlaceSchema, z.string().url()]).optional(),
374
+ // Can be Place or VirtualLocation URL
375
+ image: z.union([z.string().url(), z.array(z.string().url())]).optional(),
376
+ description: z.string().min(10).optional(),
377
+ // Organizer
378
+ organizer: z.object({
379
+ "@type": z.enum(["Organization", "Person"]),
380
+ name: z.string(),
381
+ url: z.string().url().optional()
382
+ }).optional(),
383
+ // Performer
384
+ performer: z.union([
385
+ z.object({
386
+ "@type": z.enum(["Person", "PerformingGroup"]),
387
+ name: z.string()
388
+ }),
389
+ z.array(z.object({
390
+ "@type": z.enum(["Person", "PerformingGroup"]),
391
+ name: z.string()
392
+ }))
393
+ ]).optional(),
394
+ // Offers (tickets)
395
+ offers: z.union([EventOfferSchema, z.array(EventOfferSchema)]).optional(),
396
+ // Additional
397
+ url: z.string().url().optional()
398
+ });
399
+
400
+ // src/core/geo/processor.ts
401
+ var GeoProcessor = class {
402
+ /**
403
+ * Optimizes schema density based on configuration.
404
+ * Removes nulls, undefined, and non-essential fields for 'high' density (AI-focused).
405
+ */
406
+ static optimizeDensity(schema, density = "low") {
407
+ if (density === "low") {
408
+ return this.pruneEmpty(schema);
409
+ }
410
+ const optimized = this.pruneEmpty(schema);
411
+ if (density === "high") {
412
+ const aiEssentialKeys = [
413
+ "@context",
414
+ "@type",
415
+ "name",
416
+ "headline",
417
+ "description",
418
+ "url",
419
+ "sameAs",
420
+ "author",
421
+ "datePublished",
422
+ "publisher",
423
+ "itemListElement",
424
+ "item",
425
+ "position"
426
+ ];
427
+ return this.filterRecursive(optimized, (key) => aiEssentialKeys.includes(key));
428
+ }
429
+ return optimized;
430
+ }
431
+ /**
432
+ * Recursively removes null, undefined, and empty strings/arrays
433
+ */
434
+ static pruneEmpty(obj) {
435
+ if (Array.isArray(obj)) {
436
+ return obj.map((v) => this.pruneEmpty(v)).filter((v) => v !== null && v !== void 0 && v !== "");
437
+ }
438
+ if (obj !== null && typeof obj === "object") {
439
+ return Object.entries(obj).reduce((acc, [key, value]) => {
440
+ const pruned = this.pruneEmpty(value);
441
+ if (pruned !== null && pruned !== void 0 && pruned !== "" && (!Array.isArray(pruned) || pruned.length > 0) && (typeof pruned !== "object" || Object.keys(pruned).length > 0)) {
442
+ acc[key] = pruned;
443
+ }
444
+ return acc;
445
+ }, {});
446
+ }
447
+ return obj;
448
+ }
449
+ /**
450
+ * Filter object keys recursively based on a predicate
451
+ */
452
+ static filterRecursive(obj, predicate) {
453
+ if (Array.isArray(obj)) {
454
+ return obj.map((v) => this.filterRecursive(v, predicate));
455
+ }
456
+ if (obj !== null && typeof obj === "object") {
457
+ return Object.entries(obj).reduce((acc, [key, value]) => {
458
+ if (key === "@type" || key === "@context" || predicate(key)) {
459
+ acc[key] = this.filterRecursive(value, predicate);
460
+ }
461
+ return acc;
462
+ }, {});
463
+ }
464
+ return obj;
465
+ }
466
+ /**
467
+ * Validates authority links to prevent hallucinations.
468
+ * Checks if sameAs links match known authoritative patterns.
469
+ */
470
+ static validateAuthorities(sameAs = []) {
471
+ const authorityPatterns = [
472
+ /wikipedia\.org/,
473
+ /wikidata\.org/,
474
+ /linkedin\.com/,
475
+ /twitter\.com/,
476
+ /x\.com/,
477
+ /facebook\.com/,
478
+ /instagram\.com/,
479
+ /github\.com/,
480
+ /crunchbase\.com/
481
+ ];
482
+ const valid = [];
483
+ const warnings = [];
484
+ sameAs.forEach((url) => {
485
+ try {
486
+ const parsed = new URL(url);
487
+ const isAuthority = authorityPatterns.some((pattern) => pattern.test(parsed.hostname));
488
+ if (isAuthority) {
489
+ valid.push(url);
490
+ } else {
491
+ warnings.push(`Low authority or unknown source: ${url}`);
492
+ }
493
+ } catch (e) {
494
+ warnings.push(`Invalid URL format: ${url}`);
495
+ }
496
+ });
497
+ return { valid, warnings };
498
+ }
499
+ };
500
+
501
+ // src/core/generators/identity.ts
502
+ function generateIdentitySchema(config) {
503
+ if (config.type === "Organization") {
504
+ const orgConfig = {
505
+ name: config.name,
506
+ ...config.alternateName && { alternateName: config.alternateName },
507
+ ...config.url && { url: config.url },
508
+ ...config.logoUrl && { logo: config.logoUrl },
509
+ ...config.description && { description: config.description },
510
+ ...config.sameAs && { sameAs: config.sameAs },
511
+ ...config.email && { email: config.email },
512
+ ...config.telephone && { telephone: config.telephone },
513
+ ...config.address && { address: config.address },
514
+ ...config.contactPoint && { contactPoint: config.contactPoint },
515
+ ...config.foundingDate && { foundingDate: config.foundingDate },
516
+ ...config.founder && {
517
+ founder: Array.isArray(config.founder) ? config.founder : [config.founder]
518
+ },
519
+ ...config.geo && { geo: config.geo }
520
+ };
521
+ return generateOrganizationSchema(orgConfig);
522
+ } else {
523
+ const personConfig = {
524
+ name: config.name,
525
+ ...config.url && { url: config.url },
526
+ ...config.imageUrl && { image: config.imageUrl },
527
+ ...config.description && { description: config.description },
528
+ ...config.jobTitle && { jobTitle: config.jobTitle },
529
+ ...config.sameAs && { sameAs: config.sameAs },
530
+ ...config.email && { email: config.email },
531
+ ...config.telephone && { telephone: config.telephone },
532
+ ...config.address && { address: config.address },
533
+ ...config.worksFor && { worksFor: config.worksFor },
534
+ ...config.geo && { geo: config.geo }
535
+ };
536
+ return generatePersonSchema(personConfig);
537
+ }
538
+ }
539
+ function generateOrganizationSchema(config) {
540
+ if (config.geo?.enableHallucinationGuard && config.sameAs) {
541
+ const { warnings } = GeoProcessor.validateAuthorities(config.sameAs);
542
+ if (warnings.length > 0) {
543
+ console.warn("[SemanticLayer:GEO] Authority validation warnings:", warnings);
544
+ }
545
+ }
546
+ const schema = {
547
+ "@context": "https://schema.org",
548
+ "@type": "Organization",
549
+ name: config.name,
550
+ ...config.alternateName && { alternateName: config.alternateName },
551
+ ...config.url && { url: config.url },
552
+ ...config.description && { description: config.description },
553
+ ...config.logo && { logo: config.logo },
554
+ ...config.sameAs && config.sameAs.length > 0 && { sameAs: config.sameAs },
555
+ ...config.email && { email: config.email },
556
+ ...config.telephone && { telephone: config.telephone },
557
+ ...config.address && {
558
+ address: {
559
+ "@type": "PostalAddress",
560
+ ...config.address
561
+ }
562
+ },
563
+ ...config.contactPoint && {
564
+ contactPoint: {
565
+ "@type": "ContactPoint",
566
+ ...config.contactPoint
567
+ }
568
+ },
569
+ ...config.foundingDate && { foundingDate: config.foundingDate },
570
+ ...config.founder && { founder: Array.isArray(config.founder) ? config.founder : [config.founder] },
571
+ ...config.numberOfEmployees && { numberOfEmployees: config.numberOfEmployees }
572
+ };
573
+ const parsed = OrganizationSchema.parse(schema);
574
+ if (config.geo) {
575
+ return GeoProcessor.optimizeDensity(parsed, config.geo.contentDensity);
576
+ }
577
+ return parsed;
578
+ }
579
+ function generatePersonSchema(config) {
580
+ if (config.geo?.enableHallucinationGuard && config.sameAs) {
581
+ const { warnings } = GeoProcessor.validateAuthorities(config.sameAs);
582
+ if (warnings.length > 0) {
583
+ console.warn("[SemanticLayer:GEO] Authority validation warnings:", warnings);
584
+ }
585
+ }
586
+ const schema = {
587
+ "@context": "https://schema.org",
588
+ "@type": "Person",
589
+ name: config.name,
590
+ ...config.url && { url: config.url },
591
+ ...config.description && { description: config.description },
592
+ ...config.image && { image: config.image },
593
+ ...config.sameAs && config.sameAs.length > 0 && { sameAs: config.sameAs },
594
+ ...config.email && { email: config.email },
595
+ ...config.telephone && { telephone: config.telephone },
596
+ ...config.address && {
597
+ address: {
598
+ "@type": "PostalAddress",
599
+ ...config.address
600
+ }
601
+ },
602
+ ...config.jobTitle && { jobTitle: config.jobTitle },
603
+ ...config.worksFor && {
604
+ worksFor: typeof config.worksFor === "string" ? { "@type": "Organization", name: config.worksFor } : config.worksFor
605
+ },
606
+ ...config.birthDate && { birthDate: config.birthDate }
607
+ };
608
+ const parsed = PersonSchema.parse(schema);
609
+ if (config.geo) {
610
+ return GeoProcessor.optimizeDensity(parsed, config.geo.contentDensity);
611
+ }
612
+ return parsed;
613
+ }
614
+ function serializeSchema(schema) {
615
+ return JSON.stringify(schema, null, 2);
616
+ }
617
+ function generateScriptTag(schema) {
618
+ const serialized = serializeSchema(schema);
619
+ return `<script type="application/ld+json">
620
+ ${serialized}
621
+ </script>`;
622
+ }
623
+
624
+ // src/core/generators/product.ts
625
+ function generateProductSchema(config) {
626
+ const schema = {
627
+ "@context": "https://schema.org",
628
+ "@type": "Product",
629
+ name: config.name,
630
+ ...config.description && { description: config.description },
631
+ ...config.image && { image: config.image },
632
+ ...config.sku && { sku: config.sku },
633
+ ...config.gtin && { gtin: config.gtin },
634
+ ...config.brand && {
635
+ brand: {
636
+ "@type": "Brand",
637
+ name: config.brand
638
+ }
639
+ },
640
+ ...config.offers && {
641
+ offers: Array.isArray(config.offers) ? config.offers.map((offer) => convertOffer(offer)) : convertOffer(config.offers)
642
+ },
643
+ ...config.aggregateRating && {
644
+ aggregateRating: {
645
+ "@type": "AggregateRating",
646
+ ...config.aggregateRating
647
+ }
648
+ },
649
+ ...config.category && { category: config.category },
650
+ ...config.color && { color: config.color }
651
+ };
652
+ return ProductSchema.parse(schema);
653
+ }
654
+ function convertOffer(config) {
655
+ return {
656
+ "@type": "Offer",
657
+ price: config.price,
658
+ priceCurrency: config.priceCurrency,
659
+ ...config.availability && { availability: config.availability },
660
+ ...config.url && { url: config.url },
661
+ ...config.priceValidUntil && { priceValidUntil: config.priceValidUntil },
662
+ ...config.seller && {
663
+ seller: {
664
+ "@type": "Organization",
665
+ name: config.seller.name
666
+ }
667
+ }
668
+ };
669
+ }
670
+
671
+ // src/core/generators/article.ts
672
+ function generateArticleSchema(config) {
673
+ const schema = {
674
+ "@context": "https://schema.org",
675
+ "@type": "Article",
676
+ headline: config.headline,
677
+ image: config.image,
678
+ author: convertAuthor(config.author),
679
+ datePublished: config.datePublished,
680
+ ...config.dateModified && { dateModified: config.dateModified },
681
+ ...config.description && { description: config.description },
682
+ ...config.articleBody && { articleBody: config.articleBody },
683
+ ...config.publisher && {
684
+ publisher: {
685
+ "@type": "Organization",
686
+ name: config.publisher.name,
687
+ ...config.publisher.logo && {
688
+ logo: {
689
+ "@type": "ImageObject",
690
+ url: config.publisher.logo
691
+ }
692
+ }
693
+ }
694
+ },
695
+ ...config.url && { url: config.url },
696
+ ...config.keywords && { keywords: config.keywords },
697
+ ...config.articleSection && { articleSection: config.articleSection }
698
+ };
699
+ const parsed = ArticleSchema.parse(schema);
700
+ if (config.geo) {
701
+ return GeoProcessor.optimizeDensity(parsed, config.geo.contentDensity);
702
+ }
703
+ return parsed;
704
+ }
705
+ function convertAuthor(author) {
706
+ if (typeof author === "string") {
707
+ return {
708
+ "@type": "Person",
709
+ name: author
710
+ };
711
+ }
712
+ if (Array.isArray(author)) {
713
+ return author.map((a) => ({
714
+ "@type": "Person",
715
+ name: a.name,
716
+ ...a.url && { url: a.url }
717
+ }));
718
+ }
719
+ return {
720
+ "@type": "Person",
721
+ name: author.name,
722
+ ...author.url && { url: author.url }
723
+ };
724
+ }
725
+
726
+ // src/core/generators/faq.ts
727
+ function generateFAQSchema(config) {
728
+ const schema = {
729
+ "@context": "https://schema.org",
730
+ "@type": "FAQPage",
731
+ mainEntity: config.questions.map((q) => ({
732
+ "@type": "Question",
733
+ name: q.question,
734
+ acceptedAnswer: {
735
+ "@type": "Answer",
736
+ text: q.answer
737
+ }
738
+ }))
739
+ };
740
+ return FAQPageSchema.parse(schema);
741
+ }
742
+
743
+ // src/core/generators/breadcrumb.ts
744
+ function generateBreadcrumbSchema(config) {
745
+ const schema = {
746
+ "@context": "https://schema.org",
747
+ "@type": "BreadcrumbList",
748
+ itemListElement: config.items.map((item, index) => ({
749
+ "@type": "ListItem",
750
+ position: index + 1,
751
+ name: item.name,
752
+ ...item.url && { item: item.url }
753
+ }))
754
+ };
755
+ return BreadcrumbListSchema.parse(schema);
756
+ }
757
+
758
+ // src/core/generators/opengraph.ts
759
+ function generateOpenGraphTags(config) {
760
+ const validated = OpenGraphSchema.parse(config);
761
+ const tags = [
762
+ { property: "og:title", content: validated.title },
763
+ { property: "og:description", content: validated.description },
764
+ { property: "og:url", content: validated.url },
765
+ { property: "og:type", content: validated.type },
766
+ { property: "og:image", content: validated.image },
767
+ { property: "og:locale", content: validated.locale }
768
+ ];
769
+ if (validated.imageAlt) {
770
+ tags.push({ property: "og:image:alt", content: validated.imageAlt });
771
+ }
772
+ if (validated.siteName) {
773
+ tags.push({ property: "og:site_name", content: validated.siteName });
774
+ }
775
+ if (validated.type === "article") {
776
+ if (validated.publishedTime) {
777
+ tags.push({ property: "article:published_time", content: validated.publishedTime });
778
+ }
779
+ if (validated.modifiedTime) {
780
+ tags.push({ property: "article:modified_time", content: validated.modifiedTime });
781
+ }
782
+ if (validated.author) {
783
+ tags.push({ property: "article:author", content: validated.author });
784
+ }
785
+ if (validated.section) {
786
+ tags.push({ property: "article:section", content: validated.section });
787
+ }
788
+ if (validated.tags) {
789
+ validated.tags.forEach((tag) => {
790
+ tags.push({ property: "article:tag", content: tag });
791
+ });
792
+ }
793
+ }
794
+ if (validated.type === "product") {
795
+ if (validated.price) {
796
+ tags.push({ property: "product:price:amount", content: String(validated.price) });
797
+ }
798
+ if (validated.currency) {
799
+ tags.push({ property: "product:price:currency", content: validated.currency });
800
+ }
801
+ }
802
+ return tags;
803
+ }
804
+ function generateTwitterCardTags(config) {
805
+ const validated = TwitterCardSchema.parse(config);
806
+ const tags = [
807
+ { name: "twitter:card", content: validated.card },
808
+ { name: "twitter:title", content: validated.title },
809
+ { name: "twitter:description", content: validated.description },
810
+ { name: "twitter:image", content: validated.image }
811
+ ];
812
+ if (validated.site) {
813
+ tags.push({ name: "twitter:site", content: validated.site });
814
+ }
815
+ if (validated.creator) {
816
+ tags.push({ name: "twitter:creator", content: validated.creator });
817
+ }
818
+ if (validated.imageAlt) {
819
+ tags.push({ name: "twitter:image:alt", content: validated.imageAlt });
820
+ }
821
+ return tags;
822
+ }
823
+ function generateSocialMetaTags(config) {
824
+ const openGraph = generateOpenGraphTags({
825
+ title: config.title,
826
+ description: config.description,
827
+ url: config.url,
828
+ image: config.image,
829
+ ...config.imageAlt && { imageAlt: config.imageAlt },
830
+ ...config.type && { type: config.type },
831
+ ...config.siteName && { siteName: config.siteName }
832
+ });
833
+ const twitter = generateTwitterCardTags({
834
+ title: config.title,
835
+ description: config.description,
836
+ image: config.image,
837
+ ...config.imageAlt && { imageAlt: config.imageAlt },
838
+ ...config.twitterSite && { site: config.twitterSite },
839
+ ...config.twitterCreator && { creator: config.twitterCreator },
840
+ ...config.twitterCard && { card: config.twitterCard }
841
+ });
842
+ return { openGraph, twitter };
843
+ }
844
+ function metaTagsToHTML(tags) {
845
+ return tags.map((tag) => {
846
+ const attr = tag.property ? `property="${tag.property}"` : `name="${tag.name}"`;
847
+ return `<meta ${attr} content="${tag.content}" />`;
848
+ }).join("\n");
849
+ }
850
+
851
+ // src/core/generators/localbusiness.ts
852
+ function generateLocalBusinessSchema(config) {
853
+ const schema = {
854
+ "@context": "https://schema.org",
855
+ "@type": config.type || "LocalBusiness",
856
+ name: config.name,
857
+ image: config.image,
858
+ address: {
859
+ "@type": "PostalAddress",
860
+ ...config.address
861
+ },
862
+ ...config.url && { url: config.url },
863
+ ...config.telephone && { telephone: config.telephone },
864
+ ...config.priceRange && { priceRange: config.priceRange },
865
+ ...config.geo && {
866
+ geo: {
867
+ "@type": "GeoCoordinates",
868
+ latitude: config.geo.latitude,
869
+ longitude: config.geo.longitude
870
+ }
871
+ },
872
+ ...config.openingHours && {
873
+ openingHoursSpecification: Array.isArray(config.openingHours) ? config.openingHours.map((hours) => ({
874
+ "@type": "OpeningHoursSpecification",
875
+ ...hours
876
+ })) : {
877
+ "@type": "OpeningHoursSpecification",
878
+ ...config.openingHours
879
+ }
880
+ },
881
+ ...config.description && { description: config.description },
882
+ ...config.aggregateRating && {
883
+ aggregateRating: {
884
+ "@type": "AggregateRating",
885
+ ...config.aggregateRating
886
+ }
887
+ },
888
+ ...config.servesCuisine && { servesCuisine: config.servesCuisine },
889
+ ...config.menu && { menu: config.menu },
890
+ ...config.acceptsReservations !== void 0 && { acceptsReservations: config.acceptsReservations },
891
+ ...config.paymentAccepted && { paymentAccepted: config.paymentAccepted }
892
+ };
893
+ return LocalBusinessSchema.parse(schema);
894
+ }
895
+
896
+ // src/core/generators/event.ts
897
+ function generateEventSchema(config) {
898
+ const schema = {
899
+ "@context": "https://schema.org",
900
+ "@type": config.type || "Event",
901
+ name: config.name,
902
+ startDate: config.startDate,
903
+ ...config.endDate && { endDate: config.endDate },
904
+ ...config.eventStatus && { eventStatus: `https://schema.org/${config.eventStatus}` },
905
+ ...config.eventAttendanceMode && { eventAttendanceMode: `https://schema.org/${config.eventAttendanceMode}` },
906
+ ...config.location && {
907
+ location: typeof config.location === "string" ? config.location : {
908
+ "@type": "Place",
909
+ name: config.location.name,
910
+ ...config.location.address && {
911
+ address: typeof config.location.address === "string" ? config.location.address : {
912
+ "@type": "PostalAddress",
913
+ ...config.location.address
914
+ }
915
+ }
916
+ }
917
+ },
918
+ ...config.image && { image: config.image },
919
+ ...config.description && { description: config.description },
920
+ ...config.url && { url: config.url },
921
+ ...config.organizer && {
922
+ organizer: {
923
+ "@type": config.organizer.type,
924
+ name: config.organizer.name,
925
+ ...config.organizer.url && { url: config.organizer.url }
926
+ }
927
+ },
928
+ ...config.performer && {
929
+ performer: Array.isArray(config.performer) ? config.performer.map((p) => ({
930
+ "@type": p.type,
931
+ name: p.name
932
+ })) : {
933
+ "@type": config.performer.type,
934
+ name: config.performer.name
935
+ }
936
+ },
937
+ ...config.offers && {
938
+ offers: Array.isArray(config.offers) ? config.offers.map((offer) => ({
939
+ "@type": "Offer",
940
+ ...offer,
941
+ ...offer.availability && { availability: offer.availability }
942
+ })) : {
943
+ "@type": "Offer",
944
+ ...config.offers,
945
+ ...config.offers.availability && { availability: config.offers.availability }
946
+ }
947
+ }
948
+ };
949
+ return EventSchema.parse(schema);
950
+ }
951
+
952
+ // src/core/generators/webpage.ts
953
+ function generateWebPageSchema(config) {
954
+ const schema = {
955
+ "@context": "https://schema.org",
956
+ "@type": "WebPage",
957
+ name: config.name,
958
+ description: config.description,
959
+ url: config.url,
960
+ ...config.inLanguage && { inLanguage: config.inLanguage },
961
+ ...config.isPartOf && { isPartOf: config.isPartOf },
962
+ ...config.breadcrumb && { breadcrumb: config.breadcrumb }
963
+ };
964
+ return WebPageSchema.parse(schema);
965
+ }
966
+
967
+ export { generateArticleSchema, generateBreadcrumbSchema, generateEventSchema, generateFAQSchema, generateIdentitySchema, generateLocalBusinessSchema, generateOpenGraphTags, generateOrganizationSchema, generatePersonSchema, generateProductSchema, generateScriptTag, generateSocialMetaTags, generateTwitterCardTags, generateWebPageSchema, metaTagsToHTML, serializeSchema };
968
+ //# sourceMappingURL=index.js.map
969
+ //# sourceMappingURL=index.js.map