@cedgetec-utils/astro-components 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedgetec-utils/astro-components",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.ts"
@@ -13,10 +13,15 @@
13
13
  "astro-component"
14
14
  ],
15
15
  "scripts": {},
16
+ "dependencies": {
17
+ "astro": "^5.17.1"
18
+ },
16
19
  "devDependencies": {
17
- "astro": "^5.15.3"
20
+ "oxfmt": "^0.28.0",
21
+ "oxlint": "^1.43.0",
22
+ "typescript": "^5.9.3"
18
23
  },
19
24
  "peerDependencies": {
20
- "astro": "^5.0.0"
25
+ "astro": "^5.17.1"
21
26
  }
22
- }
27
+ }
package/src/Head.astro CHANGED
@@ -47,22 +47,22 @@ const openGraphImage = props.image
47
47
 
48
48
  <head>
49
49
  <meta charset="UTF-8" />
50
- <meta name="viewport" content="width=device-width" />
51
- <!-- Icons -->
50
+ <title>{title}</title>
51
+
52
52
  <link rel="icon" href={iconDark.src} />
53
53
  <link rel="icon" href={iconLight.src} media="(prefers-color-scheme:dark)" />
54
54
  <link rel="icon" href={iconDark.src} media="(prefers-color-scheme:light)" />
55
+
55
56
  <meta name="generator" content={Astro.generator} />
56
57
  <meta name="view-transition" content="same-origin" />
57
- <!-- Title etc. -->
58
- <title>{title}</title>
58
+ <meta name="viewport" content="width=device-width" />
59
59
  {props.description && <meta name="description" content={props.description} />}
60
60
  {
61
61
  props.keywords && (
62
62
  <meta name="keywords" content={props.keywords.join(",")} />
63
63
  )
64
64
  }
65
- <!-- Open Graph -->
65
+
66
66
  <meta property="og:title" content={ogTitle} />
67
67
  {
68
68
  props.description && (
@@ -0,0 +1,184 @@
1
+ import type { ZodObject, ZodRawShape, ZodSchema } from "astro/zod"
2
+ import { defineCollection } from "astro:content"
3
+ import { z } from "astro/zod"
4
+
5
+ async function fetchWithRetry(
6
+ url: string,
7
+ options: RequestInit = {},
8
+ retries = 10,
9
+ delay = 200,
10
+ ): Promise<Response> {
11
+ try {
12
+ const controller = new AbortController()
13
+ const timeout = setTimeout(() => controller.abort(), 10_000)
14
+
15
+ const res = await fetch(url, {
16
+ ...options,
17
+ signal: controller.signal,
18
+ })
19
+
20
+ clearTimeout(timeout)
21
+
22
+ if (!res.ok) {
23
+ throw new Error(`HTTP ${res.status}`)
24
+ }
25
+
26
+ return res
27
+ } catch (err) {
28
+ if (retries <= 0) throw err
29
+ console.warn(`Fetch failed, retrying (${retries})…`)
30
+ await new Promise((r) => setTimeout(r, delay))
31
+ return fetchWithRetry(url, options, retries - 1, delay * 1.5)
32
+ }
33
+ }
34
+
35
+ function getZodSchemaPaths(schema: ZodSchema, prefix = ""): string[] {
36
+ const paths: string[] = []
37
+
38
+ // Unwrap optional, nullable, and effects (transforms, refinements, etc.)
39
+ let unwrapped = schema
40
+ while (
41
+ unwrapped instanceof z.ZodOptional ||
42
+ unwrapped instanceof z.ZodNullable ||
43
+ unwrapped instanceof z.ZodEffects
44
+ ) {
45
+ if (unwrapped instanceof z.ZodEffects) {
46
+ unwrapped = unwrapped._def.schema
47
+ } else {
48
+ unwrapped = unwrapped._def.innerType
49
+ }
50
+ }
51
+
52
+ // Handle ZodObject
53
+ if (unwrapped instanceof z.ZodObject) {
54
+ const shape = unwrapped.shape
55
+ for (const key in shape) {
56
+ const fieldPath = prefix ? `${prefix}.${key}` : key
57
+ const fieldSchema = shape[key]
58
+
59
+ // Recursively get paths for nested schemas
60
+ const nestedPaths = getZodSchemaPaths(fieldSchema, fieldPath)
61
+
62
+ if (nestedPaths.length > 0) {
63
+ paths.push(...nestedPaths)
64
+ } else {
65
+ paths.push(fieldPath)
66
+ }
67
+ }
68
+ } // Handle ZodArray
69
+ else if (unwrapped instanceof z.ZodArray) {
70
+ const elementSchema = unwrapped._def.type
71
+ // Get paths from the array element schema without adding array indices
72
+ const nestedPaths = getZodSchemaPaths(elementSchema, prefix)
73
+ paths.push(...nestedPaths)
74
+ } // Handle ZodDiscriminatedUnion (for Directus M2A relationships)
75
+ else if (unwrapped instanceof z.ZodDiscriminatedUnion) {
76
+ const discriminator = unwrapped._def.discriminator as string
77
+ const options = unwrapped._def.options as z.ZodObject<any>[]
78
+
79
+ // Always include the discriminator field
80
+ paths.push(prefix ? `${prefix}.${discriminator}` : discriminator)
81
+
82
+ for (const option of options) {
83
+ const shape = option.shape
84
+ // Get the literal value of the discriminator (e.g., "pages_content_heading")
85
+ let discriminatorValue: string | undefined
86
+ const discriminatorSchema = shape[discriminator]
87
+ if (discriminatorSchema instanceof z.ZodLiteral) {
88
+ discriminatorValue = discriminatorSchema._def.value as string
89
+ }
90
+
91
+ // Process other fields in this variant
92
+ for (const key in shape) {
93
+ if (key === discriminator) continue
94
+
95
+ const fieldPath = prefix ? `${prefix}.${key}` : key
96
+ const fieldSchema = shape[key]
97
+
98
+ // For M2A "item" fields, use Directus collection-specific syntax
99
+ if (key === "item" && discriminatorValue) {
100
+ const itemPaths = getZodSchemaPaths(fieldSchema, "")
101
+ for (const itemPath of itemPaths) {
102
+ // Use syntax: item:collection_name.field
103
+ paths.push(`${fieldPath}:${discriminatorValue}.${itemPath}`)
104
+ }
105
+ } else {
106
+ const nestedPaths = getZodSchemaPaths(fieldSchema, fieldPath)
107
+ paths.push(...nestedPaths)
108
+ }
109
+ }
110
+ }
111
+ } // Base case: primitive types
112
+ else {
113
+ if (prefix) {
114
+ paths.push(prefix)
115
+ }
116
+ }
117
+
118
+ return paths
119
+ }
120
+
121
+ export class Directus {
122
+ #apiDomain: string
123
+
124
+ constructor(apiDomain: string) {
125
+ this.#apiDomain = apiDomain
126
+ }
127
+
128
+ async #query(collection: string, fields?: string[]) {
129
+ const response = await fetchWithRetry(
130
+ `https://${this.#apiDomain}/items/${collection}${
131
+ fields && `?fields=${fields.join(",")}`
132
+ }`,
133
+ )
134
+
135
+ const data = await response.json()
136
+
137
+ return data.data
138
+ }
139
+
140
+ collection<T extends ZodRawShape>(name: string, schema: ZodObject<T>) {
141
+ const schemaWithId = schema.extend({ id: z.coerce.string() })
142
+ return defineCollection({
143
+ schema: schemaWithId,
144
+ loader: async () => {
145
+ const result = await this.#query(name, getZodSchemaPaths(schemaWithId))
146
+ if (Array.isArray(result)) {
147
+ return result
148
+ }
149
+
150
+ return [result]
151
+ },
152
+ })
153
+ }
154
+
155
+ imageSchema() {
156
+ return z
157
+ .object({
158
+ id: z.string(),
159
+ type: z.string(),
160
+ title: z.string(),
161
+ modified_on: z.string(),
162
+ width: z.number().nullish(),
163
+ height: z.number().nullish(),
164
+ })
165
+ .transform((image) => ({
166
+ ...image,
167
+ url: `https://${this.#apiDomain}/assets/${image.id}`,
168
+ }))
169
+ }
170
+
171
+ fileSchema() {
172
+ return z
173
+ .object({
174
+ id: z.string(),
175
+ type: z.string(),
176
+ title: z.string(),
177
+ modified_on: z.string(),
178
+ })
179
+ .transform((file) => ({
180
+ ...file,
181
+ url: `https://${this.#apiDomain}/assets/${file.id}`,
182
+ }))
183
+ }
184
+ }