@kaleabdenbel/llmweb 1.0.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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/adapters/express.d.ts +5 -0
- package/dist/adapters/express.js +160 -0
- package/dist/adapters/express.mjs +19 -0
- package/dist/adapters/next.d.ts +18 -0
- package/dist/adapters/next.js +167 -0
- package/dist/adapters/next.mjs +25 -0
- package/dist/chunk-PYG5H54N.mjs +122 -0
- package/dist/core/merger.d.ts +7 -0
- package/dist/core/resolver.d.ts +12 -0
- package/dist/core/transformer.d.ts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +149 -0
- package/dist/index.mjs +8 -0
- package/dist/types/index.d.ts +23 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaleab Denbel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# llmweb 🛸
|
|
2
|
+
|
|
3
|
+
The data compiler that transforms messy application state into LLM-readable truth.
|
|
4
|
+
|
|
5
|
+
## The Mental Model
|
|
6
|
+
|
|
7
|
+
`llmweb` is not just a metadata library. it's a compiler for your website's soul.
|
|
8
|
+
|
|
9
|
+
- **Static Truth**: Your contract with the world. Deterministic. Versioned.
|
|
10
|
+
- **Dynamic Truth**: Live data from APIs, Server Actions, or DB calls.
|
|
11
|
+
- **The Engine**: Compiles these into a single, structured JSON object for LLMs.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install llmweb
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start (Next.js)
|
|
20
|
+
|
|
21
|
+
### 1. Define your Truth
|
|
22
|
+
|
|
23
|
+
Create an LLM route to expose your site's data.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// app/llm/route.ts
|
|
27
|
+
import { createLLMHandler } from "llmweb/adapters/next";
|
|
28
|
+
import { getStaffList } from "@/actions/getStaffList";
|
|
29
|
+
|
|
30
|
+
const config = {
|
|
31
|
+
static: "./llm.json", // Path to your static contract
|
|
32
|
+
|
|
33
|
+
dynamic: {
|
|
34
|
+
announcements: {
|
|
35
|
+
from: { type: "fetch", url: "https://api.site.com/news" },
|
|
36
|
+
map: {
|
|
37
|
+
title: "headline",
|
|
38
|
+
body: "content",
|
|
39
|
+
publishedAt: "date",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
staff: {
|
|
43
|
+
from: { type: "fn", call: getStaffList },
|
|
44
|
+
map: {
|
|
45
|
+
name: "full_name",
|
|
46
|
+
role: "role_title",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const GET = createLLMHandler(config);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Use the Component
|
|
56
|
+
|
|
57
|
+
Render the compiled truth directly in your admin or SEO pages.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { LLMJson } from "llmweb/adapters/next";
|
|
61
|
+
|
|
62
|
+
export default function LlmTruthPage() {
|
|
63
|
+
return (
|
|
64
|
+
<div className="p-8">
|
|
65
|
+
<h1>LLM Truth Engine</h1>
|
|
66
|
+
<LLMJson
|
|
67
|
+
config={config}
|
|
68
|
+
className="bg-gray-900 text-green-400 p-4 rounded"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## The Engine Features
|
|
76
|
+
|
|
77
|
+
- **Brutal Mapping**: Rename, pick, omit, and compute fields easily.
|
|
78
|
+
- **Parallel Resolution**: All dynamic sources are resolved in parallel.
|
|
79
|
+
- **Error Boundaries**: Configurable `failLoudly` mode for strict data integrity.
|
|
80
|
+
- **Timeouts**: Prevent slow APIs from hanging your truth route.
|
|
81
|
+
- **Deterministic Merging**: Static structure defines the slots; dynamic data fills them.
|
|
82
|
+
|
|
83
|
+
## Advanced Mapping
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
map: {
|
|
87
|
+
fullName: (s) => `${s.first} ${s.last}`, // Computed field
|
|
88
|
+
meta: {
|
|
89
|
+
tags: "categories", // Nested renames
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/express.ts
|
|
21
|
+
var express_exports = {};
|
|
22
|
+
__export(express_exports, {
|
|
23
|
+
llmMiddleware: () => llmMiddleware
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(express_exports);
|
|
26
|
+
|
|
27
|
+
// src/core/resolver.ts
|
|
28
|
+
async function resolveAll(dynamicSources, options = {}) {
|
|
29
|
+
const keys = Object.keys(dynamicSources);
|
|
30
|
+
const results = await Promise.allSettled(
|
|
31
|
+
keys.map((key) => resolveSource(dynamicSources[key], options.timeout))
|
|
32
|
+
);
|
|
33
|
+
const data = {};
|
|
34
|
+
results.forEach((result, index) => {
|
|
35
|
+
const key = keys[index];
|
|
36
|
+
if (result.status === "fulfilled") {
|
|
37
|
+
data[key] = result.value;
|
|
38
|
+
} else {
|
|
39
|
+
console.error(`[llmweb] Failed to resolve source "${key}":`, result.reason);
|
|
40
|
+
data[key] = null;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
async function resolveSource(source, timeoutMs) {
|
|
46
|
+
const { from } = source;
|
|
47
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
48
|
+
const signal = controller?.signal;
|
|
49
|
+
const timeoutPromise = timeoutMs ? new Promise(
|
|
50
|
+
(_, reject) => setTimeout(() => {
|
|
51
|
+
controller?.abort();
|
|
52
|
+
reject(new Error(`Timeout of ${timeoutMs}ms exceeded`));
|
|
53
|
+
}, timeoutMs)
|
|
54
|
+
) : null;
|
|
55
|
+
const resolvePromise = (async () => {
|
|
56
|
+
if (from.type === "fetch") {
|
|
57
|
+
if (!from.url) throw new Error("Fetch source requires a URL");
|
|
58
|
+
const response = await fetch(from.url, { signal });
|
|
59
|
+
if (!response.ok) throw new Error(`HTTP error ${response.status} for ${from.url}`);
|
|
60
|
+
return response.json();
|
|
61
|
+
}
|
|
62
|
+
if (from.type === "fn") {
|
|
63
|
+
if (!from.call) throw new Error('Function source requires a "call" property');
|
|
64
|
+
return from.call();
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`Unsupported source type: ${from.type}`);
|
|
67
|
+
})();
|
|
68
|
+
if (timeoutPromise) {
|
|
69
|
+
return Promise.race([resolvePromise, timeoutPromise]);
|
|
70
|
+
}
|
|
71
|
+
return resolvePromise;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/core/transformer.ts
|
|
75
|
+
function transform(sourceData, schema) {
|
|
76
|
+
if (!sourceData || typeof sourceData !== "object") return sourceData;
|
|
77
|
+
const result = {};
|
|
78
|
+
for (const [targetKey, mapping] of Object.entries(schema)) {
|
|
79
|
+
if (typeof mapping === "string") {
|
|
80
|
+
result[targetKey] = getValueByPath(sourceData, mapping);
|
|
81
|
+
} else if (typeof mapping === "function") {
|
|
82
|
+
result[targetKey] = mapping(sourceData);
|
|
83
|
+
} else if (typeof mapping === "object" && mapping !== null) {
|
|
84
|
+
result[targetKey] = transform(sourceData, mapping);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function getValueByPath(obj, path) {
|
|
90
|
+
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/core/merger.ts
|
|
94
|
+
function merge(staticData, dynamicResults) {
|
|
95
|
+
if (!staticData) return dynamicResults;
|
|
96
|
+
return {
|
|
97
|
+
...staticData,
|
|
98
|
+
...dynamicResults
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/index.ts
|
|
103
|
+
var import_node_fs = require("fs");
|
|
104
|
+
var import_node_path = require("path");
|
|
105
|
+
async function createLLMSource(config, options = {}) {
|
|
106
|
+
let staticTruth = {};
|
|
107
|
+
if (config.static) {
|
|
108
|
+
try {
|
|
109
|
+
const staticPath = config.static.startsWith("/") ? config.static : (0, import_node_path.join)(process.cwd(), config.static);
|
|
110
|
+
const content = (0, import_node_fs.readFileSync)(staticPath, "utf-8");
|
|
111
|
+
staticTruth = JSON.parse(content);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (options.failLoudly) {
|
|
114
|
+
throw new Error(`[llmweb] Static Truth Error: Failed to load/parse JSON at ${config.static}. ${error}`);
|
|
115
|
+
}
|
|
116
|
+
console.warn(`[llmweb] Warning: Could not load static JSON at ${config.static}. Proceeding with dynamic data only.`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const dynamicTruth = {};
|
|
120
|
+
if (config.dynamic) {
|
|
121
|
+
const rawResults = await resolveAll(config.dynamic, { timeout: options.timeout });
|
|
122
|
+
for (const [key, source] of Object.entries(config.dynamic)) {
|
|
123
|
+
const rawData = rawResults[key];
|
|
124
|
+
if (rawData === null && options.failLoudly) {
|
|
125
|
+
throw new Error(`[llmweb] Dynamic Truth Error: Source "${key}" failed to resolve.`);
|
|
126
|
+
}
|
|
127
|
+
if (rawData && source.map) {
|
|
128
|
+
try {
|
|
129
|
+
dynamicTruth[key] = transform(rawData, source.map);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (options.failLoudly) {
|
|
132
|
+
throw new Error(`[llmweb] Transformation Error: Failed to map source "${key}". ${error}`);
|
|
133
|
+
}
|
|
134
|
+
console.error(`[llmweb] Warning: Mapping failed for "${key}". Using raw data.`);
|
|
135
|
+
dynamicTruth[key] = rawData;
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
dynamicTruth[key] = rawData;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return merge(staticTruth, dynamicTruth);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/adapters/express.ts
|
|
146
|
+
function llmMiddleware(config) {
|
|
147
|
+
return async (req, res, next) => {
|
|
148
|
+
try {
|
|
149
|
+
const truth = await createLLMSource(config);
|
|
150
|
+
res.status(200).json(truth);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("[llmweb] Express middleware error:", error);
|
|
153
|
+
res.status(500).json({ error: "Internal Server Error" });
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
158
|
+
0 && (module.exports = {
|
|
159
|
+
llmMiddleware
|
|
160
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createLLMSource
|
|
3
|
+
} from "../chunk-PYG5H54N.mjs";
|
|
4
|
+
|
|
5
|
+
// src/adapters/express.ts
|
|
6
|
+
function llmMiddleware(config) {
|
|
7
|
+
return async (req, res, next) => {
|
|
8
|
+
try {
|
|
9
|
+
const truth = await createLLMSource(config);
|
|
10
|
+
res.status(200).json(truth);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error("[llmweb] Express middleware error:", error);
|
|
13
|
+
res.status(500).json({ error: "Internal Server Error" });
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
llmMiddleware
|
|
19
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LLMConfig } from "../types";
|
|
2
|
+
interface LLMJsonProps {
|
|
3
|
+
config: LLMConfig;
|
|
4
|
+
/**
|
|
5
|
+
* Optional custom styling for the rendered JSON if viewing in a browser.
|
|
6
|
+
*/
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A Next.js Server Component that resolves the LLM truth and renders it.
|
|
11
|
+
* By default, it renders a <pre> tag with the JSON string.
|
|
12
|
+
*/
|
|
13
|
+
export declare function LLMJson({ config, className }: LLMJsonProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
14
|
+
/**
|
|
15
|
+
* A helper for Next.js App Router Route Handlers (route.ts).
|
|
16
|
+
*/
|
|
17
|
+
export declare function createLLMHandler(config: LLMConfig): () => Promise<Response>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/next.tsx
|
|
21
|
+
var next_exports = {};
|
|
22
|
+
__export(next_exports, {
|
|
23
|
+
LLMJson: () => LLMJson,
|
|
24
|
+
createLLMHandler: () => createLLMHandler
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(next_exports);
|
|
27
|
+
|
|
28
|
+
// src/core/resolver.ts
|
|
29
|
+
async function resolveAll(dynamicSources, options = {}) {
|
|
30
|
+
const keys = Object.keys(dynamicSources);
|
|
31
|
+
const results = await Promise.allSettled(
|
|
32
|
+
keys.map((key) => resolveSource(dynamicSources[key], options.timeout))
|
|
33
|
+
);
|
|
34
|
+
const data = {};
|
|
35
|
+
results.forEach((result, index) => {
|
|
36
|
+
const key = keys[index];
|
|
37
|
+
if (result.status === "fulfilled") {
|
|
38
|
+
data[key] = result.value;
|
|
39
|
+
} else {
|
|
40
|
+
console.error(`[llmweb] Failed to resolve source "${key}":`, result.reason);
|
|
41
|
+
data[key] = null;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
async function resolveSource(source, timeoutMs) {
|
|
47
|
+
const { from } = source;
|
|
48
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
49
|
+
const signal = controller?.signal;
|
|
50
|
+
const timeoutPromise = timeoutMs ? new Promise(
|
|
51
|
+
(_, reject) => setTimeout(() => {
|
|
52
|
+
controller?.abort();
|
|
53
|
+
reject(new Error(`Timeout of ${timeoutMs}ms exceeded`));
|
|
54
|
+
}, timeoutMs)
|
|
55
|
+
) : null;
|
|
56
|
+
const resolvePromise = (async () => {
|
|
57
|
+
if (from.type === "fetch") {
|
|
58
|
+
if (!from.url) throw new Error("Fetch source requires a URL");
|
|
59
|
+
const response = await fetch(from.url, { signal });
|
|
60
|
+
if (!response.ok) throw new Error(`HTTP error ${response.status} for ${from.url}`);
|
|
61
|
+
return response.json();
|
|
62
|
+
}
|
|
63
|
+
if (from.type === "fn") {
|
|
64
|
+
if (!from.call) throw new Error('Function source requires a "call" property');
|
|
65
|
+
return from.call();
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`Unsupported source type: ${from.type}`);
|
|
68
|
+
})();
|
|
69
|
+
if (timeoutPromise) {
|
|
70
|
+
return Promise.race([resolvePromise, timeoutPromise]);
|
|
71
|
+
}
|
|
72
|
+
return resolvePromise;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/core/transformer.ts
|
|
76
|
+
function transform(sourceData, schema) {
|
|
77
|
+
if (!sourceData || typeof sourceData !== "object") return sourceData;
|
|
78
|
+
const result = {};
|
|
79
|
+
for (const [targetKey, mapping] of Object.entries(schema)) {
|
|
80
|
+
if (typeof mapping === "string") {
|
|
81
|
+
result[targetKey] = getValueByPath(sourceData, mapping);
|
|
82
|
+
} else if (typeof mapping === "function") {
|
|
83
|
+
result[targetKey] = mapping(sourceData);
|
|
84
|
+
} else if (typeof mapping === "object" && mapping !== null) {
|
|
85
|
+
result[targetKey] = transform(sourceData, mapping);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
function getValueByPath(obj, path) {
|
|
91
|
+
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/core/merger.ts
|
|
95
|
+
function merge(staticData, dynamicResults) {
|
|
96
|
+
if (!staticData) return dynamicResults;
|
|
97
|
+
return {
|
|
98
|
+
...staticData,
|
|
99
|
+
...dynamicResults
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/index.ts
|
|
104
|
+
var import_node_fs = require("fs");
|
|
105
|
+
var import_node_path = require("path");
|
|
106
|
+
async function createLLMSource(config, options = {}) {
|
|
107
|
+
let staticTruth = {};
|
|
108
|
+
if (config.static) {
|
|
109
|
+
try {
|
|
110
|
+
const staticPath = config.static.startsWith("/") ? config.static : (0, import_node_path.join)(process.cwd(), config.static);
|
|
111
|
+
const content = (0, import_node_fs.readFileSync)(staticPath, "utf-8");
|
|
112
|
+
staticTruth = JSON.parse(content);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (options.failLoudly) {
|
|
115
|
+
throw new Error(`[llmweb] Static Truth Error: Failed to load/parse JSON at ${config.static}. ${error}`);
|
|
116
|
+
}
|
|
117
|
+
console.warn(`[llmweb] Warning: Could not load static JSON at ${config.static}. Proceeding with dynamic data only.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const dynamicTruth = {};
|
|
121
|
+
if (config.dynamic) {
|
|
122
|
+
const rawResults = await resolveAll(config.dynamic, { timeout: options.timeout });
|
|
123
|
+
for (const [key, source] of Object.entries(config.dynamic)) {
|
|
124
|
+
const rawData = rawResults[key];
|
|
125
|
+
if (rawData === null && options.failLoudly) {
|
|
126
|
+
throw new Error(`[llmweb] Dynamic Truth Error: Source "${key}" failed to resolve.`);
|
|
127
|
+
}
|
|
128
|
+
if (rawData && source.map) {
|
|
129
|
+
try {
|
|
130
|
+
dynamicTruth[key] = transform(rawData, source.map);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (options.failLoudly) {
|
|
133
|
+
throw new Error(`[llmweb] Transformation Error: Failed to map source "${key}". ${error}`);
|
|
134
|
+
}
|
|
135
|
+
console.error(`[llmweb] Warning: Mapping failed for "${key}". Using raw data.`);
|
|
136
|
+
dynamicTruth[key] = rawData;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
dynamicTruth[key] = rawData;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return merge(staticTruth, dynamicTruth);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/adapters/next.tsx
|
|
147
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
148
|
+
async function LLMJson({ config, className }) {
|
|
149
|
+
const truth = await createLLMSource(config);
|
|
150
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { id: "llm-truth", className, children: JSON.stringify(truth, null, 2) });
|
|
151
|
+
}
|
|
152
|
+
function createLLMHandler(config) {
|
|
153
|
+
return async () => {
|
|
154
|
+
const truth = await createLLMSource(config);
|
|
155
|
+
return new Response(JSON.stringify(truth), {
|
|
156
|
+
status: 200,
|
|
157
|
+
headers: {
|
|
158
|
+
"Content-Type": "application/json"
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
164
|
+
0 && (module.exports = {
|
|
165
|
+
LLMJson,
|
|
166
|
+
createLLMHandler
|
|
167
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createLLMSource
|
|
3
|
+
} from "../chunk-PYG5H54N.mjs";
|
|
4
|
+
|
|
5
|
+
// src/adapters/next.tsx
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
async function LLMJson({ config, className }) {
|
|
8
|
+
const truth = await createLLMSource(config);
|
|
9
|
+
return /* @__PURE__ */ jsx("pre", { id: "llm-truth", className, children: JSON.stringify(truth, null, 2) });
|
|
10
|
+
}
|
|
11
|
+
function createLLMHandler(config) {
|
|
12
|
+
return async () => {
|
|
13
|
+
const truth = await createLLMSource(config);
|
|
14
|
+
return new Response(JSON.stringify(truth), {
|
|
15
|
+
status: 200,
|
|
16
|
+
headers: {
|
|
17
|
+
"Content-Type": "application/json"
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
LLMJson,
|
|
24
|
+
createLLMHandler
|
|
25
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// src/core/resolver.ts
|
|
2
|
+
async function resolveAll(dynamicSources, options = {}) {
|
|
3
|
+
const keys = Object.keys(dynamicSources);
|
|
4
|
+
const results = await Promise.allSettled(
|
|
5
|
+
keys.map((key) => resolveSource(dynamicSources[key], options.timeout))
|
|
6
|
+
);
|
|
7
|
+
const data = {};
|
|
8
|
+
results.forEach((result, index) => {
|
|
9
|
+
const key = keys[index];
|
|
10
|
+
if (result.status === "fulfilled") {
|
|
11
|
+
data[key] = result.value;
|
|
12
|
+
} else {
|
|
13
|
+
console.error(`[llmweb] Failed to resolve source "${key}":`, result.reason);
|
|
14
|
+
data[key] = null;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return data;
|
|
18
|
+
}
|
|
19
|
+
async function resolveSource(source, timeoutMs) {
|
|
20
|
+
const { from } = source;
|
|
21
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
22
|
+
const signal = controller?.signal;
|
|
23
|
+
const timeoutPromise = timeoutMs ? new Promise(
|
|
24
|
+
(_, reject) => setTimeout(() => {
|
|
25
|
+
controller?.abort();
|
|
26
|
+
reject(new Error(`Timeout of ${timeoutMs}ms exceeded`));
|
|
27
|
+
}, timeoutMs)
|
|
28
|
+
) : null;
|
|
29
|
+
const resolvePromise = (async () => {
|
|
30
|
+
if (from.type === "fetch") {
|
|
31
|
+
if (!from.url) throw new Error("Fetch source requires a URL");
|
|
32
|
+
const response = await fetch(from.url, { signal });
|
|
33
|
+
if (!response.ok) throw new Error(`HTTP error ${response.status} for ${from.url}`);
|
|
34
|
+
return response.json();
|
|
35
|
+
}
|
|
36
|
+
if (from.type === "fn") {
|
|
37
|
+
if (!from.call) throw new Error('Function source requires a "call" property');
|
|
38
|
+
return from.call();
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Unsupported source type: ${from.type}`);
|
|
41
|
+
})();
|
|
42
|
+
if (timeoutPromise) {
|
|
43
|
+
return Promise.race([resolvePromise, timeoutPromise]);
|
|
44
|
+
}
|
|
45
|
+
return resolvePromise;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/core/transformer.ts
|
|
49
|
+
function transform(sourceData, schema) {
|
|
50
|
+
if (!sourceData || typeof sourceData !== "object") return sourceData;
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const [targetKey, mapping] of Object.entries(schema)) {
|
|
53
|
+
if (typeof mapping === "string") {
|
|
54
|
+
result[targetKey] = getValueByPath(sourceData, mapping);
|
|
55
|
+
} else if (typeof mapping === "function") {
|
|
56
|
+
result[targetKey] = mapping(sourceData);
|
|
57
|
+
} else if (typeof mapping === "object" && mapping !== null) {
|
|
58
|
+
result[targetKey] = transform(sourceData, mapping);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
function getValueByPath(obj, path) {
|
|
64
|
+
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/core/merger.ts
|
|
68
|
+
function merge(staticData, dynamicResults) {
|
|
69
|
+
if (!staticData) return dynamicResults;
|
|
70
|
+
return {
|
|
71
|
+
...staticData,
|
|
72
|
+
...dynamicResults
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/index.ts
|
|
77
|
+
import { readFileSync } from "fs";
|
|
78
|
+
import { join } from "path";
|
|
79
|
+
async function createLLMSource(config, options = {}) {
|
|
80
|
+
let staticTruth = {};
|
|
81
|
+
if (config.static) {
|
|
82
|
+
try {
|
|
83
|
+
const staticPath = config.static.startsWith("/") ? config.static : join(process.cwd(), config.static);
|
|
84
|
+
const content = readFileSync(staticPath, "utf-8");
|
|
85
|
+
staticTruth = JSON.parse(content);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (options.failLoudly) {
|
|
88
|
+
throw new Error(`[llmweb] Static Truth Error: Failed to load/parse JSON at ${config.static}. ${error}`);
|
|
89
|
+
}
|
|
90
|
+
console.warn(`[llmweb] Warning: Could not load static JSON at ${config.static}. Proceeding with dynamic data only.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const dynamicTruth = {};
|
|
94
|
+
if (config.dynamic) {
|
|
95
|
+
const rawResults = await resolveAll(config.dynamic, { timeout: options.timeout });
|
|
96
|
+
for (const [key, source] of Object.entries(config.dynamic)) {
|
|
97
|
+
const rawData = rawResults[key];
|
|
98
|
+
if (rawData === null && options.failLoudly) {
|
|
99
|
+
throw new Error(`[llmweb] Dynamic Truth Error: Source "${key}" failed to resolve.`);
|
|
100
|
+
}
|
|
101
|
+
if (rawData && source.map) {
|
|
102
|
+
try {
|
|
103
|
+
dynamicTruth[key] = transform(rawData, source.map);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (options.failLoudly) {
|
|
106
|
+
throw new Error(`[llmweb] Transformation Error: Failed to map source "${key}". ${error}`);
|
|
107
|
+
}
|
|
108
|
+
console.error(`[llmweb] Warning: Mapping failed for "${key}". Using raw data.`);
|
|
109
|
+
dynamicTruth[key] = rawData;
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
dynamicTruth[key] = rawData;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return merge(staticTruth, dynamicTruth);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
transform,
|
|
121
|
+
createLLMSource
|
|
122
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DynamicSource } from '../types';
|
|
2
|
+
export interface ResolvedData {
|
|
3
|
+
[key: string]: any;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolves all dynamic sources in parallel.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveAll(dynamicSources: {
|
|
9
|
+
[key: string]: DynamicSource;
|
|
10
|
+
}, options?: {
|
|
11
|
+
timeout?: number;
|
|
12
|
+
}): Promise<ResolvedData>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { MapSchema } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Transforms source data according to a MapSchema.
|
|
4
|
+
* The schema keys represent the target structure, and the values
|
|
5
|
+
* represent the source keys or transformation functions.
|
|
6
|
+
*/
|
|
7
|
+
export declare function transform(sourceData: any, schema: MapSchema): any;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LLMConfig, TransformerOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The main "Compiler" for LLM truth.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createLLMSource(config: LLMConfig, options?: TransformerOptions): Promise<any>;
|
|
6
|
+
export * from './types';
|
|
7
|
+
export { transform } from './core/transformer';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createLLMSource: () => createLLMSource,
|
|
24
|
+
transform: () => transform
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/core/resolver.ts
|
|
29
|
+
async function resolveAll(dynamicSources, options = {}) {
|
|
30
|
+
const keys = Object.keys(dynamicSources);
|
|
31
|
+
const results = await Promise.allSettled(
|
|
32
|
+
keys.map((key) => resolveSource(dynamicSources[key], options.timeout))
|
|
33
|
+
);
|
|
34
|
+
const data = {};
|
|
35
|
+
results.forEach((result, index) => {
|
|
36
|
+
const key = keys[index];
|
|
37
|
+
if (result.status === "fulfilled") {
|
|
38
|
+
data[key] = result.value;
|
|
39
|
+
} else {
|
|
40
|
+
console.error(`[llmweb] Failed to resolve source "${key}":`, result.reason);
|
|
41
|
+
data[key] = null;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
async function resolveSource(source, timeoutMs) {
|
|
47
|
+
const { from } = source;
|
|
48
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
49
|
+
const signal = controller?.signal;
|
|
50
|
+
const timeoutPromise = timeoutMs ? new Promise(
|
|
51
|
+
(_, reject) => setTimeout(() => {
|
|
52
|
+
controller?.abort();
|
|
53
|
+
reject(new Error(`Timeout of ${timeoutMs}ms exceeded`));
|
|
54
|
+
}, timeoutMs)
|
|
55
|
+
) : null;
|
|
56
|
+
const resolvePromise = (async () => {
|
|
57
|
+
if (from.type === "fetch") {
|
|
58
|
+
if (!from.url) throw new Error("Fetch source requires a URL");
|
|
59
|
+
const response = await fetch(from.url, { signal });
|
|
60
|
+
if (!response.ok) throw new Error(`HTTP error ${response.status} for ${from.url}`);
|
|
61
|
+
return response.json();
|
|
62
|
+
}
|
|
63
|
+
if (from.type === "fn") {
|
|
64
|
+
if (!from.call) throw new Error('Function source requires a "call" property');
|
|
65
|
+
return from.call();
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`Unsupported source type: ${from.type}`);
|
|
68
|
+
})();
|
|
69
|
+
if (timeoutPromise) {
|
|
70
|
+
return Promise.race([resolvePromise, timeoutPromise]);
|
|
71
|
+
}
|
|
72
|
+
return resolvePromise;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/core/transformer.ts
|
|
76
|
+
function transform(sourceData, schema) {
|
|
77
|
+
if (!sourceData || typeof sourceData !== "object") return sourceData;
|
|
78
|
+
const result = {};
|
|
79
|
+
for (const [targetKey, mapping] of Object.entries(schema)) {
|
|
80
|
+
if (typeof mapping === "string") {
|
|
81
|
+
result[targetKey] = getValueByPath(sourceData, mapping);
|
|
82
|
+
} else if (typeof mapping === "function") {
|
|
83
|
+
result[targetKey] = mapping(sourceData);
|
|
84
|
+
} else if (typeof mapping === "object" && mapping !== null) {
|
|
85
|
+
result[targetKey] = transform(sourceData, mapping);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
function getValueByPath(obj, path) {
|
|
91
|
+
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/core/merger.ts
|
|
95
|
+
function merge(staticData, dynamicResults) {
|
|
96
|
+
if (!staticData) return dynamicResults;
|
|
97
|
+
return {
|
|
98
|
+
...staticData,
|
|
99
|
+
...dynamicResults
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/index.ts
|
|
104
|
+
var import_node_fs = require("fs");
|
|
105
|
+
var import_node_path = require("path");
|
|
106
|
+
async function createLLMSource(config, options = {}) {
|
|
107
|
+
let staticTruth = {};
|
|
108
|
+
if (config.static) {
|
|
109
|
+
try {
|
|
110
|
+
const staticPath = config.static.startsWith("/") ? config.static : (0, import_node_path.join)(process.cwd(), config.static);
|
|
111
|
+
const content = (0, import_node_fs.readFileSync)(staticPath, "utf-8");
|
|
112
|
+
staticTruth = JSON.parse(content);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (options.failLoudly) {
|
|
115
|
+
throw new Error(`[llmweb] Static Truth Error: Failed to load/parse JSON at ${config.static}. ${error}`);
|
|
116
|
+
}
|
|
117
|
+
console.warn(`[llmweb] Warning: Could not load static JSON at ${config.static}. Proceeding with dynamic data only.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const dynamicTruth = {};
|
|
121
|
+
if (config.dynamic) {
|
|
122
|
+
const rawResults = await resolveAll(config.dynamic, { timeout: options.timeout });
|
|
123
|
+
for (const [key, source] of Object.entries(config.dynamic)) {
|
|
124
|
+
const rawData = rawResults[key];
|
|
125
|
+
if (rawData === null && options.failLoudly) {
|
|
126
|
+
throw new Error(`[llmweb] Dynamic Truth Error: Source "${key}" failed to resolve.`);
|
|
127
|
+
}
|
|
128
|
+
if (rawData && source.map) {
|
|
129
|
+
try {
|
|
130
|
+
dynamicTruth[key] = transform(rawData, source.map);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (options.failLoudly) {
|
|
133
|
+
throw new Error(`[llmweb] Transformation Error: Failed to map source "${key}". ${error}`);
|
|
134
|
+
}
|
|
135
|
+
console.error(`[llmweb] Warning: Mapping failed for "${key}". Using raw data.`);
|
|
136
|
+
dynamicTruth[key] = rawData;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
dynamicTruth[key] = rawData;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return merge(staticTruth, dynamicTruth);
|
|
144
|
+
}
|
|
145
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
146
|
+
0 && (module.exports = {
|
|
147
|
+
createLLMSource,
|
|
148
|
+
transform
|
|
149
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type SourceType = 'fetch' | 'fn';
|
|
2
|
+
export interface SourceDefinition {
|
|
3
|
+
type: SourceType;
|
|
4
|
+
url?: string;
|
|
5
|
+
call?: (...args: any[]) => Promise<any> | any;
|
|
6
|
+
}
|
|
7
|
+
export interface MapSchema {
|
|
8
|
+
[targetKey: string]: string | ((sourceData: any) => any) | MapSchema;
|
|
9
|
+
}
|
|
10
|
+
export interface DynamicSource {
|
|
11
|
+
from: SourceDefinition;
|
|
12
|
+
map?: MapSchema;
|
|
13
|
+
}
|
|
14
|
+
export interface LLMConfig {
|
|
15
|
+
static?: string;
|
|
16
|
+
dynamic?: {
|
|
17
|
+
[key: string]: DynamicSource;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface TransformerOptions {
|
|
21
|
+
failLoudly?: boolean;
|
|
22
|
+
timeout?: number;
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kaleabdenbel/llmweb",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A compiler for LLM-readable truth from static and dynamic sources.",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.mjs",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./adapters/next": {
|
|
18
|
+
"import": "./dist/adapters/next.mjs",
|
|
19
|
+
"require": "./dist/adapters/next.js",
|
|
20
|
+
"types": "./dist/adapters/next.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./adapters/express": {
|
|
23
|
+
"import": "./dist/adapters/express.mjs",
|
|
24
|
+
"require": "./dist/adapters/express.js",
|
|
25
|
+
"types": "./dist/adapters/express.d.ts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup src/index.ts src/adapters/next.tsx src/adapters/express.ts --format cjs,esm --dts --clean",
|
|
35
|
+
"dev": "tsup src/index.ts src/adapters/next.tsx src/adapters/express.ts --format cjs,esm --watch --dts",
|
|
36
|
+
"lint": "tsc",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"llm",
|
|
41
|
+
"json",
|
|
42
|
+
"nextjs",
|
|
43
|
+
"api",
|
|
44
|
+
"metadata"
|
|
45
|
+
],
|
|
46
|
+
"author": "Kaleab Denbel",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"typescript": "^5.0.0",
|
|
50
|
+
"tsup": "^8.0.0",
|
|
51
|
+
"vitest": "^1.0.0",
|
|
52
|
+
"@types/node": "^20.0.0",
|
|
53
|
+
"@types/react": "^18.0.0",
|
|
54
|
+
"react": "^18.0.0",
|
|
55
|
+
"next": "^14.0.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": ">=18",
|
|
59
|
+
"next": ">=13"
|
|
60
|
+
}
|
|
61
|
+
}
|