@piqit/resolvers 0.0.2 → 0.0.3
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 -9
- package/src/edge.ts +0 -15
- package/src/file-markdown.ts +0 -352
- package/src/frontmatter.ts +0 -269
- package/src/index.ts +0 -65
- package/src/markdown.ts +0 -262
- package/src/path-pattern.ts +0 -226
- package/src/static.ts +0 -227
package/src/static.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Static Content Resolver
|
|
3
|
-
*
|
|
4
|
-
* A resolver for querying pre-compiled content in edge environments
|
|
5
|
-
* like Cloudflare Workers where filesystem access is not available.
|
|
6
|
-
*
|
|
7
|
-
* Content is compiled at build time and bundled as a static module.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* // 1. Build script compiles content:
|
|
11
|
-
* // build-content.ts
|
|
12
|
-
* import { fileMarkdown } from "@piqit/resolvers";
|
|
13
|
-
* const posts = fileMarkdown({ ... });
|
|
14
|
-
* const allPosts = await posts.resolve({ scan: {}, filter: {}, select: ["params.*", "frontmatter.*", "body.*"] });
|
|
15
|
-
* await Bun.write("src/generated/content.ts", `export const posts = ${JSON.stringify(allPosts)};`);
|
|
16
|
-
*
|
|
17
|
-
* // 2. Worker imports and uses static resolver:
|
|
18
|
-
* // worker.ts
|
|
19
|
-
* import { posts } from "./generated/content";
|
|
20
|
-
* import { staticContent } from "@piqit/resolvers";
|
|
21
|
-
* import { piq } from "piqit";
|
|
22
|
-
*
|
|
23
|
-
* const postsResolver = staticContent(posts);
|
|
24
|
-
*
|
|
25
|
-
* const results = await piq.from(postsResolver)
|
|
26
|
-
* .filter({ author: "John" })
|
|
27
|
-
* .select("params.slug", "frontmatter.title")
|
|
28
|
-
* .exec();
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
import type { Resolver, StandardSchema } from "piqit"
|
|
32
|
-
|
|
33
|
-
// =============================================================================
|
|
34
|
-
// Filter Helpers
|
|
35
|
-
// =============================================================================
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get a nested value from an object using dot-path notation.
|
|
39
|
-
*/
|
|
40
|
-
function getByPath(obj: unknown, path: string): unknown {
|
|
41
|
-
const parts = path.split(".")
|
|
42
|
-
let current: unknown = obj
|
|
43
|
-
|
|
44
|
-
for (const part of parts) {
|
|
45
|
-
if (current == null || typeof current !== "object") {
|
|
46
|
-
return undefined
|
|
47
|
-
}
|
|
48
|
-
current = (current as Record<string, unknown>)[part]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return current
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Default filter implementation - checks equality on frontmatter fields.
|
|
56
|
-
*/
|
|
57
|
-
function defaultFilter<T>(item: T, filter: Partial<Record<string, unknown>>): boolean {
|
|
58
|
-
const frontmatter = (item as Record<string, unknown>).frontmatter as Record<string, unknown> | undefined
|
|
59
|
-
|
|
60
|
-
if (!frontmatter) {
|
|
61
|
-
return Object.keys(filter).length === 0
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
65
|
-
if (frontmatter[key] !== value) {
|
|
66
|
-
return false
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return true
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Check if scan constraints match params.
|
|
75
|
-
*/
|
|
76
|
-
function matchesScan<T>(item: T, scan: Partial<Record<string, unknown>>): boolean {
|
|
77
|
-
const params = (item as Record<string, unknown>).params as Record<string, unknown> | undefined
|
|
78
|
-
|
|
79
|
-
if (!params) {
|
|
80
|
-
return Object.keys(scan).length === 0
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
for (const [key, value] of Object.entries(scan)) {
|
|
84
|
-
if (value !== undefined && params[key] !== value) {
|
|
85
|
-
return false
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return true
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Select specific fields from an item based on select paths.
|
|
94
|
-
*/
|
|
95
|
-
function selectFields<T extends object>(item: T, selectPaths: string[]): Partial<T> {
|
|
96
|
-
const result: Record<string, unknown> = {}
|
|
97
|
-
|
|
98
|
-
for (const path of selectPaths) {
|
|
99
|
-
// Handle wildcards like "params.*"
|
|
100
|
-
if (path.endsWith(".*")) {
|
|
101
|
-
const namespace = path.slice(0, -2)
|
|
102
|
-
const nsValue = getByPath(item, namespace)
|
|
103
|
-
if (nsValue && typeof nsValue === "object") {
|
|
104
|
-
result[namespace] = { ...nsValue as object }
|
|
105
|
-
}
|
|
106
|
-
} else {
|
|
107
|
-
// Regular path like "params.slug" or "frontmatter.title"
|
|
108
|
-
const parts = path.split(".")
|
|
109
|
-
const namespace = parts[0]
|
|
110
|
-
|
|
111
|
-
// Ensure namespace exists in result
|
|
112
|
-
if (!result[namespace]) {
|
|
113
|
-
result[namespace] = {}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Set the value
|
|
117
|
-
const value = getByPath(item, path)
|
|
118
|
-
if (parts.length === 2) {
|
|
119
|
-
(result[namespace] as Record<string, unknown>)[parts[1]] = value
|
|
120
|
-
} else {
|
|
121
|
-
// Deeper path - just copy the value
|
|
122
|
-
result[namespace] = value
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return result as Partial<T>
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// =============================================================================
|
|
131
|
-
// Schema Factories
|
|
132
|
-
// =============================================================================
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Create a passthrough schema that accepts any value.
|
|
136
|
-
* Used for static content where validation happened at build time.
|
|
137
|
-
*/
|
|
138
|
-
function createPassthroughSchema<T>(): StandardSchema<T> {
|
|
139
|
-
return {
|
|
140
|
-
"~standard": {
|
|
141
|
-
version: 1,
|
|
142
|
-
vendor: "piqit/resolvers/static",
|
|
143
|
-
validate(value: unknown) {
|
|
144
|
-
return { value: value as T }
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// =============================================================================
|
|
151
|
-
// Resolver Factory
|
|
152
|
-
// =============================================================================
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Create a static content resolver from pre-compiled data.
|
|
156
|
-
*
|
|
157
|
-
* This resolver is designed for edge environments like Cloudflare Workers
|
|
158
|
-
* where filesystem access is not available. Content is compiled at build
|
|
159
|
-
* time and bundled as a static module.
|
|
160
|
-
*
|
|
161
|
-
* @param data - Array of pre-compiled content items
|
|
162
|
-
* @returns A resolver that queries the static data
|
|
163
|
-
*
|
|
164
|
-
* @example
|
|
165
|
-
* // In your worker:
|
|
166
|
-
* import { posts } from "./generated/content";
|
|
167
|
-
* import { staticContent } from "@piqit/resolvers";
|
|
168
|
-
* import { piq } from "piqit";
|
|
169
|
-
*
|
|
170
|
-
* const postsResolver = staticContent(posts);
|
|
171
|
-
*
|
|
172
|
-
* export default {
|
|
173
|
-
* async fetch(request: Request) {
|
|
174
|
-
* const results = await piq.from(postsResolver)
|
|
175
|
-
* .scan({ year: "2024" })
|
|
176
|
-
* .select("params.slug", "frontmatter.title")
|
|
177
|
-
* .exec();
|
|
178
|
-
*
|
|
179
|
-
* return Response.json(results);
|
|
180
|
-
* }
|
|
181
|
-
* };
|
|
182
|
-
*/
|
|
183
|
-
export function staticContent<T extends object>(
|
|
184
|
-
data: T[]
|
|
185
|
-
): Resolver<
|
|
186
|
-
StandardSchema<Partial<Record<string, unknown>>>,
|
|
187
|
-
StandardSchema<Partial<Record<string, unknown>>>,
|
|
188
|
-
StandardSchema<T>
|
|
189
|
-
> {
|
|
190
|
-
const scanSchema = createPassthroughSchema<Partial<Record<string, unknown>>>()
|
|
191
|
-
const filterSchema = createPassthroughSchema<Partial<Record<string, unknown>>>()
|
|
192
|
-
const resultSchema = createPassthroughSchema<T>()
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
schema: {
|
|
196
|
-
scanParams: scanSchema,
|
|
197
|
-
filterParams: filterSchema,
|
|
198
|
-
result: resultSchema,
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
async resolve(spec): Promise<Partial<T>[]> {
|
|
202
|
-
let results = [...data]
|
|
203
|
-
|
|
204
|
-
// Apply scan constraints (filter by params)
|
|
205
|
-
if (spec.scan && Object.keys(spec.scan).length > 0) {
|
|
206
|
-
results = results.filter((item) => matchesScan(item, spec.scan!))
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Apply filter constraints (filter by frontmatter)
|
|
210
|
-
if (spec.filter && Object.keys(spec.filter).length > 0) {
|
|
211
|
-
results = results.filter((item) => defaultFilter(item, spec.filter!))
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Apply select to return only requested fields
|
|
215
|
-
if (spec.select && spec.select.length > 0) {
|
|
216
|
-
return results.map((item) => selectFields(item, spec.select))
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return results
|
|
220
|
-
},
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Alias for staticContent - provides a more descriptive name for the use case.
|
|
226
|
-
*/
|
|
227
|
-
export const staticResolver = staticContent
|