@happyvertical/smrt-web 0.38.7 → 0.38.9

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/dist/index.d.ts CHANGED
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Serialize `list` query params into the query string the generated REST list
3
+ * route parses (`handleList` in `core/src/generators/rest.ts`): `limit`,
4
+ * `offset`, `orderBy` as scalars, and `where` entries as `field=value`
5
+ * (equality) or `field[op]=value` for a `{ op, value }` condition (`in` joins an
6
+ * array with commas). Called with no params (the collection runtime's argument-
7
+ * free `list()`) it returns `''`, so the bare-URL behavior is unchanged.
8
+ */
9
+ export declare function buildListQuery(params?: Record<string, unknown>): string;
10
+
1
11
  /**
2
12
  * Build CRUD fetchers from a generated collection definition — the same URL
3
13
  * scheme and payload handling as the generated REST client
@@ -476,6 +486,29 @@ export declare interface PersistCollectionConfig<TData extends object = object>
476
486
  */
477
487
  export declare function registerDurableResource(namespace: string, resource: DurableResource): () => void;
478
488
 
489
+ /**
490
+ * Register every collection's generated tool descriptors with WebMCP.
491
+ *
492
+ * @returns a disposer that deregisters all tools this call registered. On a
493
+ * browser without WebMCP the call is a no-op and the disposer is inert.
494
+ */
495
+ export declare function registerWebMcpTools(definitions: SmrtWebCollectionDefinition[], options?: RegisterWebMcpToolsOptions): () => void;
496
+
497
+ export declare interface RegisterWebMcpToolsOptions {
498
+ /** REST base path for the fetchers (default `/api/v1`). */
499
+ basePath?: string;
500
+ /** Injectable fetch (tests / SSR-safe wrappers). */
501
+ fetchFn?: typeof fetch;
502
+ /**
503
+ * Override how a definition's CRUD fetchers are built. Defaults to
504
+ * {@link createDefinitionFetchers}; the primary seam for testing `execute`
505
+ * without a live server.
506
+ */
507
+ resolveFetchers?: (definition: SmrtWebCollectionDefinition) => SmrtCrudFetchers;
508
+ /** Predicate to include/exclude individual tools (e.g. reads-only surfaces). */
509
+ filter?: (definition: SmrtWebCollectionDefinition, descriptor: NonNullable<SmrtWebCollectionDefinition['toolDescriptors']>[number]) => boolean;
510
+ }
511
+
479
512
  /**
480
513
  * Run the `wrapMutation` hook across `capabilities` in array order for one
481
514
  * mutation, short-circuiting on the FIRST capability that returns `{ handled:
@@ -662,12 +695,6 @@ export declare interface SmrtWebCollection<TData extends object> {
662
695
  insert(row: SmrtWebRow<TData>): SmrtWebTransaction;
663
696
  }
664
697
 
665
- /**
666
- * One generated collection definition: everything needed to construct a client
667
- * collection over the generated REST surface. The `_row` property is a phantom
668
- * type carrier threaded through codegen — it never exists at runtime, it only
669
- * lets factories infer the row type from a definition.
670
- */
671
698
  export declare interface SmrtWebCollectionDefinition<TData extends object = object> {
672
699
  /** REST collection name (e.g. `products`). */
673
700
  name: string;
@@ -679,6 +706,12 @@ export declare interface SmrtWebCollectionDefinition<TData extends object = obje
679
706
  idField: string;
680
707
  /** CRUD + custom actions exposed by the api decorator config. */
681
708
  actions: string[];
709
+ /**
710
+ * WebMCP/MCP tool descriptors for the exposed actions (#1812). Optional so
711
+ * hand-built definitions (older codegen, tests) still satisfy the type; a
712
+ * missing value means "no WebMCP tools to register".
713
+ */
714
+ toolDescriptors?: WebToolDescriptor[];
682
715
  /** Persisted field metadata keyed by field name. */
683
716
  fields: Record<string, SmrtWebFieldDefinition>;
684
717
  /**
@@ -964,6 +997,31 @@ export declare interface UpdateStateConfig {
964
997
  namespace: DurableStoreKey;
965
998
  }
966
999
 
1000
+ /**
1001
+ * One generated collection definition: everything needed to construct a client
1002
+ * collection over the generated REST surface. The `_row` property is a phantom
1003
+ * type carrier threaded through codegen — it never exists at runtime, it only
1004
+ * lets factories infer the row type from a definition.
1005
+ */
1006
+ /**
1007
+ * One WebMCP / MCP tool descriptor for a collection action (#1812). Emitted by
1008
+ * the core web-collections codegen as PLAIN DATA (this package has no smrt
1009
+ * dependency), shaped to match Chrome's `document.modelContext.registerTool`
1010
+ * input — see https://developer.chrome.com/docs/ai/webmcp. Consumed by
1011
+ * {@link registerWebMcpTools} in `./webmcp`.
1012
+ */
1013
+ export declare interface WebToolDescriptor {
1014
+ /** The action this tool performs (`list` | `get` | … | a custom method name). */
1015
+ action: string;
1016
+ /** Tool id, `${className.toLowerCase()}_${action}` (e.g. `product_list`). */
1017
+ name: string;
1018
+ description: string;
1019
+ /** JSON Schema for the tool's arguments. */
1020
+ inputSchema: Record<string, unknown>;
1021
+ /** True for non-mutating reads → WebMCP `annotations.readOnlyHint`. */
1022
+ readOnly: boolean;
1023
+ }
1024
+
967
1025
  /**
968
1026
  * Clear every durable artifact registered under `namespace`, then drop the
969
1027
  * namespace. This is the single teardown point a logout / tenant-switch calls:
package/dist/index.js CHANGED
@@ -1433,6 +1433,84 @@ function createUpdateState(config) {
1433
1433
  };
1434
1434
  }
1435
1435
  //#endregion
1436
+ //#region src/webmcp.ts
1437
+ function getModelContext() {
1438
+ const mc = globalThis.document?.modelContext;
1439
+ if (mc && typeof mc.registerTool === "function") return mc;
1440
+ }
1441
+ function registerWebMcpTools(definitions, options = {}) {
1442
+ const ctx = getModelContext();
1443
+ if (!ctx) return () => {};
1444
+ const basePath = options.basePath ?? "/api/v1";
1445
+ const controller = new AbortController();
1446
+ for (const definition of definitions) {
1447
+ const descriptors = definition.toolDescriptors;
1448
+ if (!descriptors || descriptors.length === 0) continue;
1449
+ const fetchers = options.resolveFetchers ? options.resolveFetchers(definition) : createDefinitionFetchers(definition, basePath, options.fetchFn);
1450
+ for (const descriptor of descriptors) {
1451
+ if (options.filter && !options.filter(definition, descriptor)) continue;
1452
+ ctx.registerTool({
1453
+ name: descriptor.name,
1454
+ description: descriptor.description,
1455
+ inputSchema: descriptor.inputSchema,
1456
+ annotations: { readOnlyHint: descriptor.readOnly },
1457
+ execute: (args) => dispatch(fetchers, definition, descriptor.action, args ?? {})
1458
+ }, { signal: controller.signal });
1459
+ }
1460
+ }
1461
+ return () => controller.abort();
1462
+ }
1463
+ function requireId(args, action) {
1464
+ const id = args.id;
1465
+ if (typeof id !== "string" || id.length === 0) throw new Error(`WebMCP ${action} requires a string 'id' argument`);
1466
+ return id;
1467
+ }
1468
+ function requireIdentifier(args) {
1469
+ const value = args.id ?? args.slug;
1470
+ if (typeof value !== "string" || value.length === 0) throw new Error("WebMCP get requires a string 'id' or 'slug' argument");
1471
+ return value;
1472
+ }
1473
+ function listParams(args) {
1474
+ const params = {};
1475
+ if (args.limit !== void 0) params.limit = args.limit;
1476
+ if (args.offset !== void 0) params.offset = args.offset;
1477
+ if (args.orderBy !== void 0) params.orderBy = args.orderBy;
1478
+ if (args.where !== void 0) params.where = args.where;
1479
+ return params;
1480
+ }
1481
+ async function dispatch(fetchers, definition, action, args) {
1482
+ switch (action) {
1483
+ case "list": {
1484
+ const rows = unwrapListResult(await fetchers.list(listParams(args)), definition.name);
1485
+ return JSON.stringify(rows);
1486
+ }
1487
+ case "get": {
1488
+ if (!fetchers.get) throw new Error(`${definition.name} has no get action`);
1489
+ const row = unwrapItemResult(await fetchers.get(requireIdentifier(args)), `get(${definition.name})`);
1490
+ return JSON.stringify(row);
1491
+ }
1492
+ case "create": {
1493
+ const row = unwrapItemResult(await fetchers.create(args), `create(${definition.name})`);
1494
+ return JSON.stringify(row);
1495
+ }
1496
+ case "update": {
1497
+ if (!fetchers.update) throw new Error(`${definition.name} has no update action`);
1498
+ const { id: _id, ...body } = args;
1499
+ const row = unwrapItemResult(await fetchers.update(requireId(args, "update"), body), `update(${definition.name})`);
1500
+ return JSON.stringify(row);
1501
+ }
1502
+ case "delete":
1503
+ if (!fetchers.delete) throw new Error(`${definition.name} has no delete action`);
1504
+ await fetchers.delete(requireId(args, "delete"));
1505
+ return JSON.stringify({ success: true });
1506
+ default: return JSON.stringify({
1507
+ error: `WebMCP custom action '${action}' is not wired in the tracer`,
1508
+ action,
1509
+ collection: definition.name
1510
+ });
1511
+ }
1512
+ }
1513
+ //#endregion
1436
1514
  //#region src/index.ts
1437
1515
  var SmrtWebRequestError = class extends Error {
1438
1516
  payload;
@@ -1460,6 +1538,33 @@ function unwrapItemResult(result, context) {
1460
1538
  }
1461
1539
  throw new SmrtWebRequestError(`[smrt-web] ${context} returned an unexpected payload shape`, result);
1462
1540
  }
1541
+ var SMRT_TO_REST_OPERATOR = {
1542
+ ">": "gt",
1543
+ ">=": "gte",
1544
+ "<": "lt",
1545
+ "<=": "lte",
1546
+ "!=": "ne",
1547
+ in: "in",
1548
+ like: "like"
1549
+ };
1550
+ function buildListQuery(params) {
1551
+ if (!params) return "";
1552
+ const search = new URLSearchParams();
1553
+ const { limit, offset, orderBy, where } = params;
1554
+ if (limit !== void 0) search.set("limit", String(limit));
1555
+ if (offset !== void 0) search.set("offset", String(offset));
1556
+ if (orderBy !== void 0) search.set("orderBy", Array.isArray(orderBy) ? orderBy.join(", ") : String(orderBy));
1557
+ if (where && typeof where === "object") {
1558
+ for (const [field, condition] of Object.entries(where)) if (condition && typeof condition === "object" && !Array.isArray(condition) && "op" in condition && "value" in condition) {
1559
+ const { op, value } = condition;
1560
+ const restOp = SMRT_TO_REST_OPERATOR[op];
1561
+ const token = Array.isArray(value) ? value.join(",") : String(value);
1562
+ search.set(restOp ? `${field}[${restOp}]` : field, token);
1563
+ } else if (condition !== void 0 && condition !== null) search.set(field, String(condition));
1564
+ }
1565
+ const qs = search.toString();
1566
+ return qs ? `?${qs}` : "";
1567
+ }
1463
1568
  function createDefinitionFetchers(definition, basePath = "/api/v1", fetchFn = (...args) => globalThis.fetch(...args)) {
1464
1569
  const collectionUrl = `${basePath}${definition.endpoint}`;
1465
1570
  const headers = { "Content-Type": "application/json" };
@@ -1472,7 +1577,7 @@ function createDefinitionFetchers(definition, basePath = "/api/v1", fetchFn = (.
1472
1577
  return payload;
1473
1578
  };
1474
1579
  return {
1475
- list: async () => parse(await fetchFn(collectionUrl, { headers })),
1580
+ list: async (params) => parse(await fetchFn(`${collectionUrl}${buildListQuery(params)}`, { headers })),
1476
1581
  get: async (id) => parse(await fetchFn(`${collectionUrl}/${id}`, { headers })),
1477
1582
  create: async (data) => parse(await fetchFn(collectionUrl, {
1478
1583
  method: "POST",
@@ -1779,6 +1884,6 @@ function createSmrtCollection(definition, options) {
1779
1884
  return handle;
1780
1885
  }
1781
1886
  //#endregion
1782
- export { DEFAULT_PERSIST_DEBOUNCE_MS, SmrtWebRequestError, createDefinitionFetchers, createSmrtCollection, createSmrtWebClient, createSmrtWebEventSubscriber, createUpdateState, durableStoreNamespace, getEngineCollection, getOutboxHandle, liveInvalidation, newLocalId, offlineOutbox, persistCollection, registerDurableResource, runWrapMutation, unwrapItemResult, unwrapListResult, wipeDurableStore };
1887
+ export { DEFAULT_PERSIST_DEBOUNCE_MS, SmrtWebRequestError, buildListQuery, createDefinitionFetchers, createSmrtCollection, createSmrtWebClient, createSmrtWebEventSubscriber, createUpdateState, durableStoreNamespace, getEngineCollection, getOutboxHandle, liveInvalidation, newLocalId, offlineOutbox, persistCollection, registerDurableResource, registerWebMcpTools, runWrapMutation, unwrapItemResult, unwrapListResult, wipeDurableStore };
1783
1888
 
1784
1889
  //# sourceMappingURL=index.js.map