cms-renderer 0.0.0 → 0.1.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.
- package/dist/chunk-HVKFEZBT.js +116 -0
- package/dist/chunk-HVKFEZBT.js.map +1 -0
- package/dist/chunk-JHKDRASN.js +39 -0
- package/dist/chunk-JHKDRASN.js.map +1 -0
- package/dist/chunk-RPM73PQZ.js +17 -0
- package/dist/chunk-RPM73PQZ.js.map +1 -0
- package/dist/lib/block-renderer.d.ts +32 -0
- package/dist/lib/block-renderer.js +7 -0
- package/dist/lib/block-renderer.js.map +1 -0
- package/dist/lib/cms-api.d.ts +25 -0
- package/dist/lib/cms-api.js +7 -0
- package/dist/lib/cms-api.js.map +1 -0
- package/dist/lib/data-utils.d.ts +218 -0
- package/dist/lib/data-utils.js +247 -0
- package/dist/lib/data-utils.js.map +1 -0
- package/dist/lib/image/lazy-load.d.ts +75 -0
- package/dist/lib/image/lazy-load.js +83 -0
- package/dist/lib/image/lazy-load.js.map +1 -0
- package/dist/lib/markdown-utils.d.ts +172 -0
- package/dist/lib/markdown-utils.js +137 -0
- package/dist/lib/markdown-utils.js.map +1 -0
- package/dist/lib/renderer.d.ts +40 -0
- package/dist/lib/renderer.js +371 -0
- package/dist/lib/renderer.js.map +1 -0
- package/{lib/result.ts → dist/lib/result.d.ts} +32 -146
- package/dist/lib/result.js +37 -0
- package/dist/lib/result.js.map +1 -0
- package/dist/lib/schema.d.ts +15 -0
- package/dist/lib/schema.js +35 -0
- package/dist/lib/schema.js.map +1 -0
- package/{lib/trpc.ts → dist/lib/trpc.d.ts} +6 -4
- package/dist/lib/trpc.js +7 -0
- package/dist/lib/trpc.js.map +1 -0
- package/dist/lib/types.d.ts +163 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +50 -11
- package/.turbo/turbo-check-types.log +0 -2
- package/lib/__tests__/enrich-block-images.test.ts +0 -394
- package/lib/block-renderer.tsx +0 -60
- package/lib/cms-api.ts +0 -86
- package/lib/data-utils.ts +0 -572
- package/lib/image/lazy-load.ts +0 -209
- package/lib/markdown-utils.ts +0 -368
- package/lib/renderer.tsx +0 -189
- package/lib/schema.ts +0 -74
- package/lib/types.ts +0 -201
- package/next.config.ts +0 -39
- package/postcss.config.mjs +0 -5
- package/tsconfig.json +0 -12
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../packages/cms-schema/src/blocks/article.ts","../../../../packages/cms-schema/src/validation/image.ts","../../../../packages/cms-schema/src/blocks/schemas/article-block.ts","../../../../packages/cms-schema/src/blocks/schemas/cta-block.ts","../../../../packages/cms-schema/src/blocks/schemas/features-block.ts","../../../../packages/cms-schema/src/blocks/schemas/hero-block.ts","../../../../packages/cms-schema/src/fields/complex/media.ts","../../../../packages/cms-schema/src/blocks/schemas/logo-trust-block.ts","../../../../packages/cms-schema/src/blocks/registry.ts","../../lib/renderer.tsx"],"sourcesContent":["/**\n * Article block normalization helpers shared across CMS and website apps.\n */\n\nexport interface ArticleContent {\n headline: string;\n author?: string;\n publishedAt?: string;\n body: string;\n tags?: readonly string[];\n status?: string;\n}\n\nexport interface ArticleRow {\n id: string;\n published_content: unknown;\n}\n\nexport interface NormalizedArticleRow extends ArticleContent {\n id: string;\n tags: string[];\n status: string;\n}\n\n/**\n * Normalize article content coming from Supabase blocks.\n * Returns null when required fields are missing or invalid.\n */\nexport function normalizeArticleContent(payload: unknown): ArticleContent | null {\n if (!payload || typeof payload !== 'object') {\n return null;\n }\n\n const record = payload as Record<string, unknown>;\n const headline = typeof record.headline === 'string' ? record.headline : null;\n const body = typeof record.body === 'string' ? record.body : null;\n\n if (!headline || !body) {\n return null;\n }\n\n const author = typeof record.author === 'string' ? record.author : undefined;\n const publishedAt = typeof record.publishedAt === 'string' ? record.publishedAt : undefined;\n const tags = Array.isArray(record.tags) ? record.tags.map((tag) => String(tag)) : undefined;\n const statusRaw = typeof record.status === 'string' ? record.status.trim() : undefined;\n\n return {\n headline,\n body,\n author,\n publishedAt,\n tags,\n status: statusRaw || undefined,\n };\n}\n\n/**\n * Normalize an article block row, applying defaults for tags/status.\n */\nexport function normalizeArticleRow(row: ArticleRow): NormalizedArticleRow | null {\n const article = normalizeArticleContent(row.published_content);\n if (!article) {\n return null;\n }\n\n return {\n ...article,\n id: row.id,\n tags: article.tags ? [...article.tags] : [],\n status: article.status ?? 'published',\n };\n}\n\n/**\n * Ensure the article is published.\n */\nexport function isArticlePublished(article: ArticleContent): boolean {\n const now = new Date();\n const publishedAt = article.publishedAt ? new Date(article.publishedAt) : null;\n\n return (\n article.status === 'published' &&\n publishedAt !== null &&\n !Number.isNaN(publishedAt.getTime()) &&\n publishedAt <= now\n );\n}\n","/**\n * Image Validation Utilities\n *\n * Provides validation for:\n * - File type (MIME type checking)\n * - File size (configurable limits)\n * - Image dimensions (width/height constraints)\n */\n\nimport { z } from 'zod';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nexport const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'] as const;\nexport type ImageMimeType = (typeof ALLOWED_MIME_TYPES)[number];\n\nexport const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB\nexport const MIN_FILE_SIZE = 1024; // 1KB (avoid empty/corrupt files)\n\nexport const MAX_DIMENSION = 8192; // 8K resolution\nexport const MIN_DIMENSION = 10; // Minimum useful size\n\n// =============================================================================\n// Zod Schemas\n// =============================================================================\n\nexport const MimeTypeSchema = z.enum(ALLOWED_MIME_TYPES);\n\nexport const FileSizeSchema = z\n .number()\n .int()\n .min(MIN_FILE_SIZE, `File too small. Minimum: ${MIN_FILE_SIZE} bytes`)\n .max(MAX_FILE_SIZE, `File too large. Maximum: ${MAX_FILE_SIZE / 1024 / 1024}MB`);\n\nexport const DimensionSchema = z\n .number()\n .int()\n .min(MIN_DIMENSION, `Dimension too small. Minimum: ${MIN_DIMENSION}px`)\n .max(MAX_DIMENSION, `Dimension too large. Maximum: ${MAX_DIMENSION}px`);\n\n/**\n * Schema for upload request validation.\n */\nexport const UploadRequestSchema = z.object({\n filename: z\n .string()\n .min(1, 'Filename is required')\n .max(255, 'Filename too long')\n .regex(/^[^<>:\"/\\\\|?*]+$/, 'Filename contains invalid characters'),\n mimeType: MimeTypeSchema,\n fileSize: FileSizeSchema,\n});\n\nexport type UploadRequest = z.infer<typeof UploadRequestSchema>;\n\n/**\n * Schema for confirming upload with dimensions.\n * assetId is optional - if not provided, the server will generate a UUID.\n */\nexport const ConfirmUploadSchema = z.object({\n assetId: z.uuid().optional(),\n width: DimensionSchema,\n height: DimensionSchema,\n});\n\nexport type ConfirmUpload = z.infer<typeof ConfirmUploadSchema>;\n\n// =============================================================================\n// Validation Functions\n// =============================================================================\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/**\n * Validate file type by MIME type.\n */\nexport function validateMimeType(mimeType: string): ValidationResult {\n const result = MimeTypeSchema.safeParse(mimeType);\n if (result.success) {\n return { valid: true };\n }\n return {\n valid: false,\n error: `File type not supported. Use JPEG, PNG, or WebP.`,\n };\n}\n\n/**\n * Validate file size.\n */\nexport function validateFileSize(size: number): ValidationResult {\n if (size < MIN_FILE_SIZE) {\n return {\n valid: false,\n error: `File appears to be empty or corrupt.`,\n };\n }\n if (size > MAX_FILE_SIZE) {\n const sizeMB = (size / 1024 / 1024).toFixed(1);\n return {\n valid: false,\n error: `File too large (${sizeMB}MB). Maximum: 10 MB.`,\n };\n }\n return { valid: true };\n}\n\n/**\n * Validate image dimensions.\n */\nexport function validateDimensions(width: number, height: number): ValidationResult {\n if (width < MIN_DIMENSION || height < MIN_DIMENSION) {\n return {\n valid: false,\n error: `Image too small. Minimum: ${MIN_DIMENSION}x${MIN_DIMENSION}px.`,\n };\n }\n if (width > MAX_DIMENSION || height > MAX_DIMENSION) {\n return {\n valid: false,\n error: `Image too large. Maximum: ${MAX_DIMENSION}x${MAX_DIMENSION}px.`,\n };\n }\n return { valid: true };\n}\n\n/**\n * Validate complete upload request.\n */\nexport function validateUploadRequest(\n request: unknown\n): { valid: true; data: UploadRequest } | { valid: false; error: string } {\n const result = UploadRequestSchema.safeParse(request);\n if (result.success) {\n return { valid: true, data: result.data };\n }\n const firstIssue = result.error.issues[0];\n return {\n valid: false,\n error: firstIssue?.message ?? 'Invalid upload request',\n };\n}\n\n// =============================================================================\n// File Extension Utilities\n// =============================================================================\n\nconst MIME_TO_EXT: Record<ImageMimeType, string> = {\n 'image/jpeg': 'jpg',\n 'image/png': 'png',\n 'image/webp': 'webp',\n};\n\nconst EXT_TO_MIME: Record<string, ImageMimeType> = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n webp: 'image/webp',\n};\n\n/**\n * Get file extension from MIME type.\n */\nexport function getExtensionFromMime(mimeType: ImageMimeType | string): string {\n return MIME_TO_EXT[mimeType as ImageMimeType] ?? 'bin';\n}\n\n/**\n * Get MIME type from file extension.\n */\nexport function getMimeFromExtension(ext: string): ImageMimeType | undefined {\n return EXT_TO_MIME[ext.toLowerCase()];\n}\n\n/**\n * Extract extension from filename.\n */\nexport function getExtensionFromFilename(filename: string): string {\n const parts = filename.split('.');\n return parts.pop()?.toLowerCase?.() ?? '';\n}\n\n/**\n * Sanitize filename for storage.\n * Removes special characters, preserves extension.\n */\nexport function sanitizeFilename(filename: string): string {\n // Get extension\n const ext = getExtensionFromFilename(filename);\n\n // Get base name without extension\n const base = filename.slice(0, filename.length - ext.length - 1);\n\n // Sanitize: lowercase, replace spaces and special chars\n const sanitized = base\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 50); // Limit length\n\n return ext ? `${sanitized}.${ext}` : sanitized;\n}\n","/**\n * ArticleBlock Schema.\n *\n * Defines the structure and validation for ArticleBlock content.\n *\n * ArticleBlock represents a block of content focused on articles,\n * including fields such as headline, author, publication date, body content, tags, and status.\n *\n * Includes:\n * - Zod schema for validation\n * - TypeScript type inference\n * - Default values\n * - Validation function\n * - Function to create default content\n */\nimport { z } from 'zod';\n\n// Article block content schema with validation rules\nexport const ArticleBlockContentSchema = z.object({\n headline: z.string().min(1, 'Article headline is required').max(300, 'Headline too long').trim(),\n author: z.string().max(100, 'Article author too long').trim().optional(),\n publishedAt: z.iso\n .datetime({ message: 'Article publishedAt must be a valid ISO 8601 datetime string.' })\n .optional(),\n body: z.string().min(1, 'Article body content is required'),\n tags: z.array(z.string()).optional(),\n status: z.enum(['draft', 'review', 'published']),\n});\n\n// Inferred type for components\nexport type ArticleBlockContent = z.infer<typeof ArticleBlockContentSchema>;\n\n// Schema name constant\nexport const ARTICLE_BLOCK_SCHEMA_NAME = 'article';\n\n// Default values for a new ArticleBlock\nexport const ArticleBlockDefaults: Partial<ArticleBlockContent> = {\n status: 'draft',\n};\n\n// Validate ArticleBlock content\nexport function validateArticleBlockContent(content: unknown): {\n valid: boolean;\n data?: ArticleBlockContent;\n errors?: z.ZodError;\n} {\n const result = ArticleBlockContentSchema.safeParse(content);\n if (result.success) {\n return { valid: true, data: result.data };\n }\n return { valid: false, errors: result.error };\n}\n\n// Create default ArticleBlock content\nexport function createDefaultArticleContent(): ArticleBlockContent {\n return {\n headline: 'untitled article',\n author: '',\n publishedAt: new Date().toISOString(),\n body: '',\n tags: [],\n status: 'draft',\n };\n}\n","/**\n * CTABlock Schema.\n *\n * A call-to-action section with headline, description, and action buttons.\n * Supports primary and optional secondary CTA buttons.\n */\n\nimport { z } from 'zod';\n\n// CTA button schema\n// URL can be either a full URL (http://...) or a relative path (/path)\nconst CTAButtonSchema = z.object({\n text: z.string().min(1, 'Button text is required').max(50, 'Button text too long'),\n url: z.string().refine(\n (val) => {\n // Accept full URLs\n if (val.startsWith('http://') || val.startsWith('https://')) {\n try {\n new URL(val);\n return true;\n } catch {\n return false;\n }\n }\n // Accept relative paths starting with /\n if (val.startsWith('/')) {\n return true;\n }\n // Accept hash/anchor links\n if (val.startsWith('#')) {\n return true;\n }\n return false;\n },\n {\n message:\n 'URL must be a valid full URL (http://... or https://...), relative path (/path), or anchor (#anchor)',\n }\n ),\n});\n\nexport type CTAButton = z.infer<typeof CTAButtonSchema>;\n\n// CTABlock content schema with validation rules\nexport const CTABlockContentSchema = z.object({\n headline: z.string().min(1, 'Headline is required').max(100, 'Headline too long'),\n description: z.string().max(500, 'Description too long').optional(),\n primaryButton: CTAButtonSchema,\n secondaryButton: CTAButtonSchema.optional(),\n});\n\n// Inferred type for components\nexport type CTABlockContent = z.infer<typeof CTABlockContentSchema>;\n\n// Schema name constant\nexport const CTA_BLOCK_SCHEMA_NAME = 'cta-block';\n\n// Default values for a new CTABlock\nexport const CTABlockDefaults: Partial<CTABlockContent> = {\n primaryButton: {\n text: 'Get Started',\n url: '#',\n },\n};\n\n// Validate CTABlock content\nexport function validateCTABlockContent(content: unknown): {\n valid: boolean;\n data?: CTABlockContent;\n errors?: z.ZodError;\n} {\n const result = CTABlockContentSchema.safeParse(content);\n if (result.success) {\n return { valid: true, data: result.data };\n }\n return { valid: false, errors: result.error };\n}\n\n// Create default CTABlock content\nexport function createDefaultCTAContent(): CTABlockContent {\n return {\n headline: '',\n description: undefined,\n primaryButton: {\n text: 'Get Started',\n url: '#',\n },\n secondaryButton: undefined,\n };\n}\n","/**\n * FeaturesBlock Schema.\n *\n * A section displaying a list of features with icons, titles, and descriptions.\n * Supports different layouts (grid, list, carousel).\n */\n\nimport { z } from 'zod';\n\n// Layout options for features display\nexport const FeaturesLayout = ['grid', 'list', 'carousel'] as const;\nexport type FeaturesLayoutType = (typeof FeaturesLayout)[number];\n\n// Single feature item schema\nexport const FeatureItemSchema = z.object({\n icon: z.string().max(50, 'Icon name too long').optional(),\n title: z.string().min(1, 'Title is required').max(100, 'Title too long'),\n description: z.string().max(500, 'Description too long').optional(),\n});\n\nexport type FeatureItem = z.infer<typeof FeatureItemSchema>;\n\n// FeaturesBlock content schema with array validation\nexport const FeaturesBlockContentSchema = z.object({\n title: z.string().min(1, 'Section title is required').max(100, 'Title too long'),\n subtitle: z.string().max(200, 'Subtitle too long').optional(),\n features: z\n .array(FeatureItemSchema)\n .min(1, 'At least one feature is required')\n .max(6, 'Maximum 6 features allowed'),\n layout: z.enum(FeaturesLayout).default('grid'),\n});\n\n// Inferred type for components\nexport type FeaturesBlockContent = z.infer<typeof FeaturesBlockContentSchema>;\n\n// Schema name constant\nexport const FEATURES_BLOCK_SCHEMA_NAME = 'features-block';\n\n// Default values for a new FeaturesBlock\nexport const FeaturesBlockDefaults: Partial<FeaturesBlockContent> = {\n layout: 'grid',\n features: [\n {\n icon: 'star',\n title: 'Feature Title',\n description: 'Describe this feature',\n },\n ],\n};\n\n// Validate FeaturesBlock content\nexport function validateFeaturesBlockContent(content: unknown): {\n valid: boolean;\n data?: FeaturesBlockContent;\n errors?: z.ZodError;\n} {\n const result = FeaturesBlockContentSchema.safeParse(content);\n if (result.success) {\n return { valid: true, data: result.data };\n }\n return { valid: false, errors: result.error };\n}\n\n// Create default FeaturesBlock content\nexport function createDefaultFeaturesContent(): FeaturesBlockContent {\n return {\n title: '',\n subtitle: undefined,\n features: [],\n layout: 'grid',\n };\n}\n\n// Add a feature to the content\nexport function addFeature(\n content: FeaturesBlockContent,\n feature: FeatureItem\n): FeaturesBlockContent {\n if (content.features.length >= 6) {\n throw new Error('Maximum 6 features allowed');\n }\n return {\n ...content,\n features: [...content.features, feature],\n };\n}\n\n// Remove a feature by index\nexport function removeFeature(content: FeaturesBlockContent, index: number): FeaturesBlockContent {\n if (index < 0 || index >= content.features.length) {\n throw new Error('Invalid feature index');\n }\n if (content.features.length <= 1) {\n throw new Error('At least one feature is required');\n }\n return {\n ...content,\n features: content.features.filter((_, i) => i !== index),\n };\n}\n\n// Update a feature by index\nexport function updateFeature(\n content: FeaturesBlockContent,\n index: number,\n updates: Partial<FeatureItem>\n): FeaturesBlockContent {\n if (index < 0 || index >= content.features.length) {\n throw new Error('Invalid feature index');\n }\n return {\n ...content,\n features: content.features.map((feature, i) =>\n i === index ? { ...feature, ...updates } : feature\n ),\n };\n}\n","/**\n * HeroBlock Schema.\n *\n * A full-width hero section typically used at the top of pages.\n * Supports headline, subheadline, CTA button, background image, and alignment.\n */\n\nimport { z } from 'zod';\nimport { ImageReferenceSchema } from '../../fields/complex/media';\n\n// Alignment options for the hero content\nexport const HeroAlignment = ['left', 'center', 'right'] as const;\nexport type HeroAlignmentType = (typeof HeroAlignment)[number];\n\n// HeroBlock content schema with validation rules\nexport const HeroBlockContentSchema = z.object({\n headline: z.string().min(1, 'Headline is required').max(100, 'Headline too long'),\n subheadline: z.string().max(200, 'Subheadline too long').optional(),\n ctaText: z.string().max(50, 'CTA text too long').optional(),\n ctaUrl: z\n .string()\n .refine(\n (val) => {\n // Empty string is valid\n if (val === '') return true;\n // Accept full URLs\n if (val.startsWith('http://') || val.startsWith('https://')) {\n try {\n new URL(val);\n return true;\n } catch {\n return false;\n }\n }\n // Accept relative paths starting with /\n if (val.startsWith('/')) {\n return true;\n }\n // Accept hash/anchor links\n if (val.startsWith('#')) {\n return true;\n }\n return false;\n },\n {\n message:\n 'URL must be a valid full URL (http://... or https://...), relative path (/path), anchor (#anchor), or empty string',\n }\n )\n .optional()\n .or(z.literal('')),\n backgroundImage: ImageReferenceSchema.nullable().optional(),\n alignment: z.enum(HeroAlignment).default('center'),\n});\n\n// Inferred type for components\nexport type HeroBlockContent = z.infer<typeof HeroBlockContentSchema>;\n\n// Schema name constant\nexport const HERO_BLOCK_SCHEMA_NAME = 'hero-block';\n\n// Default values for a new HeroBlock\nexport const HeroBlockDefaults: Partial<HeroBlockContent> = {\n alignment: 'center',\n};\n\n// Validate HeroBlock content\nexport function validateHeroBlockContent(content: unknown): {\n valid: boolean;\n data?: HeroBlockContent;\n errors?: z.ZodError;\n} {\n const result = HeroBlockContentSchema.safeParse(content);\n if (result.success) {\n return { valid: true, data: result.data };\n }\n return { valid: false, errors: result.error };\n}\n\n// Create default HeroBlock content\nexport function createDefaultHeroContent(): HeroBlockContent {\n return {\n headline: '',\n subheadline: undefined,\n ctaText: undefined,\n ctaUrl: undefined,\n backgroundImage: undefined,\n alignment: 'center',\n };\n}\n","/**\n * Image and file field factories.\n *\n * The image() factory creates an ImageReference schema that:\n * - References an ImageAsset by ID\n * - Stores context-specific alt text (required for accessibility)\n * - Supports optional hotspot (focal point) and crop settings\n */\n\nimport { z } from 'zod';\n\nimport type { FieldMeta } from '../../types';\nimport {\n DimensionSchema,\n FileSizeSchema,\n MAX_DIMENSION,\n MAX_FILE_SIZE,\n MIN_DIMENSION,\n MimeTypeSchema,\n} from '../../validation/image';\n\n// =============================================================================\n// ImageAsset Schema (Public API type)\n// =============================================================================\n\n/**\n * Public API schema for image assets.\n *\n * This is the camelCase API representation. The database uses snake_case\n * (ImageAssetRow in db/image-asset-types.ts). Conversion between formats\n * should happen at the API boundary.\n *\n * Reuses validation schemas from validation/image.ts to ensure consistency.\n */\nexport const ImageAssetSchema = z.object({\n /** UUID primary key */\n id: z.uuid(),\n /** R2/S3 storage URL for the original file */\n url: z.url(),\n /** Image width in pixels */\n width: DimensionSchema,\n /** Image height in pixels */\n height: DimensionSchema,\n /** Original filename from upload */\n originalFilename: z.string().min(1).max(255),\n /** MIME type (only web-safe formats allowed) */\n mimeType: MimeTypeSchema,\n /** File size in bytes */\n fileSize: FileSizeSchema,\n /** Base64-encoded tiny preview for blur-up loading */\n lqip: z.string().optional(),\n});\n\nexport type ImageAsset = z.infer<typeof ImageAssetSchema>;\n\n// =============================================================================\n// ImageReference Schema (Asset-reference model)\n// =============================================================================\n\n/**\n * Hotspot defines the focal point of an image.\n * Coordinates are fractions (0-1) from top-left.\n */\nexport const HotspotSchema = z.object({\n x: z.number().min(0).max(1),\n y: z.number().min(0).max(1),\n});\n\nexport type Hotspot = z.infer<typeof HotspotSchema>;\n\n/**\n * Crop defines the region to extract from the image.\n * - x, y: Top-left coordinate of the crop region in pixels\n * - width, height: Size of the crop region in pixels\n * All values are positive integers representing pixel coordinates/dimensions.\n */\nexport const CropSchema = z.object({\n /** X coordinate of top-left corner in pixels */\n x: z.number().int().nonnegative(),\n /** Y coordinate of top-left corner in pixels */\n y: z.number().int().nonnegative(),\n /** Width of crop region in pixels (must be > 0) */\n width: z.number().int().positive(),\n /** Height of crop region in pixels (must be > 0) */\n height: z.number().int().positive(),\n});\n\nexport type Crop = z.infer<typeof CropSchema>;\n\n/**\n * ImageReference is what blocks store.\n * It points to an ImageAsset and adds context-specific metadata.\n */\nexport const ImageReferenceSchema = z.object({\n // Alt text is REQUIRED for accessibility\n alt: z.string().min(1, 'Alt text is required for accessibility'),\n\n // Optional metadata\n caption: z.string().max(500).optional(),\n attribution: z.string().max(255).optional(),\n\n // Reference to the ImageAsset with stored transformation\n _asset: z.object({\n id: z.uuid(),\n transformation: z.string().nullable().optional(),\n }),\n});\n\nexport type ImageReference = z.infer<typeof ImageReferenceSchema>;\n\n// =============================================================================\n// Image Field Factory\n// =============================================================================\n\nexport interface ImageFieldOptions {\n label: string;\n description?: string;\n required?: boolean;\n accept?: string;\n maxSize?: number;\n minWidth?: number;\n minHeight?: number;\n maxWidth?: number;\n maxHeight?: number;\n aspectRatio?: number;\n}\n\n/**\n * Create an image field with metadata.\n *\n * @example\n * const featuredImage = image({\n * label: 'Featured Image',\n * required: true,\n * accept: 'image/jpeg, image/png',\n * maxSize: 5 * 1024 * 1024,\n * });\n *\n * // Type of the value:\n * // {\n * // alt: string;\n * // caption?: string;\n * // attribution?: string;\n * // _asset: { id: string; transformation?: string | null };\n * // }\n */\nexport function image(options: ImageFieldOptions) {\n const meta: FieldMeta = {\n label: options.label,\n component: 'image-uploader',\n required: options.required,\n description: options.description,\n options: {\n accept: options.accept ?? 'image/jpeg, image/png, image/webp',\n maxSize: options.maxSize ?? MAX_FILE_SIZE, // 10MB default\n minWidth: options.minWidth ?? MIN_DIMENSION,\n minHeight: options.minHeight ?? MIN_DIMENSION,\n maxWidth: options.maxWidth ?? MAX_DIMENSION,\n maxHeight: options.maxHeight ?? MAX_DIMENSION,\n aspectRatio: options.aspectRatio,\n },\n };\n\n // Use the new ImageReferenceSchema instead of URL-only\n const result = options.required ? ImageReferenceSchema : ImageReferenceSchema.optional();\n\n return result.meta(meta);\n}\n\n// =============================================================================\n// File Field\n// =============================================================================\n\nexport interface FileFieldOptions {\n label: string;\n description?: string;\n required?: boolean;\n accept?: string;\n maxSize?: number;\n}\n\nconst fileSchema = z.object({\n url: z.url(),\n name: z.string(),\n size: z.number().int().positive(),\n type: z.string(),\n});\n\n/**\n * Create a file field with metadata.\n */\nexport function file(options: FileFieldOptions) {\n const meta: FieldMeta = {\n label: options.label,\n component: 'file-uploader',\n required: options.required,\n description: options.description,\n options: {\n accept: options.accept ?? '*/*',\n maxSize: options.maxSize,\n },\n };\n\n const result = options.required ? fileSchema : fileSchema.optional();\n return result.meta(meta);\n}\n","/**\n * LogoTrustBlock Schema.\n *\n * A section displaying customer/partner logos for trust and credibility.\n * Supports an optional title and an array of logo images.\n *\n * Each logo uses ImageReference to integrate with the image manager.\n */\n\nimport { z } from 'zod';\n\nimport { ImageReferenceSchema } from '../../fields/complex/media';\n\n// Logo item schema - uses ImageReference for image manager integration\nexport const LogoItemSchema = z.object({\n /** Unique ID for this logo item */\n id: z.uuid(),\n /** Image reference (alt + _asset with transformation) */\n image: ImageReferenceSchema,\n /** Optional company/brand name to display */\n name: z.string().max(100, 'Name too long').optional(),\n});\n\nexport type LogoItem = z.infer<typeof LogoItemSchema>;\n\n/**\n * Legacy logo format schema for backwards compatibility.\n * Used for logos stored with direct URL before the ImageReference migration.\n */\nexport const LegacyLogoItemSchema = z.object({\n /** Direct URL to the logo image */\n url: z.string(),\n /** Alt text for the image */\n alt: z.string(),\n /** Optional company/brand name */\n name: z.string().optional(),\n});\n\nexport type LegacyLogoItem = z.infer<typeof LegacyLogoItemSchema>;\n\n// LogoTrustBlock content schema with validation rules\nexport const LogoTrustBlockContentSchema = z.object({\n title: z.string().max(100, 'Title too long').optional(),\n logos: z.array(LogoItemSchema).max(20, 'Maximum 20 logos allowed'),\n});\n\n// Inferred type for components\nexport type LogoTrustBlockContent = z.infer<typeof LogoTrustBlockContentSchema>;\n\n// Schema name constant\nexport const LOGO_TRUST_BLOCK_SCHEMA_NAME = 'logo-trust-block';\n\n// Default values for a new LogoTrustBlock\nexport const LogoTrustBlockDefaults: Partial<LogoTrustBlockContent> = {\n logos: [],\n};\n\n// Validate LogoTrustBlock content\nexport function validateLogoTrustBlockContent(content: unknown): {\n valid: boolean;\n data?: LogoTrustBlockContent;\n errors?: z.ZodError;\n} {\n const result = LogoTrustBlockContentSchema.safeParse(content);\n if (result.success) {\n return { valid: true, data: result.data };\n }\n return { valid: false, errors: result.error };\n}\n\n// Create default LogoTrustBlock content\nexport function createDefaultLogoTrustContent(): LogoTrustBlockContent {\n return {\n title: undefined,\n logos: [],\n };\n}\n","/**\n * Block Schema Registry.\n *\n * Centralizes registration for all block schemas.\n * Call registerAllBlockSchemas() during application initialization.\n */\n\nimport { clearSchemaRegistry, registerBlockSchema } from '../validation';\nimport { ARTICLE_BLOCK_SCHEMA_NAME, ArticleBlockContentSchema } from './schemas/article-block';\nimport { CTA_BLOCK_SCHEMA_NAME, CTABlockContentSchema } from './schemas/cta-block';\nimport { FEATURES_BLOCK_SCHEMA_NAME, FeaturesBlockContentSchema } from './schemas/features-block';\nimport { HERO_BLOCK_SCHEMA_NAME, HeroBlockContentSchema } from './schemas/hero-block';\nimport {\n LOGO_TRUST_BLOCK_SCHEMA_NAME,\n LogoTrustBlockContentSchema,\n} from './schemas/logo-trust-block';\n\n// All registered block schema names\nexport const BLOCK_SCHEMA_NAMES = [\n ARTICLE_BLOCK_SCHEMA_NAME,\n HERO_BLOCK_SCHEMA_NAME,\n FEATURES_BLOCK_SCHEMA_NAME,\n CTA_BLOCK_SCHEMA_NAME,\n LOGO_TRUST_BLOCK_SCHEMA_NAME,\n] as const;\n\n// Union type for type-safe schema name usage\nexport type BlockSchemaName = (typeof BLOCK_SCHEMA_NAMES)[number];\n\n// Register all block schemas\nexport function registerAllBlockSchemas(): void {\n registerBlockSchema(ARTICLE_BLOCK_SCHEMA_NAME, ArticleBlockContentSchema);\n registerBlockSchema(HERO_BLOCK_SCHEMA_NAME, HeroBlockContentSchema);\n registerBlockSchema(FEATURES_BLOCK_SCHEMA_NAME, FeaturesBlockContentSchema);\n registerBlockSchema(CTA_BLOCK_SCHEMA_NAME, CTABlockContentSchema);\n registerBlockSchema(LOGO_TRUST_BLOCK_SCHEMA_NAME, LogoTrustBlockContentSchema);\n}\n\n// Clear and re-register all block schemas (for testing)\nexport function resetBlockSchemas(): void {\n clearSchemaRegistry();\n registerAllBlockSchemas();\n}\n\n// Type guard for schema names\nexport function isValidBlockSchemaName(name: string): name is BlockSchemaName {\n return BLOCK_SCHEMA_NAMES.includes(name as BlockSchemaName);\n}\n","/**\n * Catch-all Route Handler for Parametric Routes\n *\n * Handles routes with multiple segments like /us/en/products.\n * Uses the CMS API to fetch and render blocks.\n */\n\nimport {\n isArticlePublished,\n isValidBlockSchemaName,\n normalizeArticleContent,\n} from '@repo/cms-schema/blocks';\nimport type { Metadata } from 'next';\nimport { unstable_noStore } from 'next/cache';\nimport { notFound } from 'next/navigation';\nimport { BlockRenderer } from './block-renderer';\nimport { getCmsClient } from './cms-api';\nimport type { BlockComponentRegistry, BlockData } from './types';\n\ntype PageProps = {\n params: Promise<{ slug: string[] }>;\n /** CMS API base URL (e.g., 'http://localhost:3000') */\n cmsUrl: string;\n registry?: Partial<BlockComponentRegistry>;\n /** API key for CMS API authentication */\n apiKey?: string;\n /** Website ID (required if not using env variables) */\n websiteId?: string;\n};\n\nfunction getWebsiteId(providedWebsiteId?: string): string {\n const websiteId =\n providedWebsiteId ??\n process.env.NEXT_PUBLIC_WEBSITE_ID ??\n process.env.WEBSITE_ID ??\n process.env.CMS_WEBSITE_ID;\n\n if (!websiteId) {\n throw new Error(\n 'Missing websiteId for website renderer. Either pass websiteId prop or set NEXT_PUBLIC_WEBSITE_ID (or WEBSITE_ID/CMS_WEBSITE_ID) to a valid UUID.'\n );\n }\n\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(websiteId)) {\n throw new Error(\n `Invalid websiteId \"${websiteId}\". Provide a valid UUID via prop or set NEXT_PUBLIC_WEBSITE_ID (or WEBSITE_ID/CMS_WEBSITE_ID).`\n );\n }\n\n return websiteId;\n}\n\n/**\n * Force dynamic rendering to ensure routes are always fresh.\n * This prevents Next.js from caching pages when routes are published.\n */\nexport const dynamic = 'force-dynamic';\n\n/**\n * Catch-all route handler for parametric routes.\n *\n * Handles paths like:\n * - /us/en/products -> slug = ['us', 'en', 'products']\n * - /about -> slug = ['about']\n *\n * Reconstructs the full path and fetches route via tRPC.\n */\nexport default async function ParametricRoutePage({\n params,\n registry,\n apiKey,\n cmsUrl,\n websiteId: providedWebsiteId,\n}: PageProps) {\n // Prevent any caching - ensure we always fetch fresh route data\n unstable_noStore();\n\n const websiteId = getWebsiteId(providedWebsiteId);\n const { slug } = await params;\n\n // Reconstruct full path from slug segments and normalize it\n const rawPath = `/${slug.join('/')}`;\n const path = normalizePath(rawPath);\n\n // Get CMS API client with optional API key and custom URL\n const client = getCmsClient({ apiKey, cmsUrl });\n\n try {\n // Fetch route by path via CMS API\n const { route } = await client.route.getByPath.query({ websiteId, path });\n\n // Only show Live routes on public website\n if (route.state !== 'Live') {\n console.error(`Route found but not Live. Path: ${path}, State: ${route.state}`);\n notFound();\n }\n\n // Fetch all blocks by ID via CMS API (skip any that fail)\n const blockPromises = route.block_ids.map(async (blockId) => {\n try {\n const result = await client.block.getById.query({ websiteId, id: blockId });\n return result.block;\n } catch (error) {\n // Log error but don't fail the entire page\n console.error(`Failed to fetch block ${blockId}:`, error);\n return null;\n }\n });\n const blockResults = await Promise.all(blockPromises);\n\n // Transform blocks to BlockData format for BlockRenderer\n // Filter out any blocks that failed to load\n const blocks: BlockData[] = [];\n\n for (const block of blockResults) {\n if (!block || block.published_content === null) continue;\n\n const content = block.published_content as Record<string, unknown> | null;\n if (!content) continue;\n\n // Handle 'article' blocks separately before checking schema type\n if (block.schema_name === 'article') {\n const article = normalizeArticleContent(content);\n const isPublished = article ? isArticlePublished(article) : null;\n if (article && isPublished) {\n blocks.push({ id: block.id, type: 'article', content: article });\n }\n continue;\n }\n\n // Skip blocks with invalid schema names (after handling 'article')\n if (!isValidBlockSchemaName(block.schema_name)) {\n continue;\n }\n\n // For all block types, map schema_name to type and include content\n // Image references are automatically resolved by block.getById\n blocks.push({\n id: block.id,\n type: block.schema_name,\n content,\n } as BlockData);\n }\n\n return (\n <main>\n {blocks.map((block) => (\n <BlockRenderer registry={registry ?? {}} key={block.id} block={block} />\n ))}\n </main>\n );\n } catch (error) {\n // Log error for debugging\n console.error(`Route fetch error for path: ${path}`, error);\n\n // If route not found or param validation fails, show 404\n // TRPCClientError has data.code for the error code\n const errorCode =\n error instanceof Error && 'data' in error\n ? (error as { data?: { code?: string } }).data?.code\n : error instanceof Error && 'code' in error\n ? (error as { code: string }).code\n : undefined;\n\n if (errorCode === 'NOT_FOUND' || errorCode === 'P0002') {\n notFound();\n }\n\n // Re-throw other errors\n throw error;\n }\n}\n\n// -----------------------------------------------------------------------------\n// Metadata\n// -----------------------------------------------------------------------------\n\n/**\n * Generate metadata for the page.\n * Uses Next.js 15+ async params pattern.\n */\nexport async function generateMetadata({\n params,\n apiKey,\n cmsUrl,\n websiteId: providedWebsiteId,\n}: PageProps): Promise<Metadata> {\n const websiteId = getWebsiteId(providedWebsiteId);\n const { slug } = await params;\n const rawPath = `/${slug.join('/')}`;\n const path = normalizePath(rawPath);\n const client = getCmsClient({ apiKey, cmsUrl });\n\n try {\n const { route } = await client.route.getByPath.query({ websiteId, path });\n return {\n title: `${route.path} | Website`,\n description: `Content page: ${route.path}`,\n };\n } catch {\n return {\n title: 'Page Not Found | Website',\n description: 'The requested page could not be found.',\n };\n }\n}\n\nexport function normalizePath(path: string): string {\n if (!path || path === '/') {\n return '/';\n }\n\n // Remove trailing slashes, ensure leading slash\n let normalized = path.trim();\n\n // Remove trailing slashes (but keep root \"/\")\n normalized = normalized.replace(/\\/+$/, '');\n\n // Ensure leading slash\n if (!normalized.startsWith('/')) {\n normalized = `/${normalized}`;\n }\n\n // Collapse multiple consecutive slashes to single slash\n normalized = normalized.replace(/\\/+/g, '/');\n\n return normalized;\n}\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z","z","z"]}
|
|
@@ -19,11 +19,6 @@
|
|
|
19
19
|
* return { data };
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
|
-
// -----------------------------------------------------------------------------
|
|
24
|
-
// Result Type
|
|
25
|
-
// -----------------------------------------------------------------------------
|
|
26
|
-
|
|
27
22
|
/**
|
|
28
23
|
* A discriminated union representing either success or failure.
|
|
29
24
|
*
|
|
@@ -45,12 +40,13 @@
|
|
|
45
40
|
* }
|
|
46
41
|
* ```
|
|
47
42
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
type Result<T, E = Error> = {
|
|
44
|
+
ok: true;
|
|
45
|
+
value: T;
|
|
46
|
+
} | {
|
|
47
|
+
ok: false;
|
|
48
|
+
error: E;
|
|
49
|
+
};
|
|
54
50
|
/**
|
|
55
51
|
* Creates a success Result.
|
|
56
52
|
*
|
|
@@ -63,10 +59,7 @@ export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error:
|
|
|
63
59
|
* // { ok: true, value: 42 }
|
|
64
60
|
* ```
|
|
65
61
|
*/
|
|
66
|
-
|
|
67
|
-
return { ok: true, value };
|
|
68
|
-
}
|
|
69
|
-
|
|
62
|
+
declare function ok<T>(value: T): Result<T, never>;
|
|
70
63
|
/**
|
|
71
64
|
* Creates a failure Result.
|
|
72
65
|
*
|
|
@@ -79,14 +72,7 @@ export function ok<T>(value: T): Result<T, never> {
|
|
|
79
72
|
* // { ok: false, error: Error('Something went wrong') }
|
|
80
73
|
* ```
|
|
81
74
|
*/
|
|
82
|
-
|
|
83
|
-
return { ok: false, error };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// -----------------------------------------------------------------------------
|
|
87
|
-
// Type Guards
|
|
88
|
-
// -----------------------------------------------------------------------------
|
|
89
|
-
|
|
75
|
+
declare function err<E>(error: E): Result<never, E>;
|
|
90
76
|
/**
|
|
91
77
|
* Type guard to check if a Result is successful.
|
|
92
78
|
*
|
|
@@ -102,10 +88,10 @@ export function err<E>(error: E): Result<never, E> {
|
|
|
102
88
|
* }
|
|
103
89
|
* ```
|
|
104
90
|
*/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
91
|
+
declare function isOk<T, E>(result: Result<T, E>): result is {
|
|
92
|
+
ok: true;
|
|
93
|
+
value: T;
|
|
94
|
+
};
|
|
109
95
|
/**
|
|
110
96
|
* Type guard to check if a Result is a failure.
|
|
111
97
|
*
|
|
@@ -121,14 +107,10 @@ export function isOk<T, E>(result: Result<T, E>): result is { ok: true; value: T
|
|
|
121
107
|
* }
|
|
122
108
|
* ```
|
|
123
109
|
*/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// -----------------------------------------------------------------------------
|
|
129
|
-
// Extractors
|
|
130
|
-
// -----------------------------------------------------------------------------
|
|
131
|
-
|
|
110
|
+
declare function isErr<T, E>(result: Result<T, E>): result is {
|
|
111
|
+
ok: false;
|
|
112
|
+
error: E;
|
|
113
|
+
};
|
|
132
114
|
/**
|
|
133
115
|
* Extracts the value from a Result, throwing if it's an error.
|
|
134
116
|
*
|
|
@@ -145,13 +127,7 @@ export function isErr<T, E>(result: Result<T, E>): result is { ok: false; error:
|
|
|
145
127
|
* const value2 = unwrap(errorResult); // throws Error('fail')
|
|
146
128
|
* ```
|
|
147
129
|
*/
|
|
148
|
-
|
|
149
|
-
if (isOk(result)) {
|
|
150
|
-
return result.value;
|
|
151
|
-
}
|
|
152
|
-
throw result.error;
|
|
153
|
-
}
|
|
154
|
-
|
|
130
|
+
declare function unwrap<T, E>(result: Result<T, E>): T;
|
|
155
131
|
/**
|
|
156
132
|
* Extracts the value from a Result, returning a default on error.
|
|
157
133
|
*
|
|
@@ -168,13 +144,7 @@ export function unwrap<T, E>(result: Result<T, E>): T {
|
|
|
168
144
|
* const value2 = unwrapOr(okResult, 0); // 42
|
|
169
145
|
* ```
|
|
170
146
|
*/
|
|
171
|
-
|
|
172
|
-
if (isOk(result)) {
|
|
173
|
-
return result.value;
|
|
174
|
-
}
|
|
175
|
-
return defaultValue;
|
|
176
|
-
}
|
|
177
|
-
|
|
147
|
+
declare function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T;
|
|
178
148
|
/**
|
|
179
149
|
* Extracts the value from a Result, computing a default on error.
|
|
180
150
|
*
|
|
@@ -191,17 +161,7 @@ export function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
|
|
|
191
161
|
* });
|
|
192
162
|
* ```
|
|
193
163
|
*/
|
|
194
|
-
|
|
195
|
-
if (isOk(result)) {
|
|
196
|
-
return result.value;
|
|
197
|
-
}
|
|
198
|
-
return fn(result.error);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// -----------------------------------------------------------------------------
|
|
202
|
-
// Transformers
|
|
203
|
-
// -----------------------------------------------------------------------------
|
|
204
|
-
|
|
164
|
+
declare function unwrapOrElse<T, E>(result: Result<T, E>, fn: (error: E) => T): T;
|
|
205
165
|
/**
|
|
206
166
|
* Maps a successful Result's value.
|
|
207
167
|
*
|
|
@@ -218,13 +178,7 @@ export function unwrapOrElse<T, E>(result: Result<T, E>, fn: (error: E) => T): T
|
|
|
218
178
|
* const still = map(errorResult, (n) => n * 2); // err('fail')
|
|
219
179
|
* ```
|
|
220
180
|
*/
|
|
221
|
-
|
|
222
|
-
if (isOk(result)) {
|
|
223
|
-
return ok(fn(result.value));
|
|
224
|
-
}
|
|
225
|
-
return result;
|
|
226
|
-
}
|
|
227
|
-
|
|
181
|
+
declare function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;
|
|
228
182
|
/**
|
|
229
183
|
* Maps a failed Result's error.
|
|
230
184
|
*
|
|
@@ -238,13 +192,7 @@ export function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<
|
|
|
238
192
|
* const mapped = mapErr(result, (e) => new Error(e)); // err(Error('not found'))
|
|
239
193
|
* ```
|
|
240
194
|
*/
|
|
241
|
-
|
|
242
|
-
if (isErr(result)) {
|
|
243
|
-
return err(fn(result.error));
|
|
244
|
-
}
|
|
245
|
-
return result;
|
|
246
|
-
}
|
|
247
|
-
|
|
195
|
+
declare function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>;
|
|
248
196
|
/**
|
|
249
197
|
* Chains Result-returning operations.
|
|
250
198
|
*
|
|
@@ -267,20 +215,7 @@ export function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Resu
|
|
|
267
215
|
* const fail = flatMap(parse('abc'), double); // err('not a number')
|
|
268
216
|
* ```
|
|
269
217
|
*/
|
|
270
|
-
|
|
271
|
-
result: Result<T, E>,
|
|
272
|
-
fn: (value: T) => Result<U, E>
|
|
273
|
-
): Result<U, E> {
|
|
274
|
-
if (isOk(result)) {
|
|
275
|
-
return fn(result.value);
|
|
276
|
-
}
|
|
277
|
-
return result;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// -----------------------------------------------------------------------------
|
|
281
|
-
// Async Helpers
|
|
282
|
-
// -----------------------------------------------------------------------------
|
|
283
|
-
|
|
218
|
+
declare function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>;
|
|
284
219
|
/**
|
|
285
220
|
* Wraps a Promise in a Result type.
|
|
286
221
|
*
|
|
@@ -297,15 +232,7 @@ export function flatMap<T, U, E>(
|
|
|
297
232
|
* const response = result.value;
|
|
298
233
|
* ```
|
|
299
234
|
*/
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
const value = await promise;
|
|
303
|
-
return ok(value);
|
|
304
|
-
} catch (error) {
|
|
305
|
-
return err(error instanceof Error ? error : new Error(String(error)));
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
235
|
+
declare function fromPromise<T>(promise: Promise<T>): Promise<Result<T, Error>>;
|
|
309
236
|
/**
|
|
310
237
|
* Wraps a throwing function in a Result type.
|
|
311
238
|
*
|
|
@@ -322,14 +249,7 @@ export async function fromPromise<T>(promise: Promise<T>): Promise<Result<T, Err
|
|
|
322
249
|
* return result.value;
|
|
323
250
|
* ```
|
|
324
251
|
*/
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
return ok(fn());
|
|
328
|
-
} catch (error) {
|
|
329
|
-
return err(error instanceof Error ? error : new Error(String(error)));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
252
|
+
declare function tryCatch<T>(fn: () => T): Result<T, Error>;
|
|
333
253
|
/**
|
|
334
254
|
* Wraps an async function in a Result type.
|
|
335
255
|
*
|
|
@@ -345,19 +265,7 @@ export function tryCatch<T>(fn: () => T): Result<T, Error> {
|
|
|
345
265
|
* });
|
|
346
266
|
* ```
|
|
347
267
|
*/
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const value = await fn();
|
|
351
|
-
return ok(value);
|
|
352
|
-
} catch (error) {
|
|
353
|
-
return err(error instanceof Error ? error : new Error(String(error)));
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// -----------------------------------------------------------------------------
|
|
358
|
-
// Tuple Helpers (Go-style)
|
|
359
|
-
// -----------------------------------------------------------------------------
|
|
360
|
-
|
|
268
|
+
declare function tryCatchAsync<T>(fn: () => Promise<T>): Promise<Result<T, Error>>;
|
|
361
269
|
/**
|
|
362
270
|
* Go-style tuple for error handling: [error, value]
|
|
363
271
|
*
|
|
@@ -371,8 +279,7 @@ export async function tryCatchAsync<T>(fn: () => Promise<T>): Promise<Result<T,
|
|
|
371
279
|
* console.log(data);
|
|
372
280
|
* ```
|
|
373
281
|
*/
|
|
374
|
-
|
|
375
|
-
|
|
282
|
+
type GoTuple<T, E = Error> = [E, undefined] | [undefined, T];
|
|
376
283
|
/**
|
|
377
284
|
* Converts a Result to a Go-style tuple.
|
|
378
285
|
*
|
|
@@ -385,13 +292,7 @@ export type GoTuple<T, E = Error> = [E, undefined] | [undefined, T];
|
|
|
385
292
|
* const [err, data] = toTuple(result);
|
|
386
293
|
* ```
|
|
387
294
|
*/
|
|
388
|
-
|
|
389
|
-
if (isOk(result)) {
|
|
390
|
-
return [undefined, result.value];
|
|
391
|
-
}
|
|
392
|
-
return [result.error, undefined];
|
|
393
|
-
}
|
|
394
|
-
|
|
295
|
+
declare function toTuple<T, E>(result: Result<T, E>): GoTuple<T, E>;
|
|
395
296
|
/**
|
|
396
297
|
* Wraps an async function and returns a Go-style tuple.
|
|
397
298
|
*
|
|
@@ -413,16 +314,7 @@ export function toTuple<T, E>(result: Result<T, E>): GoTuple<T, E> {
|
|
|
413
314
|
* return data;
|
|
414
315
|
* ```
|
|
415
316
|
*/
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
const value = await fn();
|
|
419
|
-
return [undefined, value];
|
|
420
|
-
} catch (error) {
|
|
421
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
422
|
-
return [err, undefined];
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
317
|
+
declare function handle<T>(fn: () => Promise<T>): Promise<GoTuple<T, Error>>;
|
|
426
318
|
/**
|
|
427
319
|
* Wraps a synchronous function and returns a Go-style tuple.
|
|
428
320
|
*
|
|
@@ -439,12 +331,6 @@ export async function handle<T>(fn: () => Promise<T>): Promise<GoTuple<T, Error>
|
|
|
439
331
|
* return parsed;
|
|
440
332
|
* ```
|
|
441
333
|
*/
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
return [undefined, value];
|
|
446
|
-
} catch (error) {
|
|
447
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
448
|
-
return [err, undefined];
|
|
449
|
-
}
|
|
450
|
-
}
|
|
334
|
+
declare function handleSync<T>(fn: () => T): GoTuple<T, Error>;
|
|
335
|
+
|
|
336
|
+
export { type GoTuple, type Result, err, flatMap, fromPromise, handle, handleSync, isErr, isOk, map, mapErr, ok, toTuple, tryCatch, tryCatchAsync, unwrap, unwrapOr, unwrapOrElse };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
err,
|
|
3
|
+
flatMap,
|
|
4
|
+
fromPromise,
|
|
5
|
+
handle,
|
|
6
|
+
handleSync,
|
|
7
|
+
isErr,
|
|
8
|
+
isOk,
|
|
9
|
+
map,
|
|
10
|
+
mapErr,
|
|
11
|
+
ok,
|
|
12
|
+
toTuple,
|
|
13
|
+
tryCatch,
|
|
14
|
+
tryCatchAsync,
|
|
15
|
+
unwrap,
|
|
16
|
+
unwrapOr,
|
|
17
|
+
unwrapOrElse
|
|
18
|
+
} from "../chunk-HVKFEZBT.js";
|
|
19
|
+
export {
|
|
20
|
+
err,
|
|
21
|
+
flatMap,
|
|
22
|
+
fromPromise,
|
|
23
|
+
handle,
|
|
24
|
+
handleSync,
|
|
25
|
+
isErr,
|
|
26
|
+
isOk,
|
|
27
|
+
map,
|
|
28
|
+
mapErr,
|
|
29
|
+
ok,
|
|
30
|
+
toTuple,
|
|
31
|
+
tryCatch,
|
|
32
|
+
tryCatchAsync,
|
|
33
|
+
unwrap,
|
|
34
|
+
unwrapOr,
|
|
35
|
+
unwrapOrElse
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface SchemaConfig {
|
|
2
|
+
apiBase?: string;
|
|
3
|
+
fetchOptions?: RequestInit;
|
|
4
|
+
}
|
|
5
|
+
interface SchemaQuery {
|
|
6
|
+
fetchAll: <T = Record<string, unknown>>() => Promise<T[]>;
|
|
7
|
+
fetchSingle: <T = Record<string, unknown>>() => Promise<T | null>;
|
|
8
|
+
}
|
|
9
|
+
interface SchemaClient {
|
|
10
|
+
name: (schemaName: string) => SchemaQuery;
|
|
11
|
+
}
|
|
12
|
+
declare function configureSchema(config?: SchemaConfig): SchemaClient;
|
|
13
|
+
declare const schema: SchemaClient;
|
|
14
|
+
|
|
15
|
+
export { configureSchema, schema };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// lib/schema.ts
|
|
2
|
+
var DEFAULT_API_BASE = process.env.NEXT_PUBLIC_CMS_API_URL ?? "http://localhost:4000/api";
|
|
3
|
+
function createSchemaQuery(apiBase, schemaName, fetchOptions = {}) {
|
|
4
|
+
const baseUrl = `${apiBase}/schemas/${schemaName}`;
|
|
5
|
+
return {
|
|
6
|
+
async fetchAll() {
|
|
7
|
+
const response = await fetch(baseUrl, fetchOptions);
|
|
8
|
+
if (!response.ok) {
|
|
9
|
+
throw new Error(`Failed to fetch schema "${schemaName}": ${response.statusText}`);
|
|
10
|
+
}
|
|
11
|
+
return response.json();
|
|
12
|
+
},
|
|
13
|
+
async fetchSingle() {
|
|
14
|
+
const response = await fetch(`${baseUrl}?limit=1`, fetchOptions);
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(`Failed to fetch schema "${schemaName}": ${response.statusText}`);
|
|
17
|
+
}
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
return Array.isArray(data) ? data[0] ?? null : data;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function configureSchema(config = {}) {
|
|
24
|
+
const apiBase = config.apiBase || DEFAULT_API_BASE;
|
|
25
|
+
const fetchOptions = config.fetchOptions || {};
|
|
26
|
+
return {
|
|
27
|
+
name: (schemaName) => createSchemaQuery(apiBase, schemaName, fetchOptions)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
var schema = configureSchema();
|
|
31
|
+
export {
|
|
32
|
+
configureSchema,
|
|
33
|
+
schema
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../lib/schema.ts"],"sourcesContent":["// Fetch schema from the CMS for a headless setup\n// This should look like this\n//\n// import { schema, configureSchema } from \"@profound/cms\";\n//\n// // Option 1: Use default config (reads from CMS_API_URL env var)\n// const footerData = schema.name(\"footer\").fetchAll();\n//\n// // Option 2: Configure with custom API base and fetch options\n// const customSchema = configureSchema({\n// apiBase: \"https://my-cms.example.com/api\",\n// fetchOptions: { headers: { Authorization: \"Bearer token\" } },\n// });\n// const footerData = customSchema.name(\"footer\").fetchAll();\n//\n// console.log(footerData); # [{ logo: \"<cloudflare_signed_img_url>\", \"Footer_text\": \"Profound\" }]\n\nconst DEFAULT_API_BASE = process.env.NEXT_PUBLIC_CMS_API_URL ?? 'http://localhost:4000/api';\n\ninterface SchemaConfig {\n apiBase?: string;\n fetchOptions?: RequestInit;\n}\n\ninterface SchemaQuery {\n fetchAll: <T = Record<string, unknown>>() => Promise<T[]>;\n fetchSingle: <T = Record<string, unknown>>() => Promise<T | null>;\n}\n\ninterface SchemaClient {\n name: (schemaName: string) => SchemaQuery;\n}\n\nfunction createSchemaQuery(\n apiBase: string,\n schemaName: string,\n fetchOptions: RequestInit = {}\n): SchemaQuery {\n const baseUrl = `${apiBase}/schemas/${schemaName}`;\n\n return {\n async fetchAll<T = Record<string, unknown>>(): Promise<T[]> {\n const response = await fetch(baseUrl, fetchOptions);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch schema \"${schemaName}\": ${response.statusText}`);\n }\n\n return response.json();\n },\n\n async fetchSingle<T = Record<string, unknown>>(): Promise<T | null> {\n const response = await fetch(`${baseUrl}?limit=1`, fetchOptions);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch schema \"${schemaName}\": ${response.statusText}`);\n }\n\n const data = await response.json();\n return Array.isArray(data) ? (data[0] ?? null) : data;\n },\n };\n}\n\nexport function configureSchema(config: SchemaConfig = {}): SchemaClient {\n const apiBase = config.apiBase || DEFAULT_API_BASE;\n const fetchOptions = config.fetchOptions || {};\n\n return {\n name: (schemaName: string): SchemaQuery => createSchemaQuery(apiBase, schemaName, fetchOptions),\n };\n}\n\nexport const schema: SchemaClient = configureSchema();\n"],"mappings":";AAiBA,IAAM,mBAAmB,QAAQ,IAAI,2BAA2B;AAgBhE,SAAS,kBACP,SACA,YACA,eAA4B,CAAC,GAChB;AACb,QAAM,UAAU,GAAG,OAAO,YAAY,UAAU;AAEhD,SAAO;AAAA,IACL,MAAM,WAAsD;AAC1D,YAAM,WAAW,MAAM,MAAM,SAAS,YAAY;AAElD,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,MAAM,2BAA2B,UAAU,MAAM,SAAS,UAAU,EAAE;AAAA,MAClF;AAEA,aAAO,SAAS,KAAK;AAAA,IACvB;AAAA,IAEA,MAAM,cAA8D;AAClE,YAAM,WAAW,MAAM,MAAM,GAAG,OAAO,YAAY,YAAY;AAE/D,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,MAAM,2BAA2B,UAAU,MAAM,SAAS,UAAU,EAAE;AAAA,MAClF;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,aAAO,MAAM,QAAQ,IAAI,IAAK,KAAK,CAAC,KAAK,OAAQ;AAAA,IACnD;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB,SAAuB,CAAC,GAAiB;AACvE,QAAM,UAAU,OAAO,WAAW;AAClC,QAAM,eAAe,OAAO,gBAAgB,CAAC;AAE7C,SAAO;AAAA,IACL,MAAM,CAAC,eAAoC,kBAAkB,SAAS,YAAY,YAAY;AAAA,EAChG;AACF;AAEO,IAAM,SAAuB,gBAAgB;","names":[]}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { AppRouter } from '@repo/cms-schema/trpc';
|
|
2
|
+
import { CreateTRPCReact } from '@trpc/react-query';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* tRPC Client Setup
|
|
3
6
|
*
|
|
@@ -5,9 +8,6 @@
|
|
|
5
8
|
* Uses tRPC v11 patterns with TanStack Query v5.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
|
-
import type { AppRouter } from '@repo/cms-schema/trpc';
|
|
9
|
-
import { type CreateTRPCReact, createTRPCReact } from '@trpc/react-query';
|
|
10
|
-
|
|
11
11
|
/**
|
|
12
12
|
* tRPC React client.
|
|
13
13
|
*
|
|
@@ -25,4 +25,6 @@ import { type CreateTRPCReact, createTRPCReact } from '@trpc/react-query';
|
|
|
25
25
|
* }
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
declare const trpc: CreateTRPCReact<AppRouter, unknown>;
|
|
29
|
+
|
|
30
|
+
export { trpc };
|
package/dist/lib/trpc.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../lib/trpc.ts"],"sourcesContent":["/**\n * tRPC Client Setup\n *\n * Creates the tRPC React hooks for client components.\n * Uses tRPC v11 patterns with TanStack Query v5.\n */\n\nimport type { AppRouter } from '@repo/cms-schema/trpc';\nimport { type CreateTRPCReact, createTRPCReact } from '@trpc/react-query';\n\n/**\n * tRPC React client.\n *\n * Use this in client components to call tRPC procedures.\n *\n * @example\n * ```tsx\n * 'use client';\n * import { trpc } from '@/lib/trpc';\n *\n * function NavigationClient() {\n * const { data, isLoading } = trpc.blocks.getNavigation.useQuery();\n * if (isLoading) return <div>Loading...</div>;\n * return <nav>{data?.links.map(link => ...)}</nav>;\n * }\n * ```\n */\nexport const trpc: CreateTRPCReact<AppRouter, unknown> = createTRPCReact<AppRouter>();\n"],"mappings":";AAQA,SAA+B,uBAAuB;AAmB/C,IAAM,OAA4C,gBAA2B;","names":[]}
|