@jaypie/fabric 0.1.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/README.md +677 -0
- package/dist/cjs/commander/FabricCommander.d.ts +94 -0
- package/dist/cjs/commander/createCommanderOptions.d.ts +25 -0
- package/dist/cjs/commander/fabricCommand.d.ts +43 -0
- package/dist/cjs/commander/index.cjs +1487 -0
- package/dist/cjs/commander/index.cjs.map +1 -0
- package/dist/cjs/commander/index.d.ts +6 -0
- package/dist/cjs/commander/parseCommanderOptions.d.ts +32 -0
- package/dist/cjs/commander/registerServiceCommand.d.ts +43 -0
- package/dist/cjs/commander/types.d.ts +107 -0
- package/dist/cjs/constants.d.ts +12 -0
- package/dist/cjs/convert-date.d.ts +47 -0
- package/dist/cjs/convert.d.ts +69 -0
- package/dist/cjs/data/FabricData.d.ts +42 -0
- package/dist/cjs/data/index.cjs +1575 -0
- package/dist/cjs/data/index.cjs.map +1 -0
- package/dist/cjs/data/index.d.ts +5 -0
- package/dist/cjs/data/services/archive.d.ts +8 -0
- package/dist/cjs/data/services/create.d.ts +8 -0
- package/dist/cjs/data/services/delete.d.ts +8 -0
- package/dist/cjs/data/services/execute.d.ts +8 -0
- package/dist/cjs/data/services/index.d.ts +7 -0
- package/dist/cjs/data/services/list.d.ts +8 -0
- package/dist/cjs/data/services/read.d.ts +8 -0
- package/dist/cjs/data/services/update.d.ts +8 -0
- package/dist/cjs/data/transforms.d.ts +80 -0
- package/dist/cjs/data/types.d.ts +190 -0
- package/dist/cjs/express/FabricRouter.d.ts +29 -0
- package/dist/cjs/express/fabricExpress.d.ts +16 -0
- package/dist/cjs/express/index.cjs +505 -0
- package/dist/cjs/express/index.cjs.map +1 -0
- package/dist/cjs/express/index.d.ts +3 -0
- package/dist/cjs/express/types.d.ts +51 -0
- package/dist/cjs/helpers/fallback.d.ts +21 -0
- package/dist/cjs/helpers/index.d.ts +3 -0
- package/dist/cjs/helpers/resolvedName.d.ts +24 -0
- package/dist/cjs/http/FabricHttpServer.d.ts +31 -0
- package/dist/cjs/http/authorization.d.ts +30 -0
- package/dist/cjs/http/cors.d.ts +40 -0
- package/dist/cjs/http/fabricHttp.d.ts +28 -0
- package/dist/cjs/http/httpTransform.d.ts +36 -0
- package/dist/cjs/http/index.cjs +1820 -0
- package/dist/cjs/http/index.cjs.map +1 -0
- package/dist/cjs/http/index.d.ts +10 -0
- package/dist/cjs/http/stream.d.ts +185 -0
- package/dist/cjs/http/types.d.ts +343 -0
- package/dist/cjs/index/index.d.ts +8 -0
- package/dist/cjs/index/keyBuilder.d.ts +81 -0
- package/dist/cjs/index/registry.d.ts +56 -0
- package/dist/cjs/index/types.d.ts +54 -0
- package/dist/cjs/index.cjs +1674 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.ts +18 -0
- package/dist/cjs/lambda/createLambdaService.d.ts +33 -0
- package/dist/cjs/lambda/fabricLambda.d.ts +36 -0
- package/dist/cjs/lambda/index.cjs +967 -0
- package/dist/cjs/lambda/index.cjs.map +1 -0
- package/dist/cjs/lambda/index.d.ts +2 -0
- package/dist/cjs/lambda/types.d.ts +68 -0
- package/dist/cjs/llm/createLlmTool.d.ts +40 -0
- package/dist/cjs/llm/fabricTool.d.ts +40 -0
- package/dist/cjs/llm/index.cjs +1107 -0
- package/dist/cjs/llm/index.cjs.map +1 -0
- package/dist/cjs/llm/index.d.ts +3 -0
- package/dist/cjs/llm/inputToJsonSchema.d.ts +32 -0
- package/dist/cjs/llm/types.d.ts +61 -0
- package/dist/cjs/mcp/fabricMcp.d.ts +38 -0
- package/dist/cjs/mcp/index.cjs +938 -0
- package/dist/cjs/mcp/index.cjs.map +1 -0
- package/dist/cjs/mcp/index.d.ts +2 -0
- package/dist/cjs/mcp/registerMcpTool.d.ts +38 -0
- package/dist/cjs/mcp/types.d.ts +60 -0
- package/dist/cjs/models/base.d.ts +209 -0
- package/dist/cjs/resolve-date.d.ts +47 -0
- package/dist/cjs/resolve.d.ts +69 -0
- package/dist/cjs/resolveService.d.ts +49 -0
- package/dist/cjs/service.d.ts +13 -0
- package/dist/cjs/status.d.ts +30 -0
- package/dist/cjs/types/elementaryTypes.d.ts +84 -0
- package/dist/cjs/types/fieldCategory.d.ts +20 -0
- package/dist/cjs/types/fieldDefinition.d.ts +46 -0
- package/dist/cjs/types/index.d.ts +4 -0
- package/dist/cjs/types.d.ts +56 -0
- package/dist/esm/commander/FabricCommander.d.ts +94 -0
- package/dist/esm/commander/createCommanderOptions.d.ts +25 -0
- package/dist/esm/commander/fabricCommand.d.ts +43 -0
- package/dist/esm/commander/index.d.ts +6 -0
- package/dist/esm/commander/index.js +1482 -0
- package/dist/esm/commander/index.js.map +1 -0
- package/dist/esm/commander/parseCommanderOptions.d.ts +32 -0
- package/dist/esm/commander/registerServiceCommand.d.ts +43 -0
- package/dist/esm/commander/types.d.ts +107 -0
- package/dist/esm/constants.d.ts +12 -0
- package/dist/esm/convert-date.d.ts +47 -0
- package/dist/esm/convert.d.ts +69 -0
- package/dist/esm/data/FabricData.d.ts +42 -0
- package/dist/esm/data/index.d.ts +5 -0
- package/dist/esm/data/index.js +1548 -0
- package/dist/esm/data/index.js.map +1 -0
- package/dist/esm/data/services/archive.d.ts +8 -0
- package/dist/esm/data/services/create.d.ts +8 -0
- package/dist/esm/data/services/delete.d.ts +8 -0
- package/dist/esm/data/services/execute.d.ts +8 -0
- package/dist/esm/data/services/index.d.ts +7 -0
- package/dist/esm/data/services/list.d.ts +8 -0
- package/dist/esm/data/services/read.d.ts +8 -0
- package/dist/esm/data/services/update.d.ts +8 -0
- package/dist/esm/data/transforms.d.ts +80 -0
- package/dist/esm/data/types.d.ts +190 -0
- package/dist/esm/express/FabricRouter.d.ts +29 -0
- package/dist/esm/express/fabricExpress.d.ts +16 -0
- package/dist/esm/express/index.d.ts +3 -0
- package/dist/esm/express/index.js +500 -0
- package/dist/esm/express/index.js.map +1 -0
- package/dist/esm/express/types.d.ts +51 -0
- package/dist/esm/helpers/fallback.d.ts +21 -0
- package/dist/esm/helpers/index.d.ts +3 -0
- package/dist/esm/helpers/resolvedName.d.ts +24 -0
- package/dist/esm/http/FabricHttpServer.d.ts +31 -0
- package/dist/esm/http/authorization.d.ts +30 -0
- package/dist/esm/http/cors.d.ts +40 -0
- package/dist/esm/http/fabricHttp.d.ts +28 -0
- package/dist/esm/http/httpTransform.d.ts +36 -0
- package/dist/esm/http/index.d.ts +10 -0
- package/dist/esm/http/index.js +1775 -0
- package/dist/esm/http/index.js.map +1 -0
- package/dist/esm/http/stream.d.ts +185 -0
- package/dist/esm/http/types.d.ts +343 -0
- package/dist/esm/index/index.d.ts +8 -0
- package/dist/esm/index/keyBuilder.d.ts +81 -0
- package/dist/esm/index/registry.d.ts +56 -0
- package/dist/esm/index/types.d.ts +54 -0
- package/dist/esm/index.d.ts +18 -0
- package/dist/esm/index.js +1606 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lambda/createLambdaService.d.ts +33 -0
- package/dist/esm/lambda/fabricLambda.d.ts +36 -0
- package/dist/esm/lambda/index.d.ts +2 -0
- package/dist/esm/lambda/index.js +965 -0
- package/dist/esm/lambda/index.js.map +1 -0
- package/dist/esm/lambda/types.d.ts +68 -0
- package/dist/esm/llm/createLlmTool.d.ts +40 -0
- package/dist/esm/llm/fabricTool.d.ts +40 -0
- package/dist/esm/llm/index.d.ts +3 -0
- package/dist/esm/llm/index.js +1104 -0
- package/dist/esm/llm/index.js.map +1 -0
- package/dist/esm/llm/inputToJsonSchema.d.ts +32 -0
- package/dist/esm/llm/types.d.ts +61 -0
- package/dist/esm/mcp/fabricMcp.d.ts +38 -0
- package/dist/esm/mcp/index.d.ts +2 -0
- package/dist/esm/mcp/index.js +936 -0
- package/dist/esm/mcp/index.js.map +1 -0
- package/dist/esm/mcp/registerMcpTool.d.ts +38 -0
- package/dist/esm/mcp/types.d.ts +60 -0
- package/dist/esm/models/base.d.ts +209 -0
- package/dist/esm/resolve-date.d.ts +47 -0
- package/dist/esm/resolve.d.ts +69 -0
- package/dist/esm/resolveService.d.ts +49 -0
- package/dist/esm/service.d.ts +13 -0
- package/dist/esm/status.d.ts +30 -0
- package/dist/esm/types/elementaryTypes.d.ts +84 -0
- package/dist/esm/types/fieldCategory.d.ts +20 -0
- package/dist/esm/types/fieldDefinition.d.ts +46 -0
- package/dist/esm/types/index.d.ts +4 -0
- package/dist/esm/types.d.ts +56 -0
- package/package.json +122 -0
package/README.md
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
# @jaypie/fabric
|
|
2
|
+
|
|
3
|
+
Jaypie modeling framework with type conversion, service handlers, and adapters for CLI, Lambda, LLM, and MCP.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @jaypie/fabric
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### fabricService
|
|
14
|
+
|
|
15
|
+
Create validated service endpoints with automatic type conversion:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { fabricService } from "@jaypie/fabric";
|
|
19
|
+
|
|
20
|
+
const divisionHandler = fabricService({
|
|
21
|
+
alias: "division",
|
|
22
|
+
description: "Divides two numbers",
|
|
23
|
+
input: {
|
|
24
|
+
numerator: {
|
|
25
|
+
default: 12,
|
|
26
|
+
description: "Number 'on top', which is to be divided",
|
|
27
|
+
type: Number,
|
|
28
|
+
},
|
|
29
|
+
denominator: {
|
|
30
|
+
default: 3,
|
|
31
|
+
description: "Number 'on bottom', how many ways to split the value",
|
|
32
|
+
type: Number,
|
|
33
|
+
validate: (value) => value !== 0,
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
service: ({ numerator, denominator }) => (numerator / denominator),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await divisionHandler(); // =4
|
|
40
|
+
await divisionHandler({ numerator: 24 }); // =8
|
|
41
|
+
await divisionHandler({ numerator: 24, denominator: 2 }); // =12
|
|
42
|
+
await divisionHandler({ numerator: "14", denominator: "7" }); // =2
|
|
43
|
+
await divisionHandler({ numerator: 1, denominator: 0 }); // throws BadRequestError(); does not validate
|
|
44
|
+
await divisionHandler('{ "numerator": "18" }'); // =3; String parses as JSON
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Type Conversion (Fabric Functions)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { fabric, fabricNumber, fabricBoolean, fabricString } from "@jaypie/fabric";
|
|
51
|
+
|
|
52
|
+
fabricBoolean("true"); // true
|
|
53
|
+
fabricBoolean(1); // true
|
|
54
|
+
fabricNumber("42"); // 42
|
|
55
|
+
fabricNumber(true); // 1
|
|
56
|
+
fabricString(true); // "true"
|
|
57
|
+
fabricString(42); // "42"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Commander Adapter
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { Command } from "commander";
|
|
64
|
+
import { fabricService } from "@jaypie/fabric";
|
|
65
|
+
import { fabricCommand } from "@jaypie/fabric/commander";
|
|
66
|
+
|
|
67
|
+
const handler = fabricService({
|
|
68
|
+
alias: "greet",
|
|
69
|
+
description: "Greet a user",
|
|
70
|
+
input: {
|
|
71
|
+
userName: { type: String, flag: "user", letter: "u" },
|
|
72
|
+
loud: { type: Boolean, letter: "l", default: false },
|
|
73
|
+
},
|
|
74
|
+
service: ({ loud, userName }) => {
|
|
75
|
+
const greeting = `Hello, ${userName}!`;
|
|
76
|
+
return loud ? greeting.toUpperCase() : greeting;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const program = new Command();
|
|
81
|
+
fabricCommand({ service: handler, program });
|
|
82
|
+
program.parse();
|
|
83
|
+
// Usage: greet --user Alice -l
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Lambda Adapter
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { fabricService } from "@jaypie/fabric";
|
|
90
|
+
import { fabricLambda } from "@jaypie/fabric/lambda";
|
|
91
|
+
|
|
92
|
+
const evaluationsHandler = fabricService({
|
|
93
|
+
alias: "evaluationsHandler",
|
|
94
|
+
input: {
|
|
95
|
+
count: { type: Number, default: 1 },
|
|
96
|
+
models: { type: [String], default: [] },
|
|
97
|
+
plan: { type: String },
|
|
98
|
+
},
|
|
99
|
+
service: ({ count, models, plan }) => ({
|
|
100
|
+
jobId: `job-${Date.now()}`,
|
|
101
|
+
plan,
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const handler = fabricLambda(evaluationsHandler, {
|
|
106
|
+
secrets: ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### LLM Adapter
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { fabricService } from "@jaypie/fabric";
|
|
114
|
+
import { fabricTool } from "@jaypie/fabric/llm";
|
|
115
|
+
import { Toolkit } from "@jaypie/llm";
|
|
116
|
+
|
|
117
|
+
const handler = fabricService({
|
|
118
|
+
alias: "greet",
|
|
119
|
+
description: "Greet a user by name",
|
|
120
|
+
input: {
|
|
121
|
+
userName: { type: String, description: "The user's name" },
|
|
122
|
+
loud: { type: Boolean, default: false, description: "Shout the greeting" },
|
|
123
|
+
},
|
|
124
|
+
service: ({ userName, loud }) => {
|
|
125
|
+
const greeting = `Hello, ${userName}!`;
|
|
126
|
+
return loud ? greeting.toUpperCase() : greeting;
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const { tool } = fabricTool({ service: handler });
|
|
131
|
+
const toolkit = new Toolkit([tool]);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### MCP Adapter
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
138
|
+
import { fabricService } from "@jaypie/fabric";
|
|
139
|
+
import { fabricMcp } from "@jaypie/fabric/mcp";
|
|
140
|
+
|
|
141
|
+
const handler = fabricService({
|
|
142
|
+
alias: "greet",
|
|
143
|
+
description: "Greet a user by name",
|
|
144
|
+
input: {
|
|
145
|
+
userName: { type: String, description: "The user's name" },
|
|
146
|
+
loud: { type: Boolean, default: false, description: "Shout the greeting" },
|
|
147
|
+
},
|
|
148
|
+
service: ({ userName, loud }) => {
|
|
149
|
+
const greeting = `Hello, ${userName}!`;
|
|
150
|
+
return loud ? greeting.toUpperCase() : greeting;
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const server = new McpServer({ name: "my-server", version: "1.0.0" });
|
|
155
|
+
fabricMcp({ service: handler, server });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Express Adapter
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { Router } from "express";
|
|
162
|
+
import { fabricHttp } from "@jaypie/fabric/http";
|
|
163
|
+
import { fabricExpress, FabricRouter } from "@jaypie/fabric/express";
|
|
164
|
+
|
|
165
|
+
// Create a fabricHttp service
|
|
166
|
+
const userService = fabricHttp({
|
|
167
|
+
alias: "users",
|
|
168
|
+
input: {
|
|
169
|
+
id: { type: String, required: false },
|
|
170
|
+
},
|
|
171
|
+
service: ({ id }) => id ? getUser(id) : listUsers(),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Option 1: Single service as middleware
|
|
175
|
+
const middleware = fabricExpress({ service: userService });
|
|
176
|
+
router.use("/api", middleware);
|
|
177
|
+
// Routes: GET/POST/DELETE /api/users
|
|
178
|
+
|
|
179
|
+
// Option 2: Multiple services with FabricRouter
|
|
180
|
+
const productService = fabricHttp({
|
|
181
|
+
alias: "products",
|
|
182
|
+
service: () => listProducts(),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const router = FabricRouter({
|
|
186
|
+
services: [
|
|
187
|
+
userService,
|
|
188
|
+
productService,
|
|
189
|
+
{
|
|
190
|
+
service: userService,
|
|
191
|
+
path: "/users/:id",
|
|
192
|
+
methods: ["GET", "PUT", "DELETE"],
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
app.use("/v1", router);
|
|
198
|
+
// Routes: /v1/users, /v1/products, /v1/users/:id
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### HTTP Adapter
|
|
202
|
+
|
|
203
|
+
Create HTTP-aware services with built-in authorization and CORS support:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { fabricService } from "@jaypie/fabric";
|
|
207
|
+
import { fabricHttp } from "@jaypie/fabric/http";
|
|
208
|
+
|
|
209
|
+
// Inline service definition
|
|
210
|
+
const userService = fabricHttp({
|
|
211
|
+
alias: "users",
|
|
212
|
+
description: "User management API",
|
|
213
|
+
input: {
|
|
214
|
+
id: { type: String },
|
|
215
|
+
name: { type: String, required: false },
|
|
216
|
+
},
|
|
217
|
+
// Authorization: function receives token from Authorization header
|
|
218
|
+
// (Bearer prefix removed, whitespace stripped)
|
|
219
|
+
authorization: async (token) => {
|
|
220
|
+
const user = await validateJwt(token);
|
|
221
|
+
if (!user) throw new UnauthorizedError();
|
|
222
|
+
return user; // Available in context.auth
|
|
223
|
+
},
|
|
224
|
+
// CORS enabled by default, customize as needed
|
|
225
|
+
cors: {
|
|
226
|
+
origin: ["https://app.example.com"],
|
|
227
|
+
credentials: true,
|
|
228
|
+
},
|
|
229
|
+
service: ({ id, name }, context) => {
|
|
230
|
+
console.log("Authenticated user:", context.auth);
|
|
231
|
+
return { id, name };
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Or wrap an existing fabricService
|
|
236
|
+
const coreService = fabricService({
|
|
237
|
+
alias: "division",
|
|
238
|
+
input: {
|
|
239
|
+
numerator: { type: Number },
|
|
240
|
+
denominator: { type: Number },
|
|
241
|
+
},
|
|
242
|
+
service: ({ numerator, denominator }) => numerator / denominator,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const divisionApi = fabricHttp({
|
|
246
|
+
service: coreService,
|
|
247
|
+
authorization: false, // Public endpoint
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### HTTP Transformation
|
|
252
|
+
|
|
253
|
+
Customize how HTTP context maps to service input:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const customService = fabricHttp({
|
|
257
|
+
alias: "custom",
|
|
258
|
+
input: {
|
|
259
|
+
userId: { type: String },
|
|
260
|
+
action: { type: String },
|
|
261
|
+
},
|
|
262
|
+
// Transform HTTP context to service input
|
|
263
|
+
http: ({ headers, params, body }) => ({
|
|
264
|
+
userId: headers.get("x-user-id") ?? params.userId,
|
|
265
|
+
action: body.action,
|
|
266
|
+
}),
|
|
267
|
+
service: ({ userId, action }) => performAction(userId, action),
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### HTTP Streaming
|
|
272
|
+
|
|
273
|
+
Enable NDJSON streaming for long-running tasks or LLM responses:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { fabricHttp, pipeLlmStream } from "@jaypie/fabric/http";
|
|
277
|
+
import Llm from "@jaypie/llm";
|
|
278
|
+
|
|
279
|
+
const streamingService = fabricHttp({
|
|
280
|
+
alias: "chat",
|
|
281
|
+
input: { message: { type: String } },
|
|
282
|
+
stream: true, // Enable NDJSON streaming
|
|
283
|
+
service: async function* ({ message }, context) {
|
|
284
|
+
// Send progress messages (streamed as message events)
|
|
285
|
+
context.sendMessage({ content: "Processing...", level: "info" });
|
|
286
|
+
|
|
287
|
+
// Stream LLM response
|
|
288
|
+
const llmStream = Llm.stream(message);
|
|
289
|
+
yield* pipeLlmStream(llmStream);
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Stream events use NDJSON format with `stream` as the discriminator field:
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{"stream":"message","content":"Processing...","level":"info"}
|
|
298
|
+
{"stream":"text","content":"Hello"}
|
|
299
|
+
{"stream":"tool_call","toolCall":{"id":"...","name":"...","arguments":"..."}}
|
|
300
|
+
{"stream":"tool_result","toolResult":{"id":"...","name":"...","result":"..."}}
|
|
301
|
+
{"stream":"data","data":{"result":42}}
|
|
302
|
+
{"stream":"error","error":{"status":500,"title":"Error"}}
|
|
303
|
+
{"stream":"noop"}
|
|
304
|
+
{"stream":"complete"}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Streaming utilities:
|
|
308
|
+
- `pipeLlmStream(llmStream)` - Convert @jaypie/llm stream to HTTP events
|
|
309
|
+
- `createStreamContext(writer)` - Create context with `streamText()` and `streamEvent()` methods
|
|
310
|
+
- `createCompleteEvent()` - Create stream completion event
|
|
311
|
+
- `createNoopEvent()` - Create keep-alive signal (empty event)
|
|
312
|
+
- `formatNdjsonEvent(event)` / `formatSseEvent(event)` - Format events for output
|
|
313
|
+
|
|
314
|
+
#### FabricHttpServer (Standalone Lambda)
|
|
315
|
+
|
|
316
|
+
Route multiple services in a single Lambda function without Express:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { fabricHttp, FabricHttpServer } from "@jaypie/fabric/http";
|
|
320
|
+
import { lambdaHandler } from "@jaypie/lambda";
|
|
321
|
+
|
|
322
|
+
// Create HTTP services
|
|
323
|
+
const userService = fabricHttp({
|
|
324
|
+
alias: "users",
|
|
325
|
+
input: { id: { type: String, required: false } },
|
|
326
|
+
service: ({ id }) => id ? getUser(id) : listUsers(),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const productService = fabricHttp({
|
|
330
|
+
alias: "products",
|
|
331
|
+
service: () => listProducts(),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Create standalone server
|
|
335
|
+
const server = FabricHttpServer({
|
|
336
|
+
services: [
|
|
337
|
+
userService,
|
|
338
|
+
productService,
|
|
339
|
+
{ service: userService, path: "/users/:id", methods: ["GET", "PUT"] },
|
|
340
|
+
],
|
|
341
|
+
prefix: "/api", // Optional path prefix
|
|
342
|
+
cors: true, // Server-level CORS (default: true)
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Export as Lambda handler
|
|
346
|
+
export const handler = lambdaHandler(server);
|
|
347
|
+
// Routes: /api/users, /api/products, /api/users/:id
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
FabricHttpServer handles:
|
|
351
|
+
- API Gateway v1 (REST API) and v2 (HTTP API) event formats
|
|
352
|
+
- Route matching by path pattern and HTTP method
|
|
353
|
+
- CORS preflight requests and response headers
|
|
354
|
+
- JSON:API formatted responses (`{ data }` / `{ errors }`)
|
|
355
|
+
- 404 Not Found and 405 Method Not Allowed responses
|
|
356
|
+
|
|
357
|
+
### Data Adapter (FabricData)
|
|
358
|
+
|
|
359
|
+
Generate CRUD HTTP services for Jaypie models backed by DynamoDB:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { FabricData } from "@jaypie/fabric/data";
|
|
363
|
+
import { FabricHttpServer } from "@jaypie/fabric/http";
|
|
364
|
+
|
|
365
|
+
// Basic usage - creates all CRUD services
|
|
366
|
+
const recordServices = FabricData({ model: "record" });
|
|
367
|
+
|
|
368
|
+
// Use with FabricHttpServer
|
|
369
|
+
const server = FabricHttpServer({
|
|
370
|
+
services: recordServices.services,
|
|
371
|
+
prefix: "/api",
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
export const handler = server.handler;
|
|
375
|
+
// Routes: POST /api/records, GET /api/records, GET /api/records/:id,
|
|
376
|
+
// POST /api/records/:id, DELETE /api/records/:id, POST /api/records/:id/archive
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### Route Mapping
|
|
380
|
+
|
|
381
|
+
| Operation | HTTP Method | Route | DynamoDB Function |
|
|
382
|
+
|-----------|-------------|-------|-------------------|
|
|
383
|
+
| create | POST | `/{model}` | `putEntity` |
|
|
384
|
+
| list | GET | `/{model}` | `queryByScope` |
|
|
385
|
+
| read | GET | `/{model}/:id` | `getEntity` |
|
|
386
|
+
| update | POST | `/{model}/:id` | `updateEntity` |
|
|
387
|
+
| delete | DELETE | `/{model}/:id` | `deleteEntity` |
|
|
388
|
+
| archive | POST | `/{model}/:id/archive` | `archiveEntity` |
|
|
389
|
+
| *custom* | POST | `/{model}/:id/{action}` | custom service |
|
|
390
|
+
|
|
391
|
+
Custom operations are defined in the `execute` array and create routes like `/records/:id/publish`.
|
|
392
|
+
|
|
393
|
+
#### Configuration
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const services = FabricData({
|
|
397
|
+
// Model: string or config object
|
|
398
|
+
model: "record", // Or: { alias: "record", name: "Record", description: "..." }
|
|
399
|
+
|
|
400
|
+
// Authorization for all operations
|
|
401
|
+
authorization: async (token) => {
|
|
402
|
+
const user = await validateJwt(token);
|
|
403
|
+
if (!user) throw new UnauthorizedError();
|
|
404
|
+
return user;
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
// CORS configuration
|
|
408
|
+
cors: { origin: "*" },
|
|
409
|
+
|
|
410
|
+
// Scope calculator (default: APEX "@")
|
|
411
|
+
// Determines how entities are grouped for queries
|
|
412
|
+
scope: ({ params }) => `chat#${params.chatId}`,
|
|
413
|
+
|
|
414
|
+
// Pagination limits
|
|
415
|
+
defaultLimit: 20, // Default items per page
|
|
416
|
+
maxLimit: 100, // Maximum items per page
|
|
417
|
+
|
|
418
|
+
// Per-operation configuration
|
|
419
|
+
operations: {
|
|
420
|
+
read: { authorization: false }, // Public read
|
|
421
|
+
list: { authorization: false }, // Public list
|
|
422
|
+
delete: { authorization: requireAdmin }, // Admin-only delete
|
|
423
|
+
archive: false, // Disable archive
|
|
424
|
+
create: {
|
|
425
|
+
// Transform input before saving
|
|
426
|
+
transform: (input, existing) => ({
|
|
427
|
+
...input,
|
|
428
|
+
createdBy: input.userId,
|
|
429
|
+
}),
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### Custom Execute Actions
|
|
436
|
+
|
|
437
|
+
Add custom actions that operate on entities:
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
const services = FabricData({
|
|
441
|
+
model: "record",
|
|
442
|
+
execute: [
|
|
443
|
+
{
|
|
444
|
+
alias: "publish",
|
|
445
|
+
description: "Publish a record",
|
|
446
|
+
authorization: requireEditor,
|
|
447
|
+
input: {
|
|
448
|
+
publishDate: { type: Date, required: false },
|
|
449
|
+
notify: { type: Boolean, default: false },
|
|
450
|
+
},
|
|
451
|
+
service: async (entity, { publishDate, notify }) => {
|
|
452
|
+
// entity is the fetched record
|
|
453
|
+
const { updateEntity } = await import("@jaypie/dynamodb");
|
|
454
|
+
await updateEntity({
|
|
455
|
+
entity: {
|
|
456
|
+
...entity,
|
|
457
|
+
metadata: { ...entity.metadata, publishedAt: publishDate ?? new Date() },
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
if (notify) await sendNotification(entity);
|
|
461
|
+
return { published: true };
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
alias: "duplicate",
|
|
466
|
+
description: "Create a copy of a record",
|
|
467
|
+
service: async (entity) => {
|
|
468
|
+
const { putEntity } = await import("@jaypie/dynamodb");
|
|
469
|
+
const duplicate = {
|
|
470
|
+
...entity,
|
|
471
|
+
id: crypto.randomUUID(),
|
|
472
|
+
name: `${entity.name} (Copy)`,
|
|
473
|
+
};
|
|
474
|
+
delete duplicate.alias;
|
|
475
|
+
return putEntity({ entity: duplicate });
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
});
|
|
480
|
+
// Routes: POST /records/:id/publish, POST /records/:id/duplicate
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### List Pagination
|
|
484
|
+
|
|
485
|
+
The list operation supports pagination via cursor:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
// First request
|
|
489
|
+
GET /api/records?limit=10
|
|
490
|
+
|
|
491
|
+
// Response
|
|
492
|
+
{
|
|
493
|
+
"data": {
|
|
494
|
+
"items": [...],
|
|
495
|
+
"nextKey": "eyJpZCI6Ii4uLiJ9" // Base64 encoded cursor
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Next page
|
|
500
|
+
GET /api/records?limit=10&cursor=eyJpZCI6Ii4uLiJ9
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Query parameters:
|
|
504
|
+
- `limit` - Items per page (default: 20, max: 100)
|
|
505
|
+
- `cursor` - Pagination cursor from previous response
|
|
506
|
+
- `ascending` - Sort ascending by sequence (default: false)
|
|
507
|
+
- `archived` - Include archived entities (default: false)
|
|
508
|
+
- `deleted` - Include deleted entities (default: false)
|
|
509
|
+
|
|
510
|
+
### Modeling
|
|
511
|
+
|
|
512
|
+
FabricModel provides a standard vocabulary for entities. All fields are optional except `id` and `model`, enabling high reuse across different entity types.
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
import type { FabricModel } from "@jaypie/fabric";
|
|
516
|
+
|
|
517
|
+
const record: FabricModel = {
|
|
518
|
+
// Identity (required)
|
|
519
|
+
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
520
|
+
model: "record",
|
|
521
|
+
|
|
522
|
+
// Identity (optional)
|
|
523
|
+
name: "December 12, 2026 Session", // Full name, first reference
|
|
524
|
+
label: "December 12", // Short name, second reference
|
|
525
|
+
abbreviation: "12/12", // Shortest form
|
|
526
|
+
alias: "2026-12-12", // Slug for human lookup
|
|
527
|
+
xid: "external-system-id", // External identifier
|
|
528
|
+
description: "Daily session notes",
|
|
529
|
+
|
|
530
|
+
// Schema
|
|
531
|
+
class: "memory", // Category (varies by model)
|
|
532
|
+
type: "session", // Type (varies by model)
|
|
533
|
+
|
|
534
|
+
// Content
|
|
535
|
+
content: "Session notes here...",
|
|
536
|
+
metadata: { tags: ["work", "planning"] },
|
|
537
|
+
|
|
538
|
+
// Display
|
|
539
|
+
emoji: "📝",
|
|
540
|
+
icon: "lucide#notebook",
|
|
541
|
+
|
|
542
|
+
// Timestamps
|
|
543
|
+
createdAt: new Date(),
|
|
544
|
+
updatedAt: new Date(),
|
|
545
|
+
archivedAt: null, // Set when archived
|
|
546
|
+
deletedAt: null, // Set when soft-deleted
|
|
547
|
+
};
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
#### Specialized Models
|
|
551
|
+
|
|
552
|
+
**FabricJob** extends FabricModel for async tasks:
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
import type { FabricJob } from "@jaypie/fabric";
|
|
556
|
+
|
|
557
|
+
const job: FabricJob = {
|
|
558
|
+
id: "job-123",
|
|
559
|
+
model: "job",
|
|
560
|
+
status: "processing", // Required: current state
|
|
561
|
+
startedAt: new Date(),
|
|
562
|
+
completedAt: null,
|
|
563
|
+
progress: { // FabricProgress (value object)
|
|
564
|
+
percentageComplete: 45,
|
|
565
|
+
elapsedTime: 12000,
|
|
566
|
+
estimatedTime: 30000,
|
|
567
|
+
},
|
|
568
|
+
messages: [], // Execution log
|
|
569
|
+
createdAt: new Date(),
|
|
570
|
+
updatedAt: new Date(),
|
|
571
|
+
};
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**FabricMessage** extends FabricModel for content-focused entities:
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
import type { FabricMessage } from "@jaypie/fabric";
|
|
578
|
+
|
|
579
|
+
const message: FabricMessage = {
|
|
580
|
+
id: "msg-456",
|
|
581
|
+
model: "message",
|
|
582
|
+
content: "Hello, world!", // Required
|
|
583
|
+
type: "user", // e.g., "user", "assistant", "system"
|
|
584
|
+
createdAt: new Date(),
|
|
585
|
+
updatedAt: new Date(),
|
|
586
|
+
};
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
#### Indexing
|
|
590
|
+
|
|
591
|
+
When persisting models to DynamoDB, use index utilities to build GSI keys:
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
import { APEX, calculateScope, populateIndexKeys, DEFAULT_INDEXES } from "@jaypie/fabric";
|
|
595
|
+
|
|
596
|
+
// Root-level entity
|
|
597
|
+
const record = {
|
|
598
|
+
model: "record",
|
|
599
|
+
scope: APEX, // "@" for root level
|
|
600
|
+
alias: "2026-12-12",
|
|
601
|
+
sequence: Date.now(),
|
|
602
|
+
// ...other fields
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// Child entity (belongs to a parent)
|
|
606
|
+
const message = {
|
|
607
|
+
model: "message",
|
|
608
|
+
scope: calculateScope({ model: "chat", id: "chat-123" }), // "chat#chat-123"
|
|
609
|
+
sequence: Date.now(),
|
|
610
|
+
// ...other fields
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// Auto-populate GSI keys
|
|
614
|
+
const indexed = populateIndexKeys(record, DEFAULT_INDEXES);
|
|
615
|
+
// indexed.indexScope = "@#record"
|
|
616
|
+
// indexed.indexAlias = "@#record#2026-12-12"
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## API
|
|
620
|
+
|
|
621
|
+
### Main Export (`@jaypie/fabric`)
|
|
622
|
+
|
|
623
|
+
| Export | Description |
|
|
624
|
+
|--------|-------------|
|
|
625
|
+
| `fabricService` | Factory function for validated service endpoints |
|
|
626
|
+
| `fabric` | Master conversion dispatcher |
|
|
627
|
+
| `fabricBoolean` | Convert to boolean |
|
|
628
|
+
| `fabricNumber` | Convert to number |
|
|
629
|
+
| `fabricString` | Convert to string |
|
|
630
|
+
| `fabricArray` | Wrap in array |
|
|
631
|
+
| `resolveFromArray` | Extract from single-element array |
|
|
632
|
+
| `fabricObject` | Wrap in `{ value: ... }` |
|
|
633
|
+
| `resolveFromObject` | Extract `.value` from object |
|
|
634
|
+
| `fabricDate` | Convert to Date |
|
|
635
|
+
| `resolveFromDate` | Resolve from Date to string |
|
|
636
|
+
| `FabricModel` | Base type for models |
|
|
637
|
+
| `FabricMessage` | Message model type |
|
|
638
|
+
| `FabricJob` | Job model type |
|
|
639
|
+
| `FabricProgress` | Progress tracking type |
|
|
640
|
+
| `registerModel` | Register custom indexes for a model |
|
|
641
|
+
| `getModelIndexes` | Get indexes for a model |
|
|
642
|
+
| `populateIndexKeys` | Populate GSI keys on an entity |
|
|
643
|
+
| `buildCompositeKey` | Build composite key from fields |
|
|
644
|
+
| `calculateScope` | Calculate scope |
|
|
645
|
+
| `DEFAULT_INDEXES` | Default GSI indexes |
|
|
646
|
+
| `APEX` | Root-level marker (`"@"`) |
|
|
647
|
+
| `SEPARATOR` | Composite key separator (`"#"`) |
|
|
648
|
+
| `ARCHIVED_SUFFIX` | Suffix for archived entities |
|
|
649
|
+
| `DELETED_SUFFIX` | Suffix for deleted entities |
|
|
650
|
+
|
|
651
|
+
### Sub-Exports
|
|
652
|
+
|
|
653
|
+
| Path | Description |
|
|
654
|
+
|------|-------------|
|
|
655
|
+
| `@jaypie/fabric/commander` | Commander.js CLI adapter |
|
|
656
|
+
| `@jaypie/fabric/data` | DynamoDB CRUD service generator |
|
|
657
|
+
| `@jaypie/fabric/express` | Express middleware adapter |
|
|
658
|
+
| `@jaypie/fabric/http` | HTTP adapter with authorization and CORS |
|
|
659
|
+
| `@jaypie/fabric/lambda` | AWS Lambda adapter |
|
|
660
|
+
| `@jaypie/fabric/llm` | LLM tool adapter |
|
|
661
|
+
| `@jaypie/fabric/mcp` | MCP server adapter |
|
|
662
|
+
|
|
663
|
+
## Philosophy
|
|
664
|
+
|
|
665
|
+
The "Fabric" philosophy:
|
|
666
|
+
- **Smooth, pliable** - Things that feel right should work
|
|
667
|
+
- **Catch bad passes** - Invalid inputs throw clear errors
|
|
668
|
+
|
|
669
|
+
This means:
|
|
670
|
+
- `"true"` works where `true` is expected
|
|
671
|
+
- `"42"` works where `42` is expected
|
|
672
|
+
- JSON strings automatically parse
|
|
673
|
+
- Invalid conversions fail fast with `BadRequestError`
|
|
674
|
+
|
|
675
|
+
## License
|
|
676
|
+
|
|
677
|
+
MIT
|