@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.
- package/README.md +303 -32
- package/dist/{admin-CBlTY0do.d.ts → admin-DKwkIh5i.d.ts} +2 -2
- package/dist/{adminSchemas-zji11V74.d.ts → adminSchemas-2DUA_hZ8.d.ts} +1 -1
- package/dist/{auth-CMyRkfjZ.d.ts → auth-BhWqdyb-.d.ts} +2 -2
- package/dist/{caller-BqCYnpmC.d.ts → caller-CXTFswBO.d.ts} +27 -5
- package/dist/{chunk-2XJFNVHW.js → chunk-2PWMUVC7.js} +1 -1
- package/dist/{chunk-2XJFNVHW.js.map → chunk-2PWMUVC7.js.map} +1 -1
- package/dist/{chunk-3JNMVB2V.js → chunk-2UELNHDD.js} +2 -2
- package/dist/{chunk-HQ6WIRE2.js → chunk-3674EIRT.js} +4 -4
- package/dist/{chunk-NS5PUHIZ.js → chunk-A6DJBGOG.js} +130 -23
- package/dist/chunk-A6DJBGOG.js.map +1 -0
- package/dist/{chunk-ZACRRYWL.js → chunk-JZBCLNDU.js} +4 -4
- package/dist/{chunk-O2PWC2YW.js → chunk-KNDDCVQJ.js} +3 -3
- package/dist/{chunk-5YRQ26LF.js → chunk-RT6OILOI.js} +1 -1
- package/dist/chunk-RT6OILOI.js.map +1 -0
- package/dist/{chunk-WVIRXYI4.js → chunk-VJTFXHOK.js} +2 -2
- package/dist/{chunk-WVIRXYI4.js.map → chunk-VJTFXHOK.js.map} +1 -1
- package/dist/chunk-XNLWBZIG.js +9 -0
- package/dist/chunk-XNLWBZIG.js.map +1 -0
- package/dist/cli/quickdraw-docs.js +241 -27
- package/dist/cli/quickdraw-docs.js.map +1 -1
- package/dist/client/index.d.ts +7 -7
- package/dist/client/index.js +4 -4
- package/dist/{clientTypes-DuZW_8mb.d.ts → clientTypes-Bc-G8n-U.d.ts} +3 -3
- package/dist/{connection-DuN8GvcD.d.ts → connection-Cr1BU07W.d.ts} +2 -2
- package/dist/{createServer-CGsJlxM8.d.ts → createServer-CP9eDHZZ.d.ts} +30 -9
- package/dist/{createTestApp-Cg2CZa1g.d.ts → createTestApp-UKVGEmOf.d.ts} +6 -6
- package/dist/{envelope-nf5eGB-a.d.ts → envelope-Cp3YcVxC.d.ts} +1 -1
- package/dist/{http-D3fSFQ57.d.ts → http-Djj2Inuu.d.ts} +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +26 -6
- package/dist/index.js.map +1 -1
- package/dist/{infer-Dw21kmLw.d.ts → infer-Teo1xpsd.d.ts} +17 -0
- package/dist/{metrics-C0WvB7-I.d.ts → metrics-BdBu5o8G.d.ts} +2 -2
- package/dist/prisma/index.d.ts +4 -4
- package/dist/server/auth/index.d.ts +3 -3
- package/dist/server/index.d.ts +47 -26
- package/dist/server/index.js +62 -36
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/index.d.ts +51 -11
- package/dist/server/mcp/index.js +129 -8
- package/dist/server/mcp/index.js.map +1 -1
- package/dist/server/otel.d.ts +3 -3
- package/dist/{sharing-CoreezAq.d.ts → sharing-DVa8W-yR.d.ts} +3 -3
- package/dist/testing/client.d.ts +13 -13
- package/dist/testing/client.js +5 -5
- package/dist/testing/index.d.ts +156 -12
- package/dist/testing/index.js +1036 -21
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock.d.ts +5 -5
- package/dist/testing/mock.js +4 -4
- package/dist/{types-Bwg3kIEc.d.ts → types-Cu0vmx7c.d.ts} +8 -2
- package/dist/utils/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunk-5YRQ26LF.js.map +0 -1
- package/dist/chunk-NS5PUHIZ.js.map +0 -1
- /package/dist/{chunk-3JNMVB2V.js.map → chunk-2UELNHDD.js.map} +0 -0
- /package/dist/{chunk-HQ6WIRE2.js.map → chunk-3674EIRT.js.map} +0 -0
- /package/dist/{chunk-ZACRRYWL.js.map → chunk-JZBCLNDU.js.map} +0 -0
- /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({
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
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.
|
|
503
|
-
|
|
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:
|
|
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)
|
|
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: {
|
|
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`.
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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
|
|
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.
|
|
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({
|
|
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({
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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,
|
|
3088
|
-
with their kind,
|
|
3089
|
-
|
|
3090
|
-
|
|
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.
|
|
3107
|
-
|
|
3108
|
-
|
|
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.
|
|
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),
|
|
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-
|
|
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-
|
|
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,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-
|
|
4
|
-
import { P as Principal, M as MaybePromise } from './types-
|
|
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-
|
|
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-
|
|
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-
|
|
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).
|
|
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
|
|
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 };
|