@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 +9 -4
- package/src/Head.astro +5 -5
- package/src/directus.ts +184 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedgetec-utils/astro-components",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"
|
|
20
|
+
"oxfmt": "^0.28.0",
|
|
21
|
+
"oxlint": "^1.43.0",
|
|
22
|
+
"typescript": "^5.9.3"
|
|
18
23
|
},
|
|
19
24
|
"peerDependencies": {
|
|
20
|
-
"astro": "^5.
|
|
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
|
-
<
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
|
|
66
66
|
<meta property="og:title" content={ogTitle} />
|
|
67
67
|
{
|
|
68
68
|
props.description && (
|
package/src/directus.ts
ADDED
|
@@ -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
|
+
}
|