@usegraft/contracts 0.2.0 → 1.0.0-beta.1

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 CHANGED
@@ -33,4 +33,4 @@ throw new GraftError({
33
33
 
34
34
  ---
35
35
 
36
- MIT. [Repository](https://github.com/AndersonDesign1/graft) · [Changelog](https://github.com/AndersonDesign1/graft/blob/feat/core/packages/contracts/CHANGELOG.md)
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
@@ -292,4 +292,103 @@ type RegistryItemDescriptor = z.infer<typeof RegistryItemDescriptor>;
292
292
  */
293
293
  declare const STATIC_INDEX_DEFAULT_PATH = ".graft/index.db";
294
294
 
295
- export { CollectionDescriptor, ContentAuthority, EditorComponentList, EditorComponentSpec, type ErrorCode, ErrorCodes, FieldDescriptor, FunctionDescriptor, GraftError, type GraftErrorJSON, type GraftErrorOptions, RegistryFileDescriptor, RegistryFileRole, RegistryItemDescriptor, RegistryItemType, STATIC_INDEX_DEFAULT_PATH, SchemaDescription };
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
@@ -176,6 +176,26 @@ var RegistryItemDescriptor = z.object({
176
176
 
177
177
  // src/paths.ts
178
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
+ }
179
199
  export {
180
200
  CollectionDescriptor,
181
201
  ContentAuthority,
@@ -190,5 +210,8 @@ export {
190
210
  RegistryItemDescriptor,
191
211
  RegistryItemType,
192
212
  STATIC_INDEX_DEFAULT_PATH,
193
- SchemaDescription
213
+ SchemaDescription,
214
+ getRequestPeer,
215
+ rateIdentity,
216
+ setRequestPeer
194
217
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usegraft/contracts",
3
- "version": "0.2.0",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "Shared error codes and introspection schemas: the vocabulary every Graft package speaks.",
5
5
  "keywords": [
6
6
  "agent",
@@ -38,7 +38,7 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "zod": "^4.1.0"
41
+ "zod": "^4.5.4"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=22.16"