@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 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
- export { CollectionDescriptor, ContentAuthority, EditorComponentList, EditorComponentSpec, type ErrorCode, ErrorCodes, FieldDescriptor, FunctionDescriptor, GraftError, type GraftErrorJSON, type GraftErrorOptions, RegistryFileDescriptor, RegistryFileRole, RegistryItemDescriptor, RegistryItemType, SchemaDescription };
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
- SchemaDescription
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.1.1",
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
  }