@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/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