@fitzzero/quickdraw-core 5.0.1 → 5.1.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.
Files changed (60) hide show
  1. package/README.md +303 -32
  2. package/dist/{admin-CBlTY0do.d.ts → admin-DKwkIh5i.d.ts} +2 -2
  3. package/dist/{adminSchemas-zji11V74.d.ts → adminSchemas-2DUA_hZ8.d.ts} +1 -1
  4. package/dist/{auth-CMyRkfjZ.d.ts → auth-BhWqdyb-.d.ts} +2 -2
  5. package/dist/{caller-BqCYnpmC.d.ts → caller-CXTFswBO.d.ts} +27 -5
  6. package/dist/{chunk-2XJFNVHW.js → chunk-2PWMUVC7.js} +1 -1
  7. package/dist/{chunk-2XJFNVHW.js.map → chunk-2PWMUVC7.js.map} +1 -1
  8. package/dist/{chunk-3JNMVB2V.js → chunk-2UELNHDD.js} +2 -2
  9. package/dist/{chunk-HQ6WIRE2.js → chunk-3674EIRT.js} +4 -4
  10. package/dist/{chunk-NS5PUHIZ.js → chunk-A6DJBGOG.js} +130 -23
  11. package/dist/chunk-A6DJBGOG.js.map +1 -0
  12. package/dist/{chunk-ZACRRYWL.js → chunk-JZBCLNDU.js} +4 -4
  13. package/dist/{chunk-O2PWC2YW.js → chunk-KNDDCVQJ.js} +3 -3
  14. package/dist/{chunk-5YRQ26LF.js → chunk-RT6OILOI.js} +1 -1
  15. package/dist/chunk-RT6OILOI.js.map +1 -0
  16. package/dist/{chunk-WVIRXYI4.js → chunk-VJTFXHOK.js} +2 -2
  17. package/dist/{chunk-WVIRXYI4.js.map → chunk-VJTFXHOK.js.map} +1 -1
  18. package/dist/chunk-XNLWBZIG.js +9 -0
  19. package/dist/chunk-XNLWBZIG.js.map +1 -0
  20. package/dist/cli/quickdraw-docs.js +241 -27
  21. package/dist/cli/quickdraw-docs.js.map +1 -1
  22. package/dist/client/index.d.ts +7 -7
  23. package/dist/client/index.js +4 -4
  24. package/dist/{clientTypes-DuZW_8mb.d.ts → clientTypes-Bc-G8n-U.d.ts} +3 -3
  25. package/dist/{connection-DuN8GvcD.d.ts → connection-Cr1BU07W.d.ts} +2 -2
  26. package/dist/{createServer-CGsJlxM8.d.ts → createServer-CP9eDHZZ.d.ts} +30 -9
  27. package/dist/{createTestApp-Cg2CZa1g.d.ts → createTestApp-UKVGEmOf.d.ts} +6 -6
  28. package/dist/{envelope-nf5eGB-a.d.ts → envelope-Cp3YcVxC.d.ts} +1 -1
  29. package/dist/{http-D3fSFQ57.d.ts → http-Djj2Inuu.d.ts} +6 -6
  30. package/dist/index.d.ts +6 -6
  31. package/dist/index.js +26 -6
  32. package/dist/index.js.map +1 -1
  33. package/dist/{infer-Dw21kmLw.d.ts → infer-Teo1xpsd.d.ts} +17 -0
  34. package/dist/{metrics-C0WvB7-I.d.ts → metrics-BdBu5o8G.d.ts} +2 -2
  35. package/dist/prisma/index.d.ts +4 -4
  36. package/dist/server/auth/index.d.ts +3 -3
  37. package/dist/server/index.d.ts +47 -26
  38. package/dist/server/index.js +62 -36
  39. package/dist/server/index.js.map +1 -1
  40. package/dist/server/mcp/index.d.ts +51 -11
  41. package/dist/server/mcp/index.js +129 -8
  42. package/dist/server/mcp/index.js.map +1 -1
  43. package/dist/server/otel.d.ts +3 -3
  44. package/dist/{sharing-CoreezAq.d.ts → sharing-DVa8W-yR.d.ts} +3 -3
  45. package/dist/testing/client.d.ts +13 -13
  46. package/dist/testing/client.js +5 -5
  47. package/dist/testing/index.d.ts +156 -12
  48. package/dist/testing/index.js +1036 -21
  49. package/dist/testing/index.js.map +1 -1
  50. package/dist/testing/mock.d.ts +5 -5
  51. package/dist/testing/mock.js +4 -4
  52. package/dist/{types-Bwg3kIEc.d.ts → types-Cu0vmx7c.d.ts} +8 -2
  53. package/dist/utils/index.d.ts +1 -1
  54. package/package.json +1 -1
  55. package/dist/chunk-5YRQ26LF.js.map +0 -1
  56. package/dist/chunk-NS5PUHIZ.js.map +0 -1
  57. /package/dist/{chunk-3JNMVB2V.js.map → chunk-2UELNHDD.js.map} +0 -0
  58. /package/dist/{chunk-HQ6WIRE2.js.map → chunk-3674EIRT.js.map} +0 -0
  59. /package/dist/{chunk-ZACRRYWL.js.map → chunk-JZBCLNDU.js.map} +0 -0
  60. /package/dist/{chunk-O2PWC2YW.js.map → chunk-KNDDCVQJ.js.map} +0 -0
package/README.md CHANGED
@@ -77,6 +77,8 @@ import { z } from "zod";
77
77
  import { cardSchema, taskSchema } from "../schemas";
78
78
 
79
79
  export const taskContract = defineContract("taskService", {
80
+ // what the service is for: MCP tools and the generated docs show it
81
+ describe: "Tasks on a project's board.",
80
82
  // the full row; it must contain `id: string`
81
83
  entity: taskSchema,
82
84
  // lean shapes of the row
@@ -103,11 +105,13 @@ export const taskContract = defineContract("taskService", {
103
105
  output: z.number(),
104
106
  // fetched again whenever the project's board changes
105
107
  watch: { collection: "board", scope: (input) => input.projectId },
108
+ describe: "Counts the tasks on a project's board.",
106
109
  }),
107
110
  },
108
111
  collections: {
109
112
  // every task of a project, live, in board order
110
113
  board: {
114
+ describe: "A project's tasks, in board order.",
111
115
  scope: "projectId",
112
116
  item: "card",
113
117
  order: [
@@ -482,25 +486,49 @@ any server code (design: `docs/rfcs/0003-v5.md`, section 2).
482
486
 
483
487
  ```ts
484
488
  export const labelContract = defineContract("labelService", {
489
+ describe: "Labels a project puts on its tasks.",
485
490
  entity: labelSchema,
486
491
  projections: { chip: z.object({ id: z.string(), name: z.string() }) },
487
492
  methods: {
488
- find: query({ input: z.object({ name: z.string() }), output: nullable("entity") }),
489
- chips: query({ input: z.object({ projectId: z.string() }), output: listOf("chip") }),
490
- usage: query({ input: z.undefined(), output: z.record(z.string(), z.number()) }),
493
+ find: query({
494
+ input: z.object({ name: z.string() }),
495
+ output: nullable("entity"),
496
+ describe: "Finds a label by its name, or null.",
497
+ }),
498
+ chips: query({
499
+ input: z.object({ projectId: z.string() }),
500
+ output: listOf("chip"),
501
+ describe: "Lists a project's labels as chips.",
502
+ }),
503
+ usage: query({
504
+ input: z.undefined(),
505
+ output: z.record(z.string(), z.number()),
506
+ describe: "Counts the tasks of each label.",
507
+ }),
491
508
  },
492
509
  });
493
510
 
494
511
  // No entity: an RPC-only service, with no projections, field tiers or collections.
495
512
  export const healthContract = defineContract("healthService", {
496
- methods: { ping: query({ input: z.undefined(), output: z.literal("pong") }) },
513
+ describe: "Tells a caller the server is up.",
514
+ methods: {
515
+ ping: query({
516
+ input: z.undefined(),
517
+ output: z.literal("pong"),
518
+ describe: "Answers pong while the server runs.",
519
+ }),
520
+ },
497
521
  });
498
522
  ```
499
523
 
500
524
  - Every method is a `query` or a `mutation`, with an `input` and an
501
525
  `output`. The kind decides request sharing, cancellation, the concurrency
502
- cap, the MCP read-only hint and which client hook exists. `describe` is the
503
- method's description for people and agents (the MCP tool's description).
526
+ cap, the MCP read-only hint and which client hook exists.
527
+ - Every member takes a `describe`, a sentence or two for people and agents:
528
+ the contract itself (what the service is for), each method (its MCP tool's
529
+ description), collection, stream, channel and event. `quickdraw-docs`
530
+ leads each section of the API docs with it. It is optional in the types;
531
+ lint's `require-describe` (a warning) lists each member without one.
504
532
  - `output` is a schema, or a projection: `"entity"`, a named projection,
505
533
  `nullable("entity")` or `listOf("card")`. A handler returns database rows
506
534
  for a projection output, and the framework projects them.
@@ -693,10 +721,20 @@ export const taskService = qd.defineService(task, {
693
721
  handler: async ({ db }) => (await db.task.updateMany({ data: { status: "archived" } })).count,
694
722
  },
695
723
  claim: {
696
- access: custom((ctx, input) => input.id.length > 0 && ctx.principal.kind === "user"),
724
+ access: { entry: "Moderate" },
725
+ // users only: an agent acting for one is refused, whatever its grants
726
+ kinds: ["user"],
697
727
  handler: ({ input, ctx, db }) =>
698
728
  db.task.update({ where: { id: input.id }, data: { assigneeId: ctx.principal.userId } }),
699
729
  },
730
+ triage: {
731
+ // a check on a token's claims stays a custom form
732
+ access: custom(
733
+ (ctx, input) => ctx.principal.claims?.scope === "triage" && input.status !== "archived",
734
+ ),
735
+ handler: ({ input, db }) =>
736
+ db.task.update({ where: { id: input.id }, data: { status: input.status } }),
737
+ },
700
738
  },
701
739
  });
702
740
  ```
@@ -708,6 +746,20 @@ export const taskService = qd.defineService(task, {
708
746
  `custom(fn)`. Without a principal every form but `"public"` answers
709
747
  `UNAUTHENTICATED`; a principal that fails gets `FORBIDDEN`. The levels,
710
748
  lowest first, are `Public`, `Read`, `Moderate` and `Admin`.
749
+ - `kinds`, beside `access`, says which kinds of principal
750
+ (`principal.kind`) may call: `initQuickdraw({ kinds: ["user", "agent"] })`
751
+ for every service the app defines, `defineService(contract, { kinds })`
752
+ for one service, and a method's own `kinds` (`claim` above). Each level
753
+ only narrows the one above it: a wider or empty list is refused when the
754
+ service is defined. A principal of another kind, or without a `kind`,
755
+ gets `FORBIDDEN` before the form is asked, and no grant passes the check,
756
+ a service-wide `Admin` grant included, since a token acting with someone's
757
+ grants is what it stops. An anonymous caller is left to the form, so a
758
+ `"public"` method takes no `kinds` of its own: a refused caller would call
759
+ it signed out. A service's list also holds for its entity, collection,
760
+ topic and stream subscriptions, and its channels drop the messages of
761
+ other kinds. A check on a claim, such as a token's `scope`, stays a
762
+ `custom` form (`triage` above).
711
763
  - On a service with a policy, a method whose input has `id` under a form
712
764
  that checks no row (`"public"`, `"authenticated"`, `{ service: L }` below
713
765
  `Admin`) would let anyone that form admits reach any row by its id, so
@@ -719,7 +771,8 @@ export const taskService = qd.defineService(task, {
719
771
  `rowless: ["get"]` in the kit's options. The input's keys come from its
720
772
  JSON Schema: an `id` in any branch of a union counts, and so does one
721
773
  beside a value JSON Schema cannot write (a `Date`, a `Set`). Not checked:
722
- an input without JSON Schema (Zod 3), one that is no object (a bare string
774
+ an input without JSON Schema (Zod 3) or whose JSON Schema names no keys (a
775
+ `todoSchema` without `keys`), one that is no object (a bare string
723
776
  that is the id itself) and a row named by another key (`ids`, `taskId`, a
724
777
  nested `where.id`).
725
778
  - A service-wide `Admin` grant passes every check on its service
@@ -977,11 +1030,13 @@ authorizes a scope:
977
1030
 
978
1031
  ```ts
979
1032
  export const task = defineContract("taskService", {
1033
+ describe: "Tasks on a project's board.",
980
1034
  entity: taskSchema,
981
1035
  projections: { card: cardSchema },
982
1036
  methods: { ...crud.contract({ entity: taskSchema, get: true }) },
983
1037
  collections: {
984
1038
  byProject: {
1039
+ describe: "A project's open tasks, in board order.",
985
1040
  // a column holding the scope value
986
1041
  scope: "projectId",
987
1042
  // the projection each item is sent as
@@ -998,7 +1053,12 @@ export const task = defineContract("taskService", {
998
1053
  views: { mine: (row, who) => row.assigneeId === who.userId },
999
1054
  },
1000
1055
  // each user's own
1001
- assigned: { scope: "assigneeId", item: "card", order: [["id", "asc"]] },
1056
+ assigned: {
1057
+ describe: "The tasks assigned to a user.",
1058
+ scope: "assigneeId",
1059
+ item: "card",
1060
+ order: [["id", "asc"]],
1061
+ },
1002
1062
  },
1003
1063
  });
1004
1064
  ```
@@ -1141,18 +1201,36 @@ as a 4.x client instead of being refused with `PROTOCOL_MISMATCH`. The shim
1141
1201
  serves request/response calls only: `socket.emit("taskService:get", payload, ack)`
1142
1202
  runs through the 5.0 pipeline and is answered in the 4.x `ServiceResponse`
1143
1203
  shape, `{ success: true, data }` or `{ success: false, error, code }`, with
1144
- the HTTP status of the error code as `code`. 4.x subscriptions, collections
1145
- and channels are not served. Each service, method and principal kind that
1146
- calls through the shim is logged once at `warn`, so the remaining 4.x
1147
- clients can be found.
1204
+ the HTTP status of the error code as `code`. A call through the shim runs
1205
+ with the `ctx.socketId` and `ctx.rooms` of the socket it arrived on, so a
1206
+ handler can put a 4.x client in an app room; it leaves its rooms when it
1207
+ disconnects, and `onRoomLeave` and presence hear it as they hear a 5.0
1208
+ socket. 4.x subscriptions, collections and channels are not served, and a
1209
+ contract's events reach only 5.0 sockets: delivering events to 4.x
1210
+ listeners stays the app's own raw emit to the room until those clients
1211
+ speak protocol 5. Each service, method and principal kind that calls
1212
+ through the shim is logged once at `warn`, so the remaining 4.x clients can
1213
+ be found.
1214
+
1215
+ A service renamed during the migration keeps its old name for 4.x clients
1216
+ through aliases: with `legacyWire: { aliases: { taskService: "cardService" } }`,
1217
+ `socket.emit("taskService:get", payload, ack)` runs `cardService.get`, with
1218
+ that method's validation, access and `onCall` (grants are read under
1219
+ `cardService`, never under the old name). Its log line names the alias, once
1220
+ per old name, method and principal kind, so the app can see when the old name
1221
+ is no longer called. Each alias must name a registered service and must not be
1222
+ one itself, or `createServer` throws. Only 4.x events are aliased: a protocol-5,
1223
+ HTTP or MCP call to `taskService` finds no service.
1148
1224
 
1149
1225
  ### MCP bridge
1150
1226
 
1151
1227
  `@fitzzero/quickdraw-core/server/mcp` serves the services to AI agents as MCP
1152
1228
  tools generated from their contracts at startup: one tool per method, named
1153
- `{service}_{method}`, described by the method's `describe` text, with the
1229
+ `{service}_{method}`, described by the method's `describe` text (without
1230
+ one, by `service.method (kind)`, which tells an agent nothing), with the
1154
1231
  input schema's JSON Schema as its arguments and `readOnlyHint` on every
1155
- query. Every tool call goes through the dispatcher with transport `"mcp"`,
1232
+ query. The API docs print each method's default tool name and its read-only
1233
+ hint. Every tool call goes through the dispatcher with transport `"mcp"`,
1156
1234
  so input validation, access checks and limits apply exactly as on a socket.
1157
1235
  A method whose input cannot describe itself as JSON Schema stops the
1158
1236
  registry at startup, naming the method, unless it is excluded.
@@ -1217,6 +1295,68 @@ createMcpStdioServer({ registry, name: "my-app", version: "1.0.0" });
1217
1295
  list with `exclude` or `include`, per registry, and serve agents of
1218
1296
  different reach from separate registries.
1219
1297
 
1298
+ `bind` fills chosen arguments from who is calling instead of from the agent,
1299
+ so an agent bound to one project cannot name another in its calls. Here an
1300
+ agent's token names its project as a verified claim (`principal.claims`):
1301
+
1302
+ <!-- example: apps/api/src/mcp.ts#bind -->
1303
+
1304
+ ```ts
1305
+ // agents bound to one project: an agent's token names it as a verified claim
1306
+ const projectAgents = createMcpRegistry({
1307
+ services: [taskService],
1308
+ dispatcher: server.dispatcher,
1309
+ // agent tokens only: anyone else is anonymous, and every tool here refuses them
1310
+ principal: (request) =>
1311
+ verifyAgentToken(request.transport === "http" ? request.token : process.env.AGENT_TOKEN),
1312
+ // filled from the claim on every call, and left out of every tool's arguments
1313
+ bind: { projectId: ({ principal }) => principal.claims?.projectId },
1314
+ include: ["taskService.create", "taskService.countOnBoard"],
1315
+ // the tools' public names: create_task and count_tasks
1316
+ name: (_service, method) => (method === "create" ? "create_task" : "count_tasks"),
1317
+ });
1318
+
1319
+ // beside the first registry, at a path of its own: GET /agents/tools, POST /agents/invoke
1320
+ app.use(createMcpHttpRouter({ registry: projectAgents, path: "/agents" }));
1321
+ ```
1322
+
1323
+ - Each bound argument is left out of the `inputSchema` of every method's tool
1324
+ whose object input has it: `create_task` lists `id` and `title`, and
1325
+ `count_tasks` takes no arguments. Every call of such a tool gets the
1326
+ binder's value before its input is validated, through `tools/call` and both
1327
+ shapes of `POST /mcp/invoke` alike.
1328
+ - A call fails closed, before the method runs: `UNAUTHENTICATED` for an
1329
+ anonymous caller, and `FORBIDDEN` when the binder returns `undefined` or
1330
+ `null`, or when the agent sent the argument with another value. A binder
1331
+ that throws fails the call with `INTERNAL`, or with the code of the
1332
+ `QuickdrawError` it threw.
1333
+ - `createMcpRegistry` throws at startup when a bound argument is in no
1334
+ selected method's object input (a misspelled name would leave the real
1335
+ argument to the agent), and when it is a property of an input that is not
1336
+ an object, which it names: leave that method out, or make its input an
1337
+ object.
1338
+ - Custom tools are never bound, and neither are the calls their `caller`
1339
+ makes: a custom tool reads `principal` itself. `describeTools` and the API
1340
+ docs list the inputs as the contracts declare them, with nothing bound.
1341
+ - Binding is not access control. The same principal reaches the method over a
1342
+ socket or HTTP with nothing bound, so the method's access must still
1343
+ confine it to its project.
1344
+
1345
+ Two more needs are met by the options above, with nothing added:
1346
+
1347
+ - **A tool set per kind of caller.** Make one registry per kind, each with its
1348
+ own `include`, `bind` and `principal`, and serve each at its own `path` or
1349
+ from its own stdio entry, as above. A registry's `principal` hook refuses
1350
+ the other kinds: throwing fails their calls with `UNAUTHENTICATED`.
1351
+ - **Frozen public tools.** `name` gives a method's tool any public name, as
1352
+ `create_task` above. When agents depend on tool names and argument shapes
1353
+ that must never change, give that surface a contract of its own: its
1354
+ methods' inputs and `describe` texts are the frozen shapes, its handlers
1355
+ call the app's services through `ctx.services`, and a registry with
1356
+ `name: (_service, method) => method` serves it. The bridge never reshapes a
1357
+ tool's arguments on the way in: such a tool would be invisible to the API
1358
+ docs and to lint, which read the contracts.
1359
+
1220
1360
  ## The client
1221
1361
 
1222
1362
  `createQuickdrawClient(contracts)` builds one typed client from the
@@ -1524,6 +1664,7 @@ const newTaskSchema = z.object({ projectId: z.string(), title: z.string() });
1524
1664
  const taskPatch = z.object({ title: z.string(), status: z.string() }).partial();
1525
1665
 
1526
1666
  export const task = defineContract("taskService", {
1667
+ describe: "Tasks on a project's board.",
1527
1668
  entity: taskSchema,
1528
1669
  projections: { card: cardSchema },
1529
1670
  methods: {
@@ -1541,7 +1682,11 @@ export const task = defineContract("taskService", {
1541
1682
  bulkUpdate: { input: taskPatch },
1542
1683
  bulkDelete: true,
1543
1684
  }),
1544
- archive: mutation({ input: z.object({ id: z.string() }), output: "entity" }),
1685
+ archive: mutation({
1686
+ input: z.object({ id: z.string() }),
1687
+ output: "entity",
1688
+ describe: "Archives a task, which leaves its board.",
1689
+ }),
1545
1690
  },
1546
1691
  });
1547
1692
  ```
@@ -1686,6 +1831,7 @@ import { defineContract, search } from "@fitzzero/quickdraw-core";
1686
1831
  import { cardSchema, taskSchema } from "../schemas";
1687
1832
 
1688
1833
  export const task = defineContract("taskService", {
1834
+ describe: "Tasks on a project's board.",
1689
1835
  entity: taskSchema,
1690
1836
  projections: { card: cardSchema },
1691
1837
  methods: {
@@ -1700,6 +1846,7 @@ export const task = defineContract("taskService", {
1700
1846
  },
1701
1847
  collections: {
1702
1848
  byProject: {
1849
+ describe: "A project's tasks, in board order.",
1703
1850
  scope: "projectId",
1704
1851
  item: "card",
1705
1852
  order: [
@@ -1825,6 +1972,7 @@ import { z } from "zod";
1825
1972
  const projectSchema = z.object({ id: z.string(), name: z.string() });
1826
1973
 
1827
1974
  export const project = defineContract("projectService", {
1975
+ describe: "Projects and the people they are shared with.",
1828
1976
  entity: projectSchema,
1829
1977
  methods: {
1830
1978
  // the JSON access list jsonAcl reads: share, unshare, setLevel, listShares
@@ -1838,6 +1986,7 @@ export const project = defineContract("projectService", {
1838
1986
  collections: {
1839
1987
  // each user's projects: an invite adds the project, a remove or a leave takes it out
1840
1988
  mine: {
1989
+ describe: "The projects a user is a member of.",
1841
1990
  scope: via({ model: "projectMember", entry: "projectId", scope: "userId" }),
1842
1991
  item: "entity",
1843
1992
  order: [["id", "asc"]],
@@ -1931,6 +2080,7 @@ import { admin, defineContract } from "@fitzzero/quickdraw-core";
1931
2080
  import { taskSchema } from "../schemas";
1932
2081
 
1933
2082
  export const task = defineContract("taskService", {
2083
+ describe: "Tasks on a project's board.",
1934
2084
  // Zod 4.2 or later: the fields come from its JSON Schema
1935
2085
  entity: taskSchema,
1936
2086
  methods: {
@@ -2114,28 +2264,48 @@ const cursorSchema = z.object({ projectId: z.string(), taskId: z.string(), x: z.
2114
2264
  const logLineSchema = z.object({ line: z.string() });
2115
2265
 
2116
2266
  export const task = defineContract("taskService", {
2267
+ describe: "Tasks on a project's board.",
2117
2268
  entity: taskSchema,
2118
2269
  methods: {
2119
- enterBoard: mutation({ input: z.object({ projectId: z.string() }), output: z.boolean() }),
2270
+ enterBoard: mutation({
2271
+ input: z.object({ projectId: z.string() }),
2272
+ output: z.boolean(),
2273
+ describe: "Joins the caller's socket to a project's board room.",
2274
+ }),
2120
2275
  },
2121
2276
  streams: {
2122
2277
  // one feed per task; a subscriber needs Read on the task, and first gets the latest 50 lines
2123
- logs: { item: logLineSchema, scope: "taskId", seed: 50, access: { entry: "Read" } },
2278
+ logs: {
2279
+ item: logLineSchema,
2280
+ scope: "taskId",
2281
+ seed: 50,
2282
+ access: { entry: "Read" },
2283
+ describe: "A task's log lines, as its job writes them.",
2284
+ },
2124
2285
  // one feed for everyone
2125
- load: { item: z.number(), volatile: true, access: "authenticated" },
2286
+ load: {
2287
+ item: z.number(),
2288
+ volatile: true,
2289
+ access: "authenticated",
2290
+ describe: "The server's load, sampled every second.",
2291
+ },
2126
2292
  },
2127
2293
  channels: {
2128
2294
  // 20 a second per socket; only from a socket in the board's room, which enterBoard joined
2129
2295
  cursor: {
2296
+ describe: "Where a user's cursor is on a task card.",
2130
2297
  payload: cursorSchema,
2131
2298
  ratePerSecond: 20,
2132
2299
  requires: { room: (cursor) => `board:${cursor.projectId}` },
2133
2300
  },
2134
2301
  },
2135
2302
  events: {
2136
- cursorMoved: { payload: cursorSchema },
2303
+ cursorMoved: { payload: cursorSchema, describe: "Another user's cursor moved." },
2137
2304
  // a user's last socket left a board: `onRoomLeave` sends it
2138
- leftBoard: { payload: z.object({ projectId: z.string(), userId: z.string() }) },
2305
+ leftBoard: {
2306
+ payload: z.object({ projectId: z.string(), userId: z.string() }),
2307
+ describe: "A user left a project's board.",
2308
+ },
2139
2309
  },
2140
2310
  });
2141
2311
  ```
@@ -2795,7 +2965,83 @@ for real, once per allowed principal: give inputs that can run again, or
2795
2965
  `MatrixCell` `{ name, principal }`, sync or async), called before each
2796
2966
  cell's call, that makes a row of its own (a task
2797
2967
  to delete, an unused name), so no cell depends on the order of the
2798
- principals.
2968
+ principals. Each cell of the report records its principal's `kind`, and a
2969
+ cell that differs is listed with it: on a service or method with `kinds`,
2970
+ give the matrix a principal of each kind it refuses.
2971
+
2972
+ ### Access snapshots
2973
+
2974
+ `snapshotAccessMatrix(app, { principals, reset, rows })` records the whole
2975
+ matrix instead of chosen cases: every method of the app's services (or of
2976
+ `services`), each entity subscribe (`qd:sub`) and each collection scope
2977
+ (`qd:col:sub`), as every principal and anonymously. The first run writes the
2978
+ outcomes to `__access__/<test file>.json` beside the test, and every later
2979
+ run compares its matrix with that file, so each changed cell is a decision
2980
+ someone made. Commit the file. Use it to pin a service's table, and before
2981
+ a migration, a policy refactor or a new kind of principal:
2982
+
2983
+ <!-- example: apps/api/src/services/task.test.ts#snapshot -->
2984
+
2985
+ ```ts
2986
+ it("pins who may call what, for every method, subscribe and scope", async () => {
2987
+ const app = await createTestApp({ services: [projectService, taskService], db });
2988
+ const report = await snapshotAccessMatrix(app, {
2989
+ principals: { owner: ada, member: bo, stranger: ed },
2990
+ reset: async () => {
2991
+ await resetDatabase(prisma); // at the start, and after each write access let through
2992
+ return await seed();
2993
+ },
2994
+ rows: (board) => ({ projectService: board.projectId, taskService: board.taskId }),
2995
+ });
2996
+ expect(report.inconclusive).toEqual([]); // every cell ran, with an input its schema accepts
2997
+ await app.close();
2998
+ });
2999
+ ```
3000
+
3001
+ - `reset()` empties the database and seeds it (`resetDatabase` from
3002
+ `./testing/prisma`, then the app's seed, with the untracked client), and
3003
+ resolves with what the seed made: the fixture. It runs first, and again
3004
+ after every mutation the pipeline did not refuse, so no cell sees the
3005
+ writes of another. `principals` may be a function of the fixture, for a
3006
+ seed that makes new ids; its names stay the same.
3007
+ - `rows(fixture)` names the row each service's cells are about: a row id,
3008
+ or named variants (`{ own: p1, other: p2 }`), each a row of the matrix of
3009
+ its own. It names every service an access form, an entity subscribe or a
3010
+ collection anchor of the matrix reads; a missing one is a `TypeError`
3011
+ before any cell runs. A `"self"` scope is subscribed with the caller's own
3012
+ user id.
3013
+ - Inputs are made from each method's input schema (Zod 4.2 or later
3014
+ describes itself as JSON Schema): the row goes where the access form reads
3015
+ it (`id`, the key an `entry` or `scope` form names, or where a function
3016
+ `id` finds it; for any other form, the input's `id`), and every other
3017
+ required value is as small as the schema allows. `inputs(ref, fixture)`
3018
+ gives a method's input instead (`ref` names the service, method, kind,
3019
+ variant, row and principal of the cell); `undefined` keeps the made one.
3020
+ A cell whose input fails the schema, or names no row where an `entry` or
3021
+ `scope` form reads it, is never called: it is `VALIDATION`, and the
3022
+ report lists it in `inconclusive`.
3023
+ - Each cell is the exact outcome: `"ok"`, or the error code
3024
+ (`UNAUTHENTICATED`, `FORBIDDEN`, `NOT_FOUND`, ...). Each principal's
3025
+ `kind` is recorded beside the cells.
3026
+ - A cell whose outcome changed fails, naming the cell, both outcomes and
3027
+ whether the change opens or closes access (a changed `kind` fails too):
3028
+
3029
+ ```text
3030
+ snapshotAccessMatrix: 1 cell differs from the access snapshot (src/services/__access__/task.test.ts.json):
3031
+ taskService.rename as member: FORBIDDEN → ok (opens access)
3032
+ ```
3033
+
3034
+ When each change is meant, run the tests with
3035
+ `QD_UPDATE_ACCESS_SNAPSHOT=1` and commit the file.
3036
+
3037
+ - Cells added or removed (a new method, principal or exclusion) rewrite the
3038
+ file in a local run. Under CI (`CI=1` or `CI=true`) they fail, and so do a
3039
+ missing file and `QD_UPDATE_ACCESS_SNAPSHOT`: a snapshot that writes
3040
+ itself pins nothing.
3041
+ - Leave out a method whose outcome differs from run to run with
3042
+ `exclude: ["taskService.roll"]`; the file lists it. Take one snapshot per
3043
+ test file (one test file per service keeps merges small), or pass
3044
+ `{ file }`.
2799
3045
 
2800
3046
  ### Performance budgets
2801
3047
 
@@ -3084,10 +3330,13 @@ is also a server span named `service.method`, with an error status for
3084
3330
  ## API docs from contracts
3085
3331
 
3086
3332
  The `quickdraw-docs` command writes Markdown API docs from the contracts:
3087
- one page per service (its entity and field tiers, projections, methods
3088
- with their kind, input fields and output, collections, streams, channels
3089
- and events, from the schemas' JSON Schema where they have one) and a
3090
- `README.md` index. It reads contracts (and, with `--services`, the
3333
+ one page per service (its `describe`, its entity and field tiers,
3334
+ projections, methods with their `describe`, kind, default MCP tool name
3335
+ (`{service}_{method}`: a registry's `name`, `include` and `exclude` options
3336
+ are not visible to the command), the MCP read-only hint on a query, input
3337
+ fields and output, collections, streams, channels and events with their
3338
+ `describe`, from the schemas' JSON Schema where they have one) and a
3339
+ `README.md` index that lists each service's `describe`. It reads contracts (and, with `--services`, the
3091
3340
  services' definitions), never source code.
3092
3341
 
3093
3342
  ```bash
@@ -3103,9 +3352,18 @@ definitions: an "Access" section with the row policy, whether a
3103
3352
  service-wide `Admin` grant passes every check, who may watch the change
3104
3353
  topic (`watchAccess`) and the field levels; each method's access form, in
3105
3354
  words, and its `rowless`; who may open a collection's scope; a channel's
3106
- access; a stream's computed seed and when its items are checked. A
3107
- contract the services module has no service for says so, and a service
3108
- without a contract is an error. Pass the same flag to `--check`. The
3355
+ access; a stream's computed seed and when its items are checked. Each
3356
+ page also gets a "Depends on" section: the other services its
3357
+ declarations name, and which declaration names each one. A service depends
3358
+ on another when its row policy `inherit`s from it, it `writes` the model
3359
+ that service's rows live in, it `affects` that service's rows, one of its
3360
+ methods, channels or streams takes a `{ scope, of }` form of it, or one of
3361
+ its collections is anchored on that service's rows. A `writes` model that no
3362
+ service's rows live in is listed as a model. The index adds a Mermaid
3363
+ `graph LR` of the same edges, each labelled with how. Only declarations are
3364
+ read: a handler that reads another service's rows without declaring it adds
3365
+ nothing. A contract the services module has no service for says so, and a
3366
+ service without a contract is an error. Pass the same flag to `--check`. The
3109
3367
  services module is imported with everything it imports, as the server
3110
3368
  loads it: the workspace packages it uses (`@project/db`, `@project/shared`)
3111
3369
  load from their built output when their `package.json` points there, so
@@ -3121,7 +3379,19 @@ extensionless imports and `tsconfig` paths, which Node's loader refuses); a
3121
3379
  module whose own code throws runs once and its error is reported as is. Pages are only ever replaced
3122
3380
  or removed when they start with the generator's marker, and they are laid
3123
3381
  out as oxfmt and Prettier format Markdown, so formatting them changes
3124
- nothing. Run `--check` in CI next to the lint step.
3382
+ nothing.
3383
+
3384
+ Where the pages live depends on how busy the repository is:
3385
+
3386
+ - **Few pull requests at a time:** commit the pages and run `--check` in
3387
+ CI next to the lint step.
3388
+ - **Many at a time** (several agents, each changing methods): a committed
3389
+ generated file is a merge conflict in every pair of pull requests that
3390
+ touch contracts. Regenerate the pages on each push to the base branch
3391
+ instead, with the workflow the `quickdraw-api-docs` skill of
3392
+ [`@fitzzero/quickdraw-skills`](https://github.com/fitzzero/quickdraw/tree/main/packages/skills) ships (a job with a
3393
+ read-only token generates them, a second job commits them), and never
3394
+ commit them by hand.
3125
3395
 
3126
3396
  ## Lint rules and agent guidance
3127
3397
 
@@ -3129,7 +3399,8 @@ nothing. Run `--check` in CI next to the lint step.
3129
3399
  config every 5.0 app extends: it reports untracked and foreign writes, nested
3130
3400
  and raw SQL writes, hand-sent frames, inline auth guards, unbounded reads,
3131
3401
  database calls and emits in loops, layering breaks, bypasses of the typed
3132
- client, hand-written copies of kit methods (`prefer-kit`, a warning), and
3402
+ client, hand-written copies of kit methods (`prefer-kit`, a warning),
3403
+ contract members without a `describe` (`require-describe`, a warning), and
3133
3404
  every removed 4.x API with its replacement. Each rule supports a baseline,
3134
3405
  so an app can adopt it before fixing old code.
3135
3406
 
@@ -3183,7 +3454,7 @@ bunx @fitzzero/quickdraw-codemod v5 .
3183
3454
  | `./client` | `createQuickdrawClient`, `QuickdrawProvider`, `useQuickdraw`, `usePresence`, `useAdminServices`, `createQuickdrawConnection`, `call`, `callData`, `liveDataOf`, the coordinator; everything in `./utils` |
3184
3455
  | `./utils` | `createServerCaller`, cache keys (`methodKey`, `entityKey`, `collectionKey`), formatting, navigation, `parseJWTPayload` |
3185
3456
  | `./parser` | the JSON-only Socket.IO parser |
3186
- | `./testing` | `createTestApp`, `describeAccessMatrix`, `expectBudget`, `createRecordingSink`, `DevWarningError` |
3457
+ | `./testing` | `createTestApp`, `describeAccessMatrix`, `snapshotAccessMatrix`, `expectBudget`, `createRecordingSink`, `DevWarningError` |
3187
3458
  | `./testing/client` | `renderWithQuickdraw`, and everything in `./testing/mock` |
3188
3459
  | `./testing/mock` | `createMockClient` alone, without Testing Library: for browser bundles such as Storybook |
3189
3460
  | `./testing/prisma` | test databases on PostgreSQL or PGlite |
@@ -1,5 +1,5 @@
1
- import { e as StandardSchemaWithJSON, d as StandardSchemaV1, I as InferInput, c as InferOutput, X as RowSchema, Q as QueryDef, P as ProjectionList, p as MutationDef, J as AnyContract, ap as MethodName } from './infer-Dw21kmLw.js';
2
- import { d as ListSort, c as ListQuery, b as ListPage, D as AdminWritable, u as AdminListInput, v as AdminListQuery, y as AdminPage, r as AdminCreateInput, s as AdminCreateQuery, B as AdminUpdateInput, C as AdminUpdateQuery, w as AdminMetaInput, n as AdminServiceMeta, z as AdminSubscribers } from './adminSchemas-zji11V74.js';
1
+ import { e as StandardSchemaWithJSON, d as StandardSchemaV1, I as InferInput, c as InferOutput, X as RowSchema, Q as QueryDef, P as ProjectionList, p as MutationDef, J as AnyContract, ap as MethodName } from './infer-Teo1xpsd.js';
2
+ import { d as ListSort, c as ListQuery, b as ListPage, D as AdminWritable, u as AdminListInput, v as AdminListQuery, y as AdminPage, r as AdminCreateInput, s as AdminCreateQuery, B as AdminUpdateInput, C as AdminUpdateQuery, w as AdminMetaInput, n as AdminServiceMeta, z as AdminSubscribers } from './adminSchemas-2DUA_hZ8.js';
3
3
 
4
4
  /** A schema a kit generates: validation, and its JSON Schema. */
5
5
  type KitSchema<Input, Output = Input> = StandardSchemaWithJSON<Input, Output>;
@@ -1,4 +1,4 @@
1
- import { z as SortDirection, A as AccessLevel } from './infer-Dw21kmLw.js';
1
+ import { z as SortDirection, A as AccessLevel } from './infer-Teo1xpsd.js';
2
2
 
3
3
  /** `list`'s page size when the caller gives none. */
4
4
  declare const LIST_DEFAULT_LIMIT = 50;
@@ -1,7 +1,7 @@
1
1
  import { IncomingMessage, IncomingHttpHeaders } from 'node:http';
2
2
  import { Socket } from 'socket.io';
3
- import { A as AccessLevel } from './infer-Dw21kmLw.js';
4
- import { P as Principal, M as MaybePromise } from './types-Bwg3kIEc.js';
3
+ import { A as AccessLevel } from './infer-Teo1xpsd.js';
4
+ import { P as Principal, M as MaybePromise } from './types-Cu0vmx7c.js';
5
5
 
6
6
  /**
7
7
  * A request as Express leaves it: maybe with `body` from `express.json()`,
@@ -1,7 +1,7 @@
1
- import { ad as Version, a5 as Revision } from './envelope-nf5eGB-a.js';
1
+ import { ad as Version, a5 as Revision } from './envelope-Cp3YcVxC.js';
2
2
  import { L as Logger } from './logger-BY28acW-.js';
3
- import { A as AccessLevel, J as AnyContract, y as CollectionWhere, O as OrderBy, k as MethodKind, d as StandardSchemaV1, ag as EventName, ah as EventPayloadOf, aa as ChannelName, ab as ChannelPayloadOf, az as StreamName, am as IsScopedStream, ay as StreamItemOf, ae as ContractMap, ap as MethodName, al as InputOf, ar as OutputOf } from './infer-Dw21kmLw.js';
4
- import { P as Principal, Q as QuickdrawTypes, M as MaybePromise, c as PrincipalOf, a as McpContext, T as Transport, C as ContextExtensionOf, b as McpContextOf, D as DbOf } from './types-Bwg3kIEc.js';
3
+ import { A as AccessLevel, J as AnyContract, y as CollectionWhere, O as OrderBy, k as MethodKind, d as StandardSchemaV1, ag as EventName, ah as EventPayloadOf, aa as ChannelName, ab as ChannelPayloadOf, az as StreamName, am as IsScopedStream, ay as StreamItemOf, ae as ContractMap, ap as MethodName, al as InputOf, ar as OutputOf } from './infer-Teo1xpsd.js';
4
+ import { P as Principal, Q as QuickdrawTypes, M as MaybePromise, c as PrincipalOf, a as McpContext, T as Transport, C as ContextExtensionOf, b as McpContextOf, D as DbOf } from './types-Cu0vmx7c.js';
5
5
 
6
6
  /** What a development warning is about. */
7
7
  type DevWarningKind = "n-plus-one" | "unbounded-read" | "oversized-response" | "nested-write" | "ambient-write" | "batch-read" | "batch-create-many" | "repeated-call" | "tiered-field-in-output" | "resolver-without-reads";
@@ -381,6 +381,14 @@ interface ServiceMethod {
381
381
  */
382
382
  readonly schemaOutput: SchemaOutput | undefined;
383
383
  readonly access: AccessForm;
384
+ /**
385
+ * The kinds of principal that may call the method (RFC 0003 section 4.1):
386
+ * its own `kinds`, else its service's, else the app's (`initQuickdraw`);
387
+ * `undefined` admits every kind. A principal of another kind, or without
388
+ * one, gets `FORBIDDEN` before the access form is asked; an anonymous
389
+ * caller is left to the form.
390
+ */
391
+ readonly kinds: readonly string[] | undefined;
384
392
  readonly handler: AnyHandler;
385
393
  readonly share: ShareMode | undefined;
386
394
  /** How long a shared result is reused after its run, in milliseconds. */
@@ -439,6 +447,13 @@ interface Service<T extends QuickdrawTypes = QuickdrawTypes, C extends AnyContra
439
447
  readonly watchAccess: WatchAccess | undefined;
440
448
  /** Whether a service-wide `Admin` grant passes every access check of this service. */
441
449
  readonly adminBypass: boolean;
450
+ /**
451
+ * The kinds of principal the service admits (RFC 0003 section 4.1): its
452
+ * own `kinds`, else the app's (`initQuickdraw`); `undefined` admits every
453
+ * kind. Its methods narrow it, and its subscriptions (`qd:sub`,
454
+ * `qd:col:sub`, `qd:watch`, `qd:stream:sub`) and channels check it.
455
+ */
456
+ readonly kinds: readonly string[] | undefined;
442
457
  /** The checked method records, by method name. */
443
458
  readonly methods: Readonly<Record<string, ServiceMethod>>;
444
459
  /** The contract's channels with the handlers `defineService` gave them (RFC 0003 section 12.5). */
@@ -905,6 +920,12 @@ interface ServiceChannel {
905
920
  readonly prefix: string;
906
921
  } | undefined;
907
922
  readonly access: ChannelAccess;
923
+ /**
924
+ * The kinds of principal the channel's service admits, as a set for the
925
+ * per-message check: a message from a socket of another kind, or without
926
+ * one, is dropped. `undefined` admits every kind.
927
+ */
928
+ readonly kinds: ReadonlySet<string> | undefined;
908
929
  readonly handler: AnyChannelHandler;
909
930
  }
910
931
  /** One stream of a defined service, checked and ready to serve. */
@@ -982,7 +1003,8 @@ interface BaseContext<P = Principal, M = McpContext> {
982
1003
  /**
983
1004
  * The socket the call arrived on: its id, as the client socket's `id`,
984
1005
  * `onRoomLeave`'s `socketId` and a channel handler's `ctx.socketId` name
985
- * it (a game keys a player's input by it). `undefined` for a call that
1006
+ * it (a game keys a player's input by it). A call through the 4.x legacy
1007
+ * shim has it too: it arrived on a socket. `undefined` for a call that
986
1008
  * did not arrive over a socket: HTTP, MCP, in process, `ctx.services`. In
987
1009
  * a method that shares its runs (`share`), the first caller's.
988
1010
  */
@@ -1226,4 +1248,4 @@ type CallerFor<T extends QuickdrawTypes> = T extends {
1226
1248
  readonly contracts: infer Contracts extends ContractMap;
1227
1249
  } ? Caller<Contracts[keyof Contracts]> : Caller<AnyContract>;
1228
1250
 
1229
- export { type ProjectedOutput as $, type AnyService as A, type PublicAccess as B, type CountArgs as C, DevWarningError as D, type HandlerArgs as E, type FindManyArgs as F, type CustomAccess as G, type HandlerContext as H, type ShareMode as I, type ChannelOptions as J, type StreamOptions as K, type ChannelsRequired as L, type ModelName as M, type BaseContext as N, type CallerFor as O, type Presence as P, type AccessPolicy as Q, type RowLevels as R, type StorageAdapter as S, type AnyAccessPolicy as T, type UnitOfWorkFactory as U, type PolicyColumns as V, type WriteRecord as W, type PolicyForeign as X, type PolicyTools as Y, type RowAccess as Z, type AffectsLink as _, type StorageRow as a, type Projection as a0, type TierGroup as a1, type Tiers as a2, DEFAULT_BULK_THRESHOLD as a3, type CollectionScope as a4, type ServiceCollection as a5, type ContextRooms as a6, type ContextServices as a7, type TouchOptions as a8, type ChannelAccess as a9, type AccessForm as aA, type AccessRequest as aB, type AuthenticatedAccess as aC, type EntryAccess as aD, type IdKeyOf as aE, type IdSelector as aF, type ScopeAccess as aG, type ServiceAccess as aH, ANY_FIELD as aI, type UnitOfWork as aJ, type UnitOfWorkScope as aK, formatDevWarning as aL, N_PLUS_ONE_STATEMENTS as aM, type ChannelContext as aa, type ChannelHandler as ab, type ChannelImplementation as ac, type ChannelRoomOf as ad, type RoomLeave as ae, type RoomLeaveReason as af, type RoomLeft as ag, type RoomTarget as ah, type ServiceChannel as ai, type ServiceStream as aj, type StreamImplementation as ak, type StreamPushArgs as al, type StreamPushManyArgs as am, type StreamSeed as an, type StreamSeedContext as ao, type StreamValidation as ap, type CallOptions as aq, type MethodCaller as ar, type ServiceCaller as as, type ForeignColumns as at, type ForeignColumnsOf as au, type MembershipRead as av, type ParentLink as aw, type PolicyKind as ax, type PolicyReads as ay, type RowLevel as az, type StorageWhere as b, type StatementCount as c, type FlushSink as d, type FlushInfo as e, type Service as f, type DevWarning as g, type DevWarningKind as h, type Caller as i, type ServiceMethod as j, type AccessFilter as k, type RunContext as l, type RunOptions as m, type ServerRooms as n, type StreamHandle as o, type AnyContext as p, type AccessEngine as q, type AppRooms as r, storageOf as s, type StreamSubscriptions as t, type RoomLeaveHandler as u, type PolicyFor as v, type RowForms as w, type AccessFor as x, type ModelColumn as y, type WatchAccess as z };
1251
+ export { type ProjectedOutput as $, type AnyService as A, type PublicAccess as B, type CountArgs as C, DevWarningError as D, type HandlerArgs as E, type FindManyArgs as F, type CustomAccess as G, type HandlerContext as H, type ShareMode as I, type ChannelOptions as J, type StreamOptions as K, type ChannelsRequired as L, type ModelName as M, type BaseContext as N, type CallerFor as O, type Presence as P, type AccessPolicy as Q, type RowLevels as R, type StorageAdapter as S, type AnyAccessPolicy as T, type UnitOfWorkFactory as U, type PolicyColumns as V, type WriteRecord as W, type PolicyForeign as X, type PolicyTools as Y, type RowAccess as Z, type AffectsLink as _, type StorageRow as a, type Projection as a0, type TierGroup as a1, type Tiers as a2, DEFAULT_BULK_THRESHOLD as a3, type CollectionScope as a4, type ServiceCollection as a5, type ContextRooms as a6, type ContextServices as a7, type TouchOptions as a8, type ChannelAccess as a9, type AccessForm as aA, type AccessRequest as aB, type AuthenticatedAccess as aC, type EntryAccess as aD, type IdKeyOf as aE, type IdSelector as aF, type ScopeAccess as aG, type ServiceAccess as aH, ANY_FIELD as aI, type UnitOfWork as aJ, type UnitOfWorkScope as aK, formatDevWarning as aL, N_PLUS_ONE_STATEMENTS as aM, type ChannelContext as aa, type ChannelHandler as ab, type ChannelImplementation as ac, type ChannelRoomOf as ad, type RoomLeave as ae, type RoomLeaveReason as af, type RoomLeft as ag, type RoomTarget as ah, type ServiceChannel as ai, type ServiceStream as aj, type StreamImplementation as ak, type StreamPushArgs as al, type StreamPushManyArgs as am, type StreamSeed as an, type StreamSeedContext as ao, type StreamValidation as ap, type CallOptions as aq, type MethodCaller as ar, type ServiceCaller as as, type ForeignColumns as at, type ForeignColumnsOf as au, type MembershipRead as av, type ParentLink as aw, type PolicyKind as ax, type PolicyReads as ay, type RowLevel as az, type StorageWhere as b, type StatementCount as c, type Caller as d, type FlushSink as e, type FlushInfo as f, type ServiceMethod as g, type Service as h, type DevWarning as i, type DevWarningKind as j, type AccessFilter as k, type RunContext as l, type RunOptions as m, type ServerRooms as n, type StreamHandle as o, type AnyContext as p, type AccessEngine as q, type AppRooms as r, storageOf as s, type StreamSubscriptions as t, type RoomLeaveHandler as u, type PolicyFor as v, type RowForms as w, type AccessFor as x, type ModelColumn as y, type WatchAccess as z };