@usegraft/contracts 0.1.1 → 1.0.0-beta.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 +36 -0
- package/dist/index.d.ts +114 -1
- package/dist/index.js +29 -1
- package/package.json +16 -3
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
CHANGED
|
@@ -34,6 +34,7 @@ declare const ErrorCodes: {
|
|
|
34
34
|
readonly DESTRUCTIVE_OP_REQUIRES_APPROVAL: "DESTRUCTIVE_OP_REQUIRES_APPROVAL";
|
|
35
35
|
readonly APPROVAL_INVALID: "APPROVAL_INVALID";
|
|
36
36
|
readonly APPROVAL_SELF_DECISION: "APPROVAL_SELF_DECISION";
|
|
37
|
+
readonly APPROVAL_UNATTRIBUTED: "APPROVAL_UNATTRIBUTED";
|
|
37
38
|
readonly BRANCH_NOT_FOUND: "BRANCH_NOT_FOUND";
|
|
38
39
|
readonly BRANCH_EXISTS: "BRANCH_EXISTS";
|
|
39
40
|
readonly BRANCH_INVALID: "BRANCH_INVALID";
|
|
@@ -278,4 +279,116 @@ declare const RegistryItemDescriptor: z.ZodObject<{
|
|
|
278
279
|
}, z.core.$strip>;
|
|
279
280
|
type RegistryItemDescriptor = z.infer<typeof RegistryItemDescriptor>;
|
|
280
281
|
|
|
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
CHANGED
|
@@ -26,6 +26,7 @@ var ErrorCodes = {
|
|
|
26
26
|
DESTRUCTIVE_OP_REQUIRES_APPROVAL: "DESTRUCTIVE_OP_REQUIRES_APPROVAL",
|
|
27
27
|
APPROVAL_INVALID: "APPROVAL_INVALID",
|
|
28
28
|
APPROVAL_SELF_DECISION: "APPROVAL_SELF_DECISION",
|
|
29
|
+
APPROVAL_UNATTRIBUTED: "APPROVAL_UNATTRIBUTED",
|
|
29
30
|
BRANCH_NOT_FOUND: "BRANCH_NOT_FOUND",
|
|
30
31
|
BRANCH_EXISTS: "BRANCH_EXISTS",
|
|
31
32
|
BRANCH_INVALID: "BRANCH_INVALID",
|
|
@@ -172,6 +173,29 @@ var RegistryItemDescriptor = z.object({
|
|
|
172
173
|
/** Whether the item ships an llms.txt teaching fragment. */
|
|
173
174
|
llms: z.boolean()
|
|
174
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
|
+
}
|
|
175
199
|
export {
|
|
176
200
|
CollectionDescriptor,
|
|
177
201
|
ContentAuthority,
|
|
@@ -185,5 +209,9 @@ export {
|
|
|
185
209
|
RegistryFileRole,
|
|
186
210
|
RegistryItemDescriptor,
|
|
187
211
|
RegistryItemType,
|
|
188
|
-
|
|
212
|
+
STATIC_INDEX_DEFAULT_PATH,
|
|
213
|
+
SchemaDescription,
|
|
214
|
+
getRequestPeer,
|
|
215
|
+
rateIdentity,
|
|
216
|
+
setRequestPeer
|
|
189
217
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usegraft/contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.0",
|
|
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",
|
|
4
18
|
"license": "MIT",
|
|
5
19
|
"repository": {
|
|
6
20
|
"type": "git",
|
|
@@ -33,7 +47,6 @@
|
|
|
33
47
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
34
48
|
"dev": "tsup src/index.ts --format esm --watch",
|
|
35
49
|
"typecheck": "tsc --noEmit",
|
|
36
|
-
"test": "vitest run"
|
|
37
|
-
"lint": "oxlint ."
|
|
50
|
+
"test": "vitest run"
|
|
38
51
|
}
|
|
39
52
|
}
|