@usegraft/contracts 0.0.0-canary-20260831153011
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 +36 -0
- package/dist/index.d.ts +394 -0
- package/dist/index.js +217 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anderson Joseph
|
|
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,36 @@
|
|
|
1
|
+
# @usegraft/contracts
|
|
2
|
+
|
|
3
|
+
> Shared error codes and introspection schemas. The vocabulary every other Graft package speaks.
|
|
4
|
+
|
|
5
|
+
Part of [Graft](https://github.com/AndersonDesign1/graft), a CMS built so an AI agent is the primary operator.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i @usegraft/contracts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You rarely install this directly. It arrives as a dependency of the packages that throw.
|
|
14
|
+
|
|
15
|
+
## Errors an agent can act on
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { GraftError, ErrorCodes } from "@usegraft/contracts";
|
|
19
|
+
|
|
20
|
+
throw new GraftError({
|
|
21
|
+
code: "INPUT_VALIDATION_FAILED",
|
|
22
|
+
message: 'Slug "My Page" is not URL-safe.',
|
|
23
|
+
fix: 'Slugs are kebab-case: lowercase letters, digits and single hyphens, e.g. "my-page".',
|
|
24
|
+
details: { slug: "My Page" },
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`fix` is not decoration. Every error carries the next action, because the primary reader is an agent deciding what to do rather than a human reading a stack trace. That is why `message` says what happened and `fix` says what to do about it.
|
|
29
|
+
|
|
30
|
+
## Introspection
|
|
31
|
+
|
|
32
|
+
`CollectionDescriptor`, `FieldDescriptor`, `FunctionDescriptor` and the registry descriptors are the shapes `describe_schema` and friends return over MCP. They are declared here so the CLI, the MCP server and the Studio cannot drift on what a collection looks like.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
MIT. [Repository](https://github.com/AndersonDesign1/graft) · [Changelog](https://github.com/AndersonDesign1/graft/blob/main/packages/contracts/CHANGELOG.md)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error codes + the GraftError shape.
|
|
5
|
+
*
|
|
6
|
+
* Every Graft error carries an agent-actionable `fix` — the next concrete step an
|
|
7
|
+
* agent (or human) can take to resolve it. This is part of the "self-teaching"
|
|
8
|
+
* pillar: failures explain how to recover, not just what went wrong.
|
|
9
|
+
*/
|
|
10
|
+
declare const ErrorCodes: {
|
|
11
|
+
readonly SCHEMA_VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED";
|
|
12
|
+
readonly COLLECTION_NOT_FOUND: "COLLECTION_NOT_FOUND";
|
|
13
|
+
readonly CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND";
|
|
14
|
+
readonly CONFIG_INVALID: "CONFIG_INVALID";
|
|
15
|
+
readonly ALREADY_INITIALIZED: "ALREADY_INITIALIZED";
|
|
16
|
+
readonly ENV_VAR_MISSING: "ENV_VAR_MISSING";
|
|
17
|
+
readonly CONTENT_DIR_NOT_FOUND: "CONTENT_DIR_NOT_FOUND";
|
|
18
|
+
readonly CONTENT_REFERENCE_NOT_FOUND: "CONTENT_REFERENCE_NOT_FOUND";
|
|
19
|
+
readonly DOCUMENT_NOT_FOUND: "DOCUMENT_NOT_FOUND";
|
|
20
|
+
readonly FUNCTION_NOT_FOUND: "FUNCTION_NOT_FOUND";
|
|
21
|
+
readonly INPUT_VALIDATION_FAILED: "INPUT_VALIDATION_FAILED";
|
|
22
|
+
readonly FUNCTION_EXECUTION_FAILED: "FUNCTION_EXECUTION_FAILED";
|
|
23
|
+
readonly METHOD_NOT_ALLOWED: "METHOD_NOT_ALLOWED";
|
|
24
|
+
readonly ROUTE_NOT_FOUND: "ROUTE_NOT_FOUND";
|
|
25
|
+
readonly AUTHORITY_MISMATCH: "AUTHORITY_MISMATCH";
|
|
26
|
+
readonly INDEX_OWNERSHIP: "INDEX_OWNERSHIP";
|
|
27
|
+
readonly SLUG_NOT_UNIQUE: "SLUG_NOT_UNIQUE";
|
|
28
|
+
readonly INVALID_SLUG: "INVALID_SLUG";
|
|
29
|
+
readonly MIGRATION_REQUIRED: "MIGRATION_REQUIRED";
|
|
30
|
+
readonly MIGRATION_FAILED: "MIGRATION_FAILED";
|
|
31
|
+
readonly UNAUTHORIZED: "UNAUTHORIZED";
|
|
32
|
+
readonly TOKEN_INVALID: "TOKEN_INVALID";
|
|
33
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
34
|
+
readonly DESTRUCTIVE_OP_REQUIRES_APPROVAL: "DESTRUCTIVE_OP_REQUIRES_APPROVAL";
|
|
35
|
+
readonly APPROVAL_INVALID: "APPROVAL_INVALID";
|
|
36
|
+
readonly APPROVAL_SELF_DECISION: "APPROVAL_SELF_DECISION";
|
|
37
|
+
readonly APPROVAL_UNATTRIBUTED: "APPROVAL_UNATTRIBUTED";
|
|
38
|
+
readonly BRANCH_NOT_FOUND: "BRANCH_NOT_FOUND";
|
|
39
|
+
readonly BRANCH_EXISTS: "BRANCH_EXISTS";
|
|
40
|
+
readonly BRANCH_INVALID: "BRANCH_INVALID";
|
|
41
|
+
readonly BRANCH_BACKEND_FAILED: "BRANCH_BACKEND_FAILED";
|
|
42
|
+
readonly REGISTRY_ITEM_NOT_FOUND: "REGISTRY_ITEM_NOT_FOUND";
|
|
43
|
+
readonly REGISTRY_ITEM_INVALID: "REGISTRY_ITEM_INVALID";
|
|
44
|
+
readonly REGISTRY_FILE_EXISTS: "REGISTRY_FILE_EXISTS";
|
|
45
|
+
readonly ASSET_EXISTS: "ASSET_EXISTS";
|
|
46
|
+
readonly NEEDS_DATABASE: "NEEDS_DATABASE";
|
|
47
|
+
readonly CONTENT_TREE_READ_ONLY: "CONTENT_TREE_READ_ONLY";
|
|
48
|
+
readonly GIT_UNAVAILABLE: "GIT_UNAVAILABLE";
|
|
49
|
+
readonly COMMIT_FAILED: "COMMIT_FAILED";
|
|
50
|
+
readonly STATIC_INDEX_NOT_FOUND: "STATIC_INDEX_NOT_FOUND";
|
|
51
|
+
readonly STATIC_INDEX_UNSUPPORTED: "STATIC_INDEX_UNSUPPORTED";
|
|
52
|
+
readonly NOT_IMPLEMENTED: "NOT_IMPLEMENTED";
|
|
53
|
+
};
|
|
54
|
+
type ErrorCode = keyof typeof ErrorCodes;
|
|
55
|
+
interface GraftErrorJSON {
|
|
56
|
+
error: ErrorCode;
|
|
57
|
+
message: string;
|
|
58
|
+
fix?: string;
|
|
59
|
+
details?: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
interface GraftErrorOptions {
|
|
62
|
+
code: ErrorCode;
|
|
63
|
+
message: string;
|
|
64
|
+
/** Agent-actionable next step, e.g. "create pages/about.mdx or fix the reference in nav.ts". */
|
|
65
|
+
fix?: string;
|
|
66
|
+
details?: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
declare class GraftError extends Error {
|
|
69
|
+
readonly code: ErrorCode;
|
|
70
|
+
readonly fix?: string;
|
|
71
|
+
readonly details?: Record<string, unknown>;
|
|
72
|
+
constructor(options: GraftErrorOptions);
|
|
73
|
+
toJSON(): GraftErrorJSON;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Introspection contracts — the single source of truth for what the MCP
|
|
78
|
+
* `describe_schema` tool returns, so the MCP server, CLI, and core all agree on
|
|
79
|
+
* shape. Zod gives us runtime validation and the inferred TypeScript types from
|
|
80
|
+
* one definition (the "one Zod layer" principle).
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
declare const ContentAuthority: z.ZodEnum<{
|
|
84
|
+
"file-authoritative": "file-authoritative";
|
|
85
|
+
"db-authoritative": "db-authoritative";
|
|
86
|
+
"hybrid-with-drift-detection": "hybrid-with-drift-detection";
|
|
87
|
+
}>;
|
|
88
|
+
type ContentAuthority = z.infer<typeof ContentAuthority>;
|
|
89
|
+
/**
|
|
90
|
+
* Field introspection — recursive so object/array fields expose their shape
|
|
91
|
+
* to agents (describe_schema), not opaque "json" blobs.
|
|
92
|
+
*/
|
|
93
|
+
type FieldDescriptor = {
|
|
94
|
+
name: string;
|
|
95
|
+
type: string;
|
|
96
|
+
optional: boolean;
|
|
97
|
+
description?: string;
|
|
98
|
+
/** Nested fields when type is `object`. */
|
|
99
|
+
fields?: FieldDescriptor[];
|
|
100
|
+
/** Item shape when type is `array` (name is conventionally `"item"`). */
|
|
101
|
+
items?: FieldDescriptor;
|
|
102
|
+
};
|
|
103
|
+
declare const FieldDescriptor: z.ZodType<FieldDescriptor>;
|
|
104
|
+
declare const CollectionDescriptor: z.ZodObject<{
|
|
105
|
+
name: z.ZodString;
|
|
106
|
+
authority: z.ZodEnum<{
|
|
107
|
+
"file-authoritative": "file-authoritative";
|
|
108
|
+
"db-authoritative": "db-authoritative";
|
|
109
|
+
"hybrid-with-drift-detection": "hybrid-with-drift-detection";
|
|
110
|
+
}>;
|
|
111
|
+
fields: z.ZodArray<z.ZodType<FieldDescriptor, unknown, z.core.$ZodTypeInternals<FieldDescriptor, unknown>>>;
|
|
112
|
+
description: z.ZodOptional<z.ZodString>;
|
|
113
|
+
sections: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
114
|
+
}, z.core.$strip>;
|
|
115
|
+
type CollectionDescriptor = z.infer<typeof CollectionDescriptor>;
|
|
116
|
+
declare const FunctionDescriptor: z.ZodObject<{
|
|
117
|
+
name: z.ZodString;
|
|
118
|
+
kind: z.ZodEnum<{
|
|
119
|
+
query: "query";
|
|
120
|
+
mutation: "mutation";
|
|
121
|
+
}>;
|
|
122
|
+
args: z.ZodArray<z.ZodType<FieldDescriptor, unknown, z.core.$ZodTypeInternals<FieldDescriptor, unknown>>>;
|
|
123
|
+
returns: z.ZodOptional<z.ZodString>;
|
|
124
|
+
description: z.ZodOptional<z.ZodString>;
|
|
125
|
+
public: z.ZodOptional<z.ZodBoolean>;
|
|
126
|
+
destructive: z.ZodOptional<z.ZodBoolean>;
|
|
127
|
+
}, z.core.$strip>;
|
|
128
|
+
type FunctionDescriptor = z.infer<typeof FunctionDescriptor>;
|
|
129
|
+
declare const SchemaDescription: z.ZodObject<{
|
|
130
|
+
collections: z.ZodArray<z.ZodObject<{
|
|
131
|
+
name: z.ZodString;
|
|
132
|
+
authority: z.ZodEnum<{
|
|
133
|
+
"file-authoritative": "file-authoritative";
|
|
134
|
+
"db-authoritative": "db-authoritative";
|
|
135
|
+
"hybrid-with-drift-detection": "hybrid-with-drift-detection";
|
|
136
|
+
}>;
|
|
137
|
+
fields: z.ZodArray<z.ZodType<FieldDescriptor, unknown, z.core.$ZodTypeInternals<FieldDescriptor, unknown>>>;
|
|
138
|
+
description: z.ZodOptional<z.ZodString>;
|
|
139
|
+
sections: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
140
|
+
}, z.core.$strip>>;
|
|
141
|
+
functions: z.ZodArray<z.ZodObject<{
|
|
142
|
+
name: z.ZodString;
|
|
143
|
+
kind: z.ZodEnum<{
|
|
144
|
+
query: "query";
|
|
145
|
+
mutation: "mutation";
|
|
146
|
+
}>;
|
|
147
|
+
args: z.ZodArray<z.ZodType<FieldDescriptor, unknown, z.core.$ZodTypeInternals<FieldDescriptor, unknown>>>;
|
|
148
|
+
returns: z.ZodOptional<z.ZodString>;
|
|
149
|
+
description: z.ZodOptional<z.ZodString>;
|
|
150
|
+
public: z.ZodOptional<z.ZodBoolean>;
|
|
151
|
+
destructive: z.ZodOptional<z.ZodBoolean>;
|
|
152
|
+
}, z.core.$strip>>;
|
|
153
|
+
}, z.core.$strip>;
|
|
154
|
+
type SchemaDescription = z.infer<typeof SchemaDescription>;
|
|
155
|
+
/**
|
|
156
|
+
* Registry introspection — the shape the MCP `list_registry` / `describe_item`
|
|
157
|
+
* tools return so agents can browse owned primitives before `graft add`.
|
|
158
|
+
*
|
|
159
|
+
* The vocabulary here mirrors @usegraft/registry's authoring manifest (ITEM_TYPES /
|
|
160
|
+
* FILE_ROLES); a drift test in @usegraft/registry keeps the two in lockstep so this
|
|
161
|
+
* stays the single introspection source of truth without contracts depending on
|
|
162
|
+
* registry.
|
|
163
|
+
*/
|
|
164
|
+
declare const RegistryItemType: z.ZodEnum<{
|
|
165
|
+
block: "block";
|
|
166
|
+
field: "field";
|
|
167
|
+
access: "access";
|
|
168
|
+
bundle: "bundle";
|
|
169
|
+
}>;
|
|
170
|
+
type RegistryItemType = z.infer<typeof RegistryItemType>;
|
|
171
|
+
declare const RegistryFileRole: z.ZodEnum<{
|
|
172
|
+
module: "module";
|
|
173
|
+
component: "component";
|
|
174
|
+
content: "content";
|
|
175
|
+
env: "env";
|
|
176
|
+
editor: "editor";
|
|
177
|
+
}>;
|
|
178
|
+
type RegistryFileRole = z.infer<typeof RegistryFileRole>;
|
|
179
|
+
/**
|
|
180
|
+
* How a component presents in the Studio canvas — the editor's half of an
|
|
181
|
+
* owned primitive.
|
|
182
|
+
*
|
|
183
|
+
* Data, not code, and that is the load-bearing decision. The Studio ships as a
|
|
184
|
+
* prebuilt bundle with no bundler in the loop, so it cannot import a component
|
|
185
|
+
* from the project and render it; the only other way to let a third party
|
|
186
|
+
* control presentation would be to evaluate code they authored inside the
|
|
187
|
+
* editor, which is not a thing to ship. A declaration the editor interprets
|
|
188
|
+
* keeps the extension point open without that.
|
|
189
|
+
*
|
|
190
|
+
* It is copied into the project by `graft add`, exactly like the component it
|
|
191
|
+
* describes: owned, editable, no runtime dependency on the registry it came
|
|
192
|
+
* from. Renaming a prop means editing a file you already have.
|
|
193
|
+
*
|
|
194
|
+
* Everything is optional. A component with no declaration still renders — it
|
|
195
|
+
* gets the generic card, which is what every component got before this existed.
|
|
196
|
+
*/
|
|
197
|
+
declare const EditorComponentSpec: z.ZodObject<{
|
|
198
|
+
component: z.ZodString;
|
|
199
|
+
label: z.ZodOptional<z.ZodString>;
|
|
200
|
+
titleProp: z.ZodOptional<z.ZodString>;
|
|
201
|
+
linkProp: z.ZodOptional<z.ZodString>;
|
|
202
|
+
tone: z.ZodOptional<z.ZodObject<{
|
|
203
|
+
prop: z.ZodString;
|
|
204
|
+
map: z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
205
|
+
success: "success";
|
|
206
|
+
info: "info";
|
|
207
|
+
warn: "warn";
|
|
208
|
+
danger: "danger";
|
|
209
|
+
neutral: "neutral";
|
|
210
|
+
}>>;
|
|
211
|
+
}, z.core.$strip>>;
|
|
212
|
+
hideProps: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
213
|
+
children: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
214
|
+
snippet: z.ZodOptional<z.ZodString>;
|
|
215
|
+
}, z.core.$strip>;
|
|
216
|
+
type EditorComponentSpec = z.infer<typeof EditorComponentSpec>;
|
|
217
|
+
/** What `GET /api/studio/v1/editor-components` returns: the project's own declarations. */
|
|
218
|
+
declare const EditorComponentList: z.ZodObject<{
|
|
219
|
+
components: z.ZodArray<z.ZodObject<{
|
|
220
|
+
component: z.ZodString;
|
|
221
|
+
label: z.ZodOptional<z.ZodString>;
|
|
222
|
+
titleProp: z.ZodOptional<z.ZodString>;
|
|
223
|
+
linkProp: z.ZodOptional<z.ZodString>;
|
|
224
|
+
tone: z.ZodOptional<z.ZodObject<{
|
|
225
|
+
prop: z.ZodString;
|
|
226
|
+
map: z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
227
|
+
success: "success";
|
|
228
|
+
info: "info";
|
|
229
|
+
warn: "warn";
|
|
230
|
+
danger: "danger";
|
|
231
|
+
neutral: "neutral";
|
|
232
|
+
}>>;
|
|
233
|
+
}, z.core.$strip>>;
|
|
234
|
+
hideProps: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
235
|
+
children: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
236
|
+
snippet: z.ZodOptional<z.ZodString>;
|
|
237
|
+
}, z.core.$strip>>;
|
|
238
|
+
}, z.core.$strip>;
|
|
239
|
+
type EditorComponentList = z.infer<typeof EditorComponentList>;
|
|
240
|
+
/** One file an item writes — the target path (relative to project root) and its role. */
|
|
241
|
+
declare const RegistryFileDescriptor: z.ZodObject<{
|
|
242
|
+
target: z.ZodString;
|
|
243
|
+
role: z.ZodEnum<{
|
|
244
|
+
module: "module";
|
|
245
|
+
component: "component";
|
|
246
|
+
content: "content";
|
|
247
|
+
env: "env";
|
|
248
|
+
editor: "editor";
|
|
249
|
+
}>;
|
|
250
|
+
}, z.core.$strip>;
|
|
251
|
+
type RegistryFileDescriptor = z.infer<typeof RegistryFileDescriptor>;
|
|
252
|
+
/**
|
|
253
|
+
* Agent-facing description of one owned primitive. Deliberately omits the
|
|
254
|
+
* machine-specific absolute `dir` a loaded item carries — this is the wire shape.
|
|
255
|
+
*/
|
|
256
|
+
declare const RegistryItemDescriptor: z.ZodObject<{
|
|
257
|
+
name: z.ZodString;
|
|
258
|
+
type: z.ZodEnum<{
|
|
259
|
+
block: "block";
|
|
260
|
+
field: "field";
|
|
261
|
+
access: "access";
|
|
262
|
+
bundle: "bundle";
|
|
263
|
+
}>;
|
|
264
|
+
description: z.ZodString;
|
|
265
|
+
graftVersion: z.ZodString;
|
|
266
|
+
dependencies: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
267
|
+
registryDependencies: z.ZodArray<z.ZodString>;
|
|
268
|
+
files: z.ZodArray<z.ZodObject<{
|
|
269
|
+
target: z.ZodString;
|
|
270
|
+
role: z.ZodEnum<{
|
|
271
|
+
module: "module";
|
|
272
|
+
component: "component";
|
|
273
|
+
content: "content";
|
|
274
|
+
env: "env";
|
|
275
|
+
editor: "editor";
|
|
276
|
+
}>;
|
|
277
|
+
}, z.core.$strip>>;
|
|
278
|
+
llms: z.ZodBoolean;
|
|
279
|
+
}, z.core.$strip>;
|
|
280
|
+
type RegistryItemDescriptor = z.infer<typeof RegistryItemDescriptor>;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Well-known paths shared across packages.
|
|
284
|
+
*/
|
|
285
|
+
/**
|
|
286
|
+
* Where a static-tier project's compiled index lives, relative to the project
|
|
287
|
+
* root.
|
|
288
|
+
*
|
|
289
|
+
* Here rather than in @usegraft/db because the CLI resolves it while loading a
|
|
290
|
+
* config and deliberately lazy-loads the database package — a static import
|
|
291
|
+
* just to read one string would pull Postgres into every `graft` invocation.
|
|
292
|
+
*/
|
|
293
|
+
declare const STATIC_INDEX_DEFAULT_PATH = ".graft/index.db";
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Record the socket address a request came from. Called by an adapter that owns
|
|
297
|
+
* the connection — never from anything that merely receives a Request.
|
|
298
|
+
*/
|
|
299
|
+
declare function setRequestPeer(request: Request, address: string): void;
|
|
300
|
+
/** The recorded peer address, or undefined when no adapter registered one. */
|
|
301
|
+
declare function getRequestPeer(request: Request): string | undefined;
|
|
302
|
+
/**
|
|
303
|
+
* The rate-limit identity for a caller with no verified actor.
|
|
304
|
+
*
|
|
305
|
+
* Lives here rather than beside one handler because more than one surface
|
|
306
|
+
* needs it — `createFunctionsHandler` for anonymous function calls, and
|
|
307
|
+
* `createContentApiHandler` for a read endpoint that never authenticates at
|
|
308
|
+
* all — and the rule it encodes is the kind that must not be reimplemented
|
|
309
|
+
* twice. `.greptile/rules.md` names it as a security invariant precisely
|
|
310
|
+
* because the obvious reading of `x-forwarded-for` is the wrong one.
|
|
311
|
+
*
|
|
312
|
+
* Never reads the header unless the deployment declares how many proxies it
|
|
313
|
+
* controls, and then counts from the RIGHT: entries are appended, so the
|
|
314
|
+
* rightmost were added by infrastructure closest to us. A client can prepend
|
|
315
|
+
* anything it likes and never reach that far.
|
|
316
|
+
*/
|
|
317
|
+
declare function rateIdentity(request: Request, trustedProxyHops: number): string;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The content-index read contract.
|
|
321
|
+
*
|
|
322
|
+
* These types used to live in `@usegraft/db`, which made them unreachable
|
|
323
|
+
* without depending on the Postgres package — and `ContentRow` in particular
|
|
324
|
+
* was `typeof contentIndex.$inferSelect`, derived from a Drizzle table. So the
|
|
325
|
+
* shape every reader returns was defined by one implementation's storage
|
|
326
|
+
* schema, and any consumer that merely wanted to *describe* a row (the HTTP
|
|
327
|
+
* transport, the browser client) had to install a database driver to say so.
|
|
328
|
+
*
|
|
329
|
+
* They live here because this is the layer every package already shares.
|
|
330
|
+
* `@usegraft/db` now proves its table still matches this contract rather than
|
|
331
|
+
* defining it, which is the direction the dependency should have run in from
|
|
332
|
+
* the start: the seam owns the shape, the implementation conforms to it.
|
|
333
|
+
*/
|
|
334
|
+
/** One row of the authored-content index. */
|
|
335
|
+
interface ContentRow {
|
|
336
|
+
branchId: string;
|
|
337
|
+
collection: string;
|
|
338
|
+
slug: string;
|
|
339
|
+
/** Validated frontmatter. */
|
|
340
|
+
data: Record<string, unknown>;
|
|
341
|
+
/** Authored MDX source, byte-for-byte as written. */
|
|
342
|
+
body: string;
|
|
343
|
+
contentHash: string;
|
|
344
|
+
sourcePath: string;
|
|
345
|
+
/** Soft-delete marker. Readers exclude these. */
|
|
346
|
+
deleted: boolean;
|
|
347
|
+
updatedAt: Date;
|
|
348
|
+
/** The FTS vector, when the implementation has one. */
|
|
349
|
+
search: string | null;
|
|
350
|
+
}
|
|
351
|
+
interface ReaderReadOptions {
|
|
352
|
+
collection: string;
|
|
353
|
+
/** When set, read a single document; otherwise the whole collection. */
|
|
354
|
+
slug?: string;
|
|
355
|
+
limit?: number;
|
|
356
|
+
offset?: number;
|
|
357
|
+
/** Branch to read; defaults to "main". Static readers serve their compiled branch regardless. */
|
|
358
|
+
branch?: string;
|
|
359
|
+
}
|
|
360
|
+
interface ReaderSearchOptions {
|
|
361
|
+
/** Websearch-syntax query: words, "quoted phrases", `or`, -exclusions. */
|
|
362
|
+
query: string;
|
|
363
|
+
/** Restrict to these collections; defaults to all. */
|
|
364
|
+
collections?: string[];
|
|
365
|
+
/** Max hits, best-ranked first. Defaults to 20. */
|
|
366
|
+
limit?: number;
|
|
367
|
+
branch?: string;
|
|
368
|
+
}
|
|
369
|
+
interface ContentSearchHit {
|
|
370
|
+
row: ContentRow;
|
|
371
|
+
/** Relevance rank: slug beats frontmatter beats body prose. */
|
|
372
|
+
rank: number;
|
|
373
|
+
/** Body fragment(s) with matches wrapped in `<b>…</b>`. */
|
|
374
|
+
snippet: string;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* The seam between "who serves content reads" and "where the index lives".
|
|
378
|
+
* Implemented by the Postgres reader, the compiled SQLite artifact, and the
|
|
379
|
+
* HTTP reader in `@usegraft/content-api`.
|
|
380
|
+
*/
|
|
381
|
+
interface ContentIndexReader {
|
|
382
|
+
readContent(options: ReaderReadOptions): Promise<ContentRow[]>;
|
|
383
|
+
searchContent(options: ReaderSearchOptions): Promise<ContentSearchHit[]>;
|
|
384
|
+
close(): Promise<void>;
|
|
385
|
+
}
|
|
386
|
+
/** What one compile changed, by slug. */
|
|
387
|
+
interface ChangeSet {
|
|
388
|
+
added: string[];
|
|
389
|
+
changed: string[];
|
|
390
|
+
removed: string[];
|
|
391
|
+
unchanged: number;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export { type ChangeSet, CollectionDescriptor, ContentAuthority, type ContentIndexReader, type ContentRow, type ContentSearchHit, EditorComponentList, EditorComponentSpec, type ErrorCode, ErrorCodes, FieldDescriptor, FunctionDescriptor, GraftError, type GraftErrorJSON, type GraftErrorOptions, type ReaderReadOptions, type ReaderSearchOptions, RegistryFileDescriptor, RegistryFileRole, RegistryItemDescriptor, RegistryItemType, STATIC_INDEX_DEFAULT_PATH, SchemaDescription, getRequestPeer, rateIdentity, setRequestPeer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var ErrorCodes = {
|
|
3
|
+
SCHEMA_VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED",
|
|
4
|
+
COLLECTION_NOT_FOUND: "COLLECTION_NOT_FOUND",
|
|
5
|
+
CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
|
|
6
|
+
CONFIG_INVALID: "CONFIG_INVALID",
|
|
7
|
+
ALREADY_INITIALIZED: "ALREADY_INITIALIZED",
|
|
8
|
+
ENV_VAR_MISSING: "ENV_VAR_MISSING",
|
|
9
|
+
CONTENT_DIR_NOT_FOUND: "CONTENT_DIR_NOT_FOUND",
|
|
10
|
+
CONTENT_REFERENCE_NOT_FOUND: "CONTENT_REFERENCE_NOT_FOUND",
|
|
11
|
+
DOCUMENT_NOT_FOUND: "DOCUMENT_NOT_FOUND",
|
|
12
|
+
FUNCTION_NOT_FOUND: "FUNCTION_NOT_FOUND",
|
|
13
|
+
INPUT_VALIDATION_FAILED: "INPUT_VALIDATION_FAILED",
|
|
14
|
+
FUNCTION_EXECUTION_FAILED: "FUNCTION_EXECUTION_FAILED",
|
|
15
|
+
METHOD_NOT_ALLOWED: "METHOD_NOT_ALLOWED",
|
|
16
|
+
ROUTE_NOT_FOUND: "ROUTE_NOT_FOUND",
|
|
17
|
+
AUTHORITY_MISMATCH: "AUTHORITY_MISMATCH",
|
|
18
|
+
INDEX_OWNERSHIP: "INDEX_OWNERSHIP",
|
|
19
|
+
SLUG_NOT_UNIQUE: "SLUG_NOT_UNIQUE",
|
|
20
|
+
INVALID_SLUG: "INVALID_SLUG",
|
|
21
|
+
MIGRATION_REQUIRED: "MIGRATION_REQUIRED",
|
|
22
|
+
MIGRATION_FAILED: "MIGRATION_FAILED",
|
|
23
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
24
|
+
TOKEN_INVALID: "TOKEN_INVALID",
|
|
25
|
+
RATE_LIMITED: "RATE_LIMITED",
|
|
26
|
+
DESTRUCTIVE_OP_REQUIRES_APPROVAL: "DESTRUCTIVE_OP_REQUIRES_APPROVAL",
|
|
27
|
+
APPROVAL_INVALID: "APPROVAL_INVALID",
|
|
28
|
+
APPROVAL_SELF_DECISION: "APPROVAL_SELF_DECISION",
|
|
29
|
+
APPROVAL_UNATTRIBUTED: "APPROVAL_UNATTRIBUTED",
|
|
30
|
+
BRANCH_NOT_FOUND: "BRANCH_NOT_FOUND",
|
|
31
|
+
BRANCH_EXISTS: "BRANCH_EXISTS",
|
|
32
|
+
BRANCH_INVALID: "BRANCH_INVALID",
|
|
33
|
+
BRANCH_BACKEND_FAILED: "BRANCH_BACKEND_FAILED",
|
|
34
|
+
REGISTRY_ITEM_NOT_FOUND: "REGISTRY_ITEM_NOT_FOUND",
|
|
35
|
+
REGISTRY_ITEM_INVALID: "REGISTRY_ITEM_INVALID",
|
|
36
|
+
REGISTRY_FILE_EXISTS: "REGISTRY_FILE_EXISTS",
|
|
37
|
+
ASSET_EXISTS: "ASSET_EXISTS",
|
|
38
|
+
NEEDS_DATABASE: "NEEDS_DATABASE",
|
|
39
|
+
CONTENT_TREE_READ_ONLY: "CONTENT_TREE_READ_ONLY",
|
|
40
|
+
GIT_UNAVAILABLE: "GIT_UNAVAILABLE",
|
|
41
|
+
COMMIT_FAILED: "COMMIT_FAILED",
|
|
42
|
+
STATIC_INDEX_NOT_FOUND: "STATIC_INDEX_NOT_FOUND",
|
|
43
|
+
STATIC_INDEX_UNSUPPORTED: "STATIC_INDEX_UNSUPPORTED",
|
|
44
|
+
NOT_IMPLEMENTED: "NOT_IMPLEMENTED"
|
|
45
|
+
};
|
|
46
|
+
var GraftError = class extends Error {
|
|
47
|
+
code;
|
|
48
|
+
fix;
|
|
49
|
+
details;
|
|
50
|
+
constructor(options) {
|
|
51
|
+
super(options.message);
|
|
52
|
+
this.name = "GraftError";
|
|
53
|
+
this.code = options.code;
|
|
54
|
+
this.fix = options.fix;
|
|
55
|
+
this.details = options.details;
|
|
56
|
+
}
|
|
57
|
+
toJSON() {
|
|
58
|
+
return {
|
|
59
|
+
error: this.code,
|
|
60
|
+
message: this.message,
|
|
61
|
+
fix: this.fix,
|
|
62
|
+
details: this.details
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/introspection.ts
|
|
68
|
+
import { z } from "zod";
|
|
69
|
+
var ContentAuthority = z.enum([
|
|
70
|
+
"file-authoritative",
|
|
71
|
+
"db-authoritative",
|
|
72
|
+
"hybrid-with-drift-detection"
|
|
73
|
+
]);
|
|
74
|
+
var FieldDescriptor = z.lazy(
|
|
75
|
+
() => z.object({
|
|
76
|
+
name: z.string(),
|
|
77
|
+
type: z.string(),
|
|
78
|
+
optional: z.boolean().default(false),
|
|
79
|
+
description: z.string().optional(),
|
|
80
|
+
fields: z.array(FieldDescriptor).optional(),
|
|
81
|
+
items: FieldDescriptor.optional()
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
var CollectionDescriptor = z.object({
|
|
85
|
+
name: z.string(),
|
|
86
|
+
authority: ContentAuthority,
|
|
87
|
+
fields: z.array(FieldDescriptor),
|
|
88
|
+
description: z.string().optional(),
|
|
89
|
+
/**
|
|
90
|
+
* Reading order for the collection's `section` values, when it groups.
|
|
91
|
+
*
|
|
92
|
+
* Section order is editorial — "Start here" before "Reference" — and there
|
|
93
|
+
* is nothing in the content to infer it from, since `order` restarts within
|
|
94
|
+
* each section. Declaring it on the collection means the site nav and any
|
|
95
|
+
* tool that lists content (Studio, agents) sort identically instead of each
|
|
96
|
+
* inventing an order. Sections not listed sort last, so new content never
|
|
97
|
+
* disappears from a sidebar.
|
|
98
|
+
*/
|
|
99
|
+
sections: z.array(z.string()).optional()
|
|
100
|
+
});
|
|
101
|
+
var FunctionDescriptor = z.object({
|
|
102
|
+
name: z.string(),
|
|
103
|
+
kind: z.enum(["query", "mutation"]),
|
|
104
|
+
args: z.array(FieldDescriptor),
|
|
105
|
+
returns: z.string().optional(),
|
|
106
|
+
description: z.string().optional(),
|
|
107
|
+
/** Anonymous callers allowed. Mutations default to false; queries to true. */
|
|
108
|
+
public: z.boolean().optional(),
|
|
109
|
+
/** Always human-gated: invoking it requires an approved, one-shot, input-bound approval. */
|
|
110
|
+
destructive: z.boolean().optional()
|
|
111
|
+
});
|
|
112
|
+
var SchemaDescription = z.object({
|
|
113
|
+
collections: z.array(CollectionDescriptor),
|
|
114
|
+
functions: z.array(FunctionDescriptor)
|
|
115
|
+
});
|
|
116
|
+
var RegistryItemType = z.enum(["block", "field", "access", "bundle"]);
|
|
117
|
+
var RegistryFileRole = z.enum(["module", "component", "content", "env", "editor"]);
|
|
118
|
+
var EditorComponentSpec = z.object({
|
|
119
|
+
/** The JSX name this describes, e.g. "Callout". */
|
|
120
|
+
component: z.string().min(1),
|
|
121
|
+
/** Display name for the card's chip. Defaults to `component`. */
|
|
122
|
+
label: z.string().min(1).optional(),
|
|
123
|
+
/** Prop to show as the card's heading instead of guessing. */
|
|
124
|
+
titleProp: z.string().optional(),
|
|
125
|
+
/** Prop holding a destination, shown as a chip. */
|
|
126
|
+
linkProp: z.string().optional(),
|
|
127
|
+
/**
|
|
128
|
+
* Colour the card by one of its props — `type="warning"` on a Callout should
|
|
129
|
+
* look like a warning. Values map to the editor's own tone roles, so a
|
|
130
|
+
* third-party component cannot introduce a colour the theme does not have.
|
|
131
|
+
*/
|
|
132
|
+
tone: z.object({
|
|
133
|
+
prop: z.string().min(1),
|
|
134
|
+
map: z.record(z.string(), z.enum(["info", "warn", "danger", "success", "neutral"]))
|
|
135
|
+
}).optional(),
|
|
136
|
+
/** Props already implied by the card's shape, not worth listing again. */
|
|
137
|
+
hideProps: z.array(z.string()).default([]),
|
|
138
|
+
/** Declarations for the children this component expects, e.g. DocCard inside DocCards. */
|
|
139
|
+
children: z.array(z.string()).default([]),
|
|
140
|
+
/**
|
|
141
|
+
* The exact MDX inserted when the operator picks this component from the
|
|
142
|
+
* palette. Authored by whoever wrote the component, because only they know
|
|
143
|
+
* which props are required and what a sensible starting body is — a guess
|
|
144
|
+
* assembled from the other fields would produce blocks that do not compile.
|
|
145
|
+
* Without one the component is still rendered, just not offered for insert.
|
|
146
|
+
*
|
|
147
|
+
* **Put the opening tag on a line of its own.** Markdown only treats JSX as
|
|
148
|
+
* one HTML *block* when nothing else shares the opening tag's line; write
|
|
149
|
+
* `<Callout>text</Callout>` on a single line and remark splits it into an
|
|
150
|
+
* open tag, a text node and a close tag, which renders as three pieces of
|
|
151
|
+
* raw source rather than one card. Every authored component in this repo is
|
|
152
|
+
* written the block way for exactly this reason.
|
|
153
|
+
*/
|
|
154
|
+
snippet: z.string().min(1).optional()
|
|
155
|
+
});
|
|
156
|
+
var EditorComponentList = z.object({ components: z.array(EditorComponentSpec) });
|
|
157
|
+
var RegistryFileDescriptor = z.object({
|
|
158
|
+
target: z.string(),
|
|
159
|
+
role: RegistryFileRole
|
|
160
|
+
});
|
|
161
|
+
var RegistryItemDescriptor = z.object({
|
|
162
|
+
name: z.string(),
|
|
163
|
+
type: RegistryItemType,
|
|
164
|
+
description: z.string(),
|
|
165
|
+
/** Semver range against @usegraft/core; "*" = any (pre-1.0 default). */
|
|
166
|
+
graftVersion: z.string(),
|
|
167
|
+
/** npm packages the target must install first (package → version range). */
|
|
168
|
+
dependencies: z.record(z.string(), z.string()),
|
|
169
|
+
/** Other registry items `graft add` pulls in first (transitive). */
|
|
170
|
+
registryDependencies: z.array(z.string()),
|
|
171
|
+
/** The files this item writes into the project. */
|
|
172
|
+
files: z.array(RegistryFileDescriptor),
|
|
173
|
+
/** Whether the item ships an llms.txt teaching fragment. */
|
|
174
|
+
llms: z.boolean()
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// src/paths.ts
|
|
178
|
+
var STATIC_INDEX_DEFAULT_PATH = ".graft/index.db";
|
|
179
|
+
|
|
180
|
+
// src/peer.ts
|
|
181
|
+
var peers = /* @__PURE__ */ new WeakMap();
|
|
182
|
+
function setRequestPeer(request, address) {
|
|
183
|
+
peers.set(request, address);
|
|
184
|
+
}
|
|
185
|
+
function getRequestPeer(request) {
|
|
186
|
+
return peers.get(request);
|
|
187
|
+
}
|
|
188
|
+
function rateIdentity(request, trustedProxyHops) {
|
|
189
|
+
if (trustedProxyHops > 0) {
|
|
190
|
+
const forwarded = request.headers.get("x-forwarded-for");
|
|
191
|
+
if (forwarded) {
|
|
192
|
+
const hops = forwarded.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
193
|
+
const trusted = hops[hops.length - trustedProxyHops];
|
|
194
|
+
if (trusted) return trusted;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return getRequestPeer(request) ?? "unknown";
|
|
198
|
+
}
|
|
199
|
+
export {
|
|
200
|
+
CollectionDescriptor,
|
|
201
|
+
ContentAuthority,
|
|
202
|
+
EditorComponentList,
|
|
203
|
+
EditorComponentSpec,
|
|
204
|
+
ErrorCodes,
|
|
205
|
+
FieldDescriptor,
|
|
206
|
+
FunctionDescriptor,
|
|
207
|
+
GraftError,
|
|
208
|
+
RegistryFileDescriptor,
|
|
209
|
+
RegistryFileRole,
|
|
210
|
+
RegistryItemDescriptor,
|
|
211
|
+
RegistryItemType,
|
|
212
|
+
STATIC_INDEX_DEFAULT_PATH,
|
|
213
|
+
SchemaDescription,
|
|
214
|
+
getRequestPeer,
|
|
215
|
+
rateIdentity,
|
|
216
|
+
setRequestPeer
|
|
217
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usegraft/contracts",
|
|
3
|
+
"version": "0.0.0-canary-20260831153011",
|
|
4
|
+
"description": "Shared error codes and introspection schemas: the vocabulary every Graft package speaks.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"ai",
|
|
8
|
+
"cms",
|
|
9
|
+
"errors",
|
|
10
|
+
"graft",
|
|
11
|
+
"headless-cms",
|
|
12
|
+
"introspection",
|
|
13
|
+
"mcp",
|
|
14
|
+
"schemas",
|
|
15
|
+
"typescript"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/AndersonDesign1/graft#readme",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/AndersonDesign1/graft.git",
|
|
22
|
+
"directory": "packages/contracts"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"zod": "^4.1.0"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22.16"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
48
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"test": "vitest run"
|
|
51
|
+
}
|
|
52
|
+
}
|