@fitzzero/quickdraw-core 5.0.0-rc.4 → 5.0.0-rc.6

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 (75) hide show
  1. package/README.md +213 -55
  2. package/dist/{admin-ByHvKBth.d.ts → admin-CsXR7Zwy.d.ts} +2 -2
  3. package/dist/{adminSchemas-C1U6_voe.d.ts → adminSchemas-Dufu8r7a.d.ts} +1 -1
  4. package/dist/{auth-Cc_XKBKj.d.ts → auth-CMyRkfjZ.d.ts} +33 -4
  5. package/dist/{caller-B-7Ib98g.d.ts → caller-N1zmIB8c.d.ts} +26 -4
  6. package/dist/{chunk-Y5YAG75H.js → chunk-4HOXH4V3.js} +2 -2
  7. package/dist/{chunk-Y5YAG75H.js.map → chunk-4HOXH4V3.js.map} +1 -1
  8. package/dist/chunk-6FFNAT7S.js +702 -0
  9. package/dist/chunk-6FFNAT7S.js.map +1 -0
  10. package/dist/{chunk-QMCIKLWC.js → chunk-FMEHXFPL.js} +1 -1
  11. package/dist/chunk-FMEHXFPL.js.map +1 -0
  12. package/dist/{chunk-IJTZQX6Z.js → chunk-IA2B426W.js} +161 -108
  13. package/dist/chunk-IA2B426W.js.map +1 -0
  14. package/dist/{chunk-7EWHLDGN.js → chunk-MGSMCYRN.js} +289 -82
  15. package/dist/chunk-MGSMCYRN.js.map +1 -0
  16. package/dist/{chunk-TDK6S6V7.js → chunk-NKVWV3FL.js} +1 -1
  17. package/dist/chunk-NKVWV3FL.js.map +1 -0
  18. package/dist/{chunk-RDCIZR4I.js → chunk-O5I72GNW.js} +3 -3
  19. package/dist/{chunk-4PK6O7VL.js → chunk-PDPV254L.js} +162 -41
  20. package/dist/chunk-PDPV254L.js.map +1 -0
  21. package/dist/{chunk-OZ7DJUJY.js → chunk-RLEHORXX.js} +1 -1
  22. package/dist/chunk-RLEHORXX.js.map +1 -0
  23. package/dist/{chunk-B4IOFSM6.js → chunk-X2YCUIEK.js} +16 -5
  24. package/dist/chunk-X2YCUIEK.js.map +1 -0
  25. package/dist/{chunk-7MPXURIO.js → chunk-ZU6L7PHY.js} +241 -50
  26. package/dist/chunk-ZU6L7PHY.js.map +1 -0
  27. package/dist/cli/quickdraw-docs.js +29 -7
  28. package/dist/cli/quickdraw-docs.js.map +1 -1
  29. package/dist/client/index.d.ts +59 -8
  30. package/dist/client/index.js +117 -52
  31. package/dist/client/index.js.map +1 -1
  32. package/dist/{clientTypes-CKqjWKdP.d.ts → clientTypes-CBIEo39b.d.ts} +120 -10
  33. package/dist/{connection-GcBHwKgF.d.ts → connection-DuN8GvcD.d.ts} +9 -2
  34. package/dist/{createServer-6ZLgE-Vs.d.ts → createServer-BxcXQlk8.d.ts} +6 -6
  35. package/dist/{createTestApp-BTFzRxV8.d.ts → createTestApp-CY-RpwCu.d.ts} +6 -6
  36. package/dist/{envelope-B6TUGKsk.d.ts → envelope-nf5eGB-a.d.ts} +17 -4
  37. package/dist/{http-GTJIh3Hm.d.ts → http-DywMt0Yf.d.ts} +12 -12
  38. package/dist/index.d.ts +6 -6
  39. package/dist/index.js +18 -5
  40. package/dist/index.js.map +1 -1
  41. package/dist/{infer-Zs7dAWlI.d.ts → infer-Dw21kmLw.d.ts} +18 -5
  42. package/dist/{metrics-C77VTXEa.d.ts → metrics-C0WvB7-I.d.ts} +2 -2
  43. package/dist/prisma/index.d.ts +4 -4
  44. package/dist/prisma/index.js +73 -5
  45. package/dist/prisma/index.js.map +1 -1
  46. package/dist/server/auth/index.d.ts +81 -22
  47. package/dist/server/auth/index.js +151 -14
  48. package/dist/server/auth/index.js.map +1 -1
  49. package/dist/server/index.d.ts +49 -17
  50. package/dist/server/index.js +87 -21
  51. package/dist/server/index.js.map +1 -1
  52. package/dist/server/mcp/index.d.ts +7 -7
  53. package/dist/server/mcp/index.js +2 -2
  54. package/dist/server/otel.d.ts +3 -3
  55. package/dist/{sharing-F3blmuVL.d.ts → sharing-BSTPqvgD.d.ts} +3 -3
  56. package/dist/testing/client.d.ts +13 -13
  57. package/dist/testing/client.js +5 -5
  58. package/dist/testing/index.d.ts +11 -11
  59. package/dist/testing/index.js +6 -6
  60. package/dist/testing/mock.d.ts +11 -6
  61. package/dist/testing/mock.js +4 -4
  62. package/dist/{types-BBU3rSbi.d.ts → types-Bwg3kIEc.d.ts} +1 -1
  63. package/dist/utils/index.d.ts +1 -1
  64. package/package.json +1 -1
  65. package/dist/chunk-4PK6O7VL.js.map +0 -1
  66. package/dist/chunk-7EWHLDGN.js.map +0 -1
  67. package/dist/chunk-7MPXURIO.js.map +0 -1
  68. package/dist/chunk-B4IOFSM6.js.map +0 -1
  69. package/dist/chunk-DMUGWMNJ.js +0 -325
  70. package/dist/chunk-DMUGWMNJ.js.map +0 -1
  71. package/dist/chunk-IJTZQX6Z.js.map +0 -1
  72. package/dist/chunk-OZ7DJUJY.js.map +0 -1
  73. package/dist/chunk-QMCIKLWC.js.map +0 -1
  74. package/dist/chunk-TDK6S6V7.js.map +0 -1
  75. /package/dist/{chunk-RDCIZR4I.js.map → chunk-O5I72GNW.js.map} +0 -0
package/README.md CHANGED
@@ -531,7 +531,9 @@ export type Card = ItemOf<typeof taskContract, "board">;
531
531
 
532
532
  `InputOf`, `ParsedInputOf`, `OutputOf`, `EntityOf`, `ProjectionOf`,
533
533
  `ItemOf`, `ScopeOf`, `IndexRowOf`, `ViewName` and the rest are exported from
534
- the package root.
534
+ the package root. `FullProjectionOf<C, P>` is a projection's full row (what
535
+ a handler builds, every tiered field present), and `ReceivedRow<C, Row>`
536
+ makes a row's tiered fields optional, as a reader receives it.
535
537
 
536
538
  ## Services
537
539
 
@@ -587,9 +589,21 @@ export const taskService = qd.defineService(task, {
587
589
  - Every call runs a pipeline: look up, concurrency (16 queries in flight per
588
590
  socket and 64 queued, then `RATE_LIMITED`; mutations are not queued behind
589
591
  queries), input validation, access, "not modified", sharing, the handler
590
- under a time limit (30 s by default), output validation outside
591
- production, the reply, the flush, and one completion record (`onCall`).
592
- A mutation ignores the caller's cancel: only its time limit stops it.
592
+ under a time limit (30 s by default), the result shaped to its output
593
+ (below), output validation outside production, the reply, the flush, and
594
+ one completion record (`onCall`). A mutation ignores the caller's cancel:
595
+ only its time limit stops it.
596
+ - A method's output is sent as it declares it, on every transport and in
597
+ production too, so a handler may return the whole row: a projection
598
+ output (`"entity"`, a named projection) sends the projection's keys,
599
+ stripped per caller (below), and an output schema of the method's own
600
+ sends what its JSON Schema declares (Zod 4.2 or later). An object keeps
601
+ the keys its `properties` declare, and every key only where
602
+ `additionalProperties` allows them (`z.looseObject`, `.catchall`) or it is
603
+ a record; an array keeps its items, each reduced by its item schema; a
604
+ union keeps what any branch declares; a value the schema allows to be
605
+ anything (`z.unknown()`, a `Date`) goes as it is. An output schema
606
+ without JSON Schema (Zod 3) is sent as the handler returns it.
593
607
 
594
608
  Errors are `QuickdrawError(code, message, data?)`. Anything else a handler
595
609
  throws reaches the caller as `INTERNAL` with a generic message, and is
@@ -753,6 +767,16 @@ tracked client as `db`; the server finds the rest on it.
753
767
  recorded with its row ids, merged per row, and handed to the flush sinks
754
768
  once the response has been sent, with one revision per flush. A handler
755
769
  may return `db.task.update(...)` without awaiting it.
770
+ - A write that certainly changed nothing is not recorded, so it sends no
771
+ frame, no collection delta, no topic change and no `refreshEntry`: an
772
+ `updateMany`, `updateManyAndReturn` or `deleteMany` that matched no row,
773
+ and a `data` (or an upsert's `update`) with nothing to write, `{}`. An
774
+ upsert with `update: {}` ("make sure this row exists") reads the row
775
+ first in its place: one statement when the row is there, and the upsert
776
+ after it when it is not. Every other write is recorded, one that sets a
777
+ column to the value it already held too: the values the framework reads
778
+ before a write cannot prove it changed nothing, since another write may
779
+ land between that read and the write.
756
780
  - Writes inside `db.$transaction` join the unit only when it commits; a
757
781
  rollback drops them. Prefer the interactive form
758
782
  (`db.$transaction(async (tx) => ...)`): an array-form
@@ -879,10 +903,26 @@ export const taskService = qd.defineService(task, {
879
903
  - A projection's keys come from its schema's JSON Schema (Zod 4.2 or later),
880
904
  or from `project: { <name>: { keys } }`; a service whose projection has
881
905
  neither fails when it is defined. A handler returning a projection returns
882
- rows (a `Date` is fine where the wire has a string, extra columns are
883
- dropped); with `map`, it returns what `map` takes.
906
+ rows (a `Date` is fine where the wire has a string, a Prisma `Json` column
907
+ (`JsonColumnValue`) where it has an object, an array or a record, and
908
+ extra columns are dropped); with `map`, it returns what `map` takes.
884
909
  - Fields the contract's `fields` map puts above the caller's level on a row
885
- are stripped from that caller's copy, after any shared run.
910
+ are stripped from that caller's copy, after any shared run. Only a
911
+ projection output is stripped per caller (`"entity"`, a named projection,
912
+ and `nullable(...)` or `listOf(...)` of one): a method whose output is a
913
+ schema of its own sends what that schema declares to every caller its
914
+ access admits, so a tiered key the schema declares, at any depth (`email`
915
+ in a hand-written `{ id, name, email }`, or in `{ user: { id, email } }`),
916
+ reaches callers below its level. When a server is made, each such method
917
+ raises the development warning `[quickdraw:tiered-field-in-output]`
918
+ (thrown by `createTestApp({ strictWarnings: true })`, so the test app
919
+ fails to start): answer `"entity"` or a projection, or drop the key from
920
+ the schema. Not warned: a method whose access admits no caller below the
921
+ field's level (`{ service: "Admin" }` with the Admin bypass on,
922
+ `{ entry: L }` with `L` at the field's level or above), and a kit's
923
+ methods, which strip their own replies. An output without JSON Schema
924
+ (Zod 3) cannot be reduced, so in development each tiered key its replies
925
+ carry raises the warning when a reply first carries it.
886
926
  - `affects` names rows of other services a write changes too
887
927
  (`{ service, id: column }`, or `{ service, id: (row) => ids, columns }`);
888
928
  they are sent again after the flush, one hop.
@@ -1009,7 +1049,14 @@ export const taskService = qd.defineService(task, {
1009
1049
  authorized like a subscribe to that scope, or `service`, which changes
1010
1050
  whenever any row of the service does, or a row of a model it lists in
1011
1051
  `writes` (a game's high scores, which no service owns). A query declares
1012
- `watch: "service"` to be invalidated by it. The service topic is closed
1052
+ `watch: "service"` to be invalidated by it. A query over some of those
1053
+ models names them, `watch: { service: ["gameScore"] }` (type
1054
+ `ServiceModelsWatch`; the service's `model` and models in its `writes`;
1055
+ `defineService` refuses others), and
1056
+ is invalidated only after a flush that wrote one of them: high scores are
1057
+ not read again when a chat membership the same service writes changes.
1058
+ It is the same topic: its `qd:changed` frame names the models that
1059
+ changed it (`models`). The service topic is closed
1013
1060
  (`FORBIDDEN`) unless the service declares `watchAccess` (`"public"`,
1014
1061
  `"authenticated"` or `{ service: level }`). A watcher that loses access
1015
1062
  leaves the topic after one last `qd:changed`.
@@ -1041,7 +1088,9 @@ over three transports (design: sections 3, 8 and 10):
1041
1088
  principal comes from the session cookie (`__Host-session` over HTTPS,
1042
1089
  `session` over plain HTTP or with `COOKIE_DOMAIN`, as the auth routes name
1043
1090
  it) or an `Authorization: Bearer` token through the same
1044
- `authenticate`; the reply is `{ ok: true, d }` or
1091
+ `authenticate`, which is told which one it was (`request.credential`):
1092
+ `socketAuth` answers a cookie sent from a page outside `allowedOrigins`
1093
+ with `FORBIDDEN`, as it refuses such a socket; the reply is `{ ok: true, d }` or
1045
1094
  `{ ok: false, e: { code, message, data? } }` with the code's HTTP status.
1046
1095
  Works on Express 4 and 5, and on a bare Node server. Move it with
1047
1096
  `http: { path }`, turn it off with `http: false`, or mount
@@ -1211,6 +1260,19 @@ export function TaskDetail({ id }: { readonly id: string }) {
1211
1260
  }
1212
1261
  ```
1213
1262
 
1263
+ - A row that does not exist is refused the way a row the reader may not
1264
+ see is: `FORBIDDEN`, for a subscribe and for a method whose access names
1265
+ the row (`{ entry }`), since a policy that cannot find the row grants
1266
+ nothing (fail closed), and telling the two apart would tell a stranger
1267
+ which ids exist (a service-wide `Admin` grant, which needs no row to
1268
+ pass, gets `NOT_FOUND`). So a page cannot tell "deleted" from "no access" by the
1269
+ refusal: it learns of a delete while it holds the row, live: the `r`
1270
+ frame sets `useEntity`'s `isRemoved` (as above), and a collection's
1271
+ `removed` delta takes the item out of `items`. A page opened after the
1272
+ delete (a link, a reload, a reconnect after the row went) gets only
1273
+ `FORBIDDEN`; where the difference matters, say "not found or not shared
1274
+ with you", or ask a method of the parent row (a list of the project's
1275
+ tasks) that can answer without leaking.
1214
1276
  - A field the contract's `fields` map tiers (`notes` here) is optional in
1215
1277
  every row type a reader gets (`useEntity`, collection items, `"entity"`
1216
1278
  outputs, `EntityOf`, `ItemOf`), because a reader below its level receives
@@ -1243,7 +1305,13 @@ export function TaskDetail({ id }: { readonly id: string }) {
1243
1305
  items loaded, and `loadMore`/`loadItems`; `view` filters the members by a
1244
1306
  view of the contract, for the user the server's hello names; `load: "all"`
1245
1307
  keeps every page loaded. A `null` scope holds nothing; `enabled: false`
1246
- subscribes to nothing.
1308
+ subscribes to nothing. A scope the server refused shows its `error`
1309
+ (`FORBIDDEN`) and is loaded once more each time the user's access may
1310
+ have changed: on a connect, on new service grants (`qd:access`), and when
1311
+ another scope the client holds gets an `added` delta for the scope's
1312
+ anchor row (an invite adds the chat to the user's own list of chats, and
1313
+ the chat's messages open). A row-level grant with neither sends nothing
1314
+ the client could notice: remount the view, or call `refresh()`.
1247
1315
  - The cache follows the user: a hello naming another user removes everything
1248
1316
  quickdraw cached; new credentials for the same user refetch it; new grants
1249
1317
  (`qd:access`) refetch every query.
@@ -1260,7 +1328,26 @@ A create shows its row before the server answers with `cache.addItem(collection,
1260
1328
  scope, item)`: the item appears in the scope at once, in its place by the
1261
1329
  collection's `order` (give it the order's fields; without them it goes
1262
1330
  last), and `useCollection`'s `pending` names it while the call is in flight.
1263
- A refused call removes it. The reply's `id` (the created row's) and values
1331
+ A refused call removes it, unless it was added with `{ onRefused: "keep" }`
1332
+ (`cache.addItem(collection, scope, item, { onRefused: "keep" })`, or the
1333
+ same for `addEntity`): it then leaves `items` for `useCollection`'s
1334
+ `refused`, each `{ item, error, dismiss(), retry() }`, in the render that
1335
+ shows the mutation's error, until the app dismisses it or sends the same
1336
+ call again with `retry()` (through the mutation hook that sent it, so its
1337
+ `isPending` and callbacks follow; it shows the item `pending` again, and
1338
+ never rejects: a second refusal shows in `refused` again), so a chat shows
1339
+ a failed message with "retry" without a copy of its own. A call whose outcome is unknown is not refused: when the
1340
+ connection drops after the call was sent, or it times out, the server may
1341
+ have made the write (`isUnknownOutcome(error)` on `./client` says so). Its
1342
+ items stay, `pending`, and `useCollection`'s `checking` names them until
1343
+ the scope's next load (the reconnect's resume, or a load asked for at once
1344
+ while the socket is up): a load that holds an item's id ends it, its own
1345
+ copy shown, and one sent after the failure that answers without it refuses
1346
+ it (into `refused` with `onRefused: "keep"`). Only an id the client made,
1347
+ which the server keeps, can be found that way, and only with one is
1348
+ `retry()` safe after an unknown outcome: the second call fails `CONFLICT`
1349
+ instead of writing a second row. A refused item ends too once its scope
1350
+ holds its id. The reply's `id` (the created row's) and values
1264
1351
  replace the item's own, and once the scope's own copy arrives (its `added`
1265
1352
  delta, or a load) that copy shows in its place: never both, never a gap.
1266
1353
  `cache.addEntity(row)` adds a row to every collection of entity rows whose
@@ -1397,8 +1484,10 @@ The methods most services write by hand, as one-line opt-ins (design:
1397
1484
  section 12). Each kit's contract half comes from the package root and makes
1398
1485
  ordinary contract entries; its handlers come from `./server`. Lint's
1399
1486
  `prefer-kit` reports a method written by hand that a kit implements (`get`,
1400
- `list`, `create`, `getTask`, ...) in a service that uses no kit; one that
1401
- must stay hand-written says why in a `// quickdraw: hand-written because ...`
1487
+ `list`, `create`, `getTask`, ...) in a service that uses no kit, and one
1488
+ that duplicates what a kit spread beside it serves (a `getNote` beside
1489
+ `crud.handlers(note, { access: { get } })`); one that must stay
1490
+ hand-written says why in a `// quickdraw: hand-written because ...`
1402
1491
  comment above it.
1403
1492
 
1404
1493
  ### Read/write kit
@@ -1943,9 +2032,15 @@ before?, after }, ctx, db)` after each `adminCreate`, `adminUpdate` and
1943
2032
  transaction's tracked client; a throw undoes the write and fails the
1944
2033
  call): what an admin edit must set off, such as a game reloading its
1945
2034
  tunables, without wrapping the kit's handlers. The rows are the entity,
1946
- every field. Without it the kit opens no transaction. Each handler
1947
- `admin.handlers` returns resolves with its method's output type, so a
1948
- wrapper reads the row and returns it with no cast.
2035
+ every field. `onCommitted({ method, id, before?, after }, ctx)` runs once
2036
+ the write has committed (after `onWrite`'s transaction), in a detached
2037
+ unit of work of its own: the reply does not wait for it, its writes flush
2038
+ on their own, and a throw is logged, the write standing. Apply an edit to
2039
+ what runs in memory (a game's simulation) there, so an edit that rolled
2040
+ back is never applied. Without either hook the kit opens no transaction.
2041
+ Each handler `admin.handlers` returns resolves with its method's output
2042
+ type (`AdminOutputOf<C, M>`), so a wrapper reads the row and returns it
2043
+ with no cast.
1949
2044
  - `adminSubscribers({ id })` counts the sockets subscribed to a row per
1950
2045
  access level (`{ id, count, levels, complete }`; behind a Redis adapter
1951
2046
  the counts are this server's and `complete` is `false`), and
@@ -2111,8 +2206,8 @@ export function TaskRoom({
2111
2206
  leaves the feed and gets
2112
2207
  `qd:revoked { kind: "stream", reason: "access", s, stream, scope? }`, and
2113
2208
  `useStream` shows `FORBIDDEN` until the next connect.
2114
- - A service's `streams: { <name>: { seed, validate } }` computes a stream's
2115
- seed per subscriber, `seed: (scope, ctx) => items` (the current state,
2209
+ - A service's `streams: { <name>: { seed, validate } }` (type
2210
+ `StreamImplementation`) computes a stream's seed per subscriber, `seed: (scope, ctx) => items` (the current state,
2116
2211
  where the items that follow are deltas; on whichever node the subscriber
2117
2212
  is on, under its principal, in the tick it joins the feed; a contract
2118
2213
  `seed: n` and a seed function cannot both be declared), and
@@ -2133,7 +2228,8 @@ export function TaskRoom({
2133
2228
  limiter does not count channels. `{ room: { prefix: "world:" } }` takes
2134
2229
  a socket in any app room whose name starts with the prefix (a game of many
2135
2230
  worlds), and every room form gives the handler the room it matched as
2136
- `ctx.room`, so the payload need not repeat it. Every requirement is the
2231
+ `ctx.room` (typed `ChannelRoomOf<C, Name>`: `string` for a channel that
2232
+ requires a room, else `undefined`), so the payload need not repeat it. Every requirement is the
2137
2233
  sending socket's own: a room another socket of the user joined does not
2138
2234
  count, a reconnected socket must join again, and behind a cluster the
2139
2235
  check runs on the node the socket is connected to, with no round trip.
@@ -2158,8 +2254,10 @@ input, { enabled?, onJoined? })` (from `./client`) runs the joining call
2158
2254
  (`enterBoard` above) on every `qd:hello` and when its input changes by
2159
2255
  value, never on a re-render, and shows `status` (`idle` with no socket to
2160
2256
  join with, `joining`, `joined`, `error`), `isJoined`, `data` and `error`;
2161
- a refusal stands until the next hello, `RATE_LIMITED` is tried again after
2162
- its backoff, and it never leaves the room itself.
2257
+ a refusal stands until the next hello or `retry()` (which runs the call
2258
+ again at once on the current socket, and does nothing while there is
2259
+ none or `enabled` is false), `RATE_LIMITED` is tried again after its
2260
+ backoff, and it never leaves the room itself.
2163
2261
  `connection.onHello(listener)` is the same hook without React.
2164
2262
 
2165
2263
  Code that is not a handler (a game loop, a timer, a job) reaches rooms
@@ -2212,8 +2310,9 @@ export function hasAudience(projectId: string): boolean {
2212
2310
  the node that held the socket: its own `leave` (`reason: "leave"`), a
2213
2311
  removal (`"removed"`), or a disconnect, which leaves every app room it was
2214
2312
  in (`"disconnect"`; `qd:rotate` reconnects with a new socket). Each room
2215
- comes with `last`: true when no socket of that user is in the room any
2216
- more, on any node, which is what a game's `playerLeft` waits for. It runs
2313
+ (a `RoomLeft`) comes with `last`: true when no socket of that user is in
2314
+ the room any more, on any node, which is what a game's `playerLeft` waits
2315
+ for. It runs
2217
2316
  in a unit of work of its own (never the unit of the handler that left), a
2218
2317
  throw is logged, and `close()` waits for it. Behind a cluster `last` is
2219
2318
  decided by asking every node once the socket left, so two sockets of one
@@ -2248,8 +2347,8 @@ const allowedOrigins = [env.CLIENT_URL];
2248
2347
  const sessions = createMemorySessionStore();
2249
2348
 
2250
2349
  export const app: Express = express();
2251
- // behind a proxy, so the rate limits see the client's IP
2252
- app.set("trust proxy", 1);
2350
+ // behind a proxy, so the rate limits see the client's IP; with none, a client would pick its own
2351
+ app.set("trust proxy", env.TRUST_PROXY);
2253
2352
  // a web app on another origin also needs CORS with credentials on these routes
2254
2353
  app.use(
2255
2354
  createAuthRoutes({
@@ -2305,6 +2404,7 @@ nothing is cached:
2305
2404
  | `GET /{provider}/start?returnTo=<url>` | 302 to the provider |
2306
2405
  | `GET /{provider}/callback` | 302 to `{origin}{successPath}` with the session cookie, or to `{origin}{errorPath}?error=state`, `denied` or `failed` |
2307
2406
  | `POST /guest` | `createUser(body)`, then `{ userId, name? }` with the session cookie (and `token` with `guest({ token: true })`) |
2407
+ | `GET /providers` | `{ providers: [{ id, name, kind }] }`: the sign-ins served now (`routes.providers()` on the server) |
2308
2408
  | `GET /me` | `{ userId }`, or 401 |
2309
2409
  | `POST /logout` | 204: revokes the session, clears the cookie |
2310
2410
  | `POST /logout-all` | 204: revokes every session of the user; 401 without a live session |
@@ -2351,8 +2451,17 @@ nothing is cached:
2351
2451
  any page's handshake). A handshake without `Origin` is refused unless it
2352
2452
  is a browser's same-origin request (`Sec-Fetch-Site: same-origin`) or
2353
2453
  `allowMissingOrigin: true` is set for native clients that keep cookies.
2354
- Bearer tokens need no Origin, and HTTP calls are guarded by their JSON
2355
- content type instead.
2454
+ An HTTP call (`/qd/...`) that sends the cookie is checked the same way
2455
+ and answered `FORBIDDEN` (403) from an `Origin` outside `allowedOrigins`,
2456
+ besides needing its JSON content type. One without `Origin` is accepted:
2457
+ a browser sends `Origin` with every POST, so it comes from curl or a
2458
+ server rendering a page with the user's forwarded cookie (refused only
2459
+ when `Sec-Fetch-Site` names another site). Bearer tokens need no Origin
2460
+ on either transport. An app's own `authenticate` learns where an HTTP
2461
+ call's token came from in `request.credential` (`"cookie"` or
2462
+ `"bearer"`, type `HttpCredentialSource`), applies the same rule with
2463
+ `cookieOriginAllowed(request, allowedOrigins)`, and a
2464
+ `QuickdrawError("FORBIDDEN")` it throws on HTTP is answered as it is.
2356
2465
  - `socketAuth({ devCredentials })` signs a socket in by the user id its
2357
2466
  handshake names (`auth: { userId }`, no token), as the function answers
2358
2467
  (the principal, or `null` to refuse): for a game editor or load-test bots
@@ -2369,11 +2478,17 @@ nothing is cached:
2369
2478
  session's token, for clients that keep no cookies (a game engine, a page
2370
2479
  in a third-party iframe), at the cost of the token being readable by the
2371
2480
  page's scripts.
2372
- - `requireSession({ sessions, jwtSecret }, { loadPrincipal? })` guards the
2373
- app's own REST routes (below): the credential is read as `/me` reads it,
2374
- the JWT verified once and the session checked in the store; otherwise
2375
- 401 `{ error: "UNAUTHENTICATED", message }`. 4.1's `createRequireAuth`
2376
- stays for token-keyed sessions.
2481
+ - `requireSession({ sessions, jwtSecret }, { loadPrincipal?, allowedOrigins? })`
2482
+ guards the app's own REST routes (below): the credential is read as `/me`
2483
+ reads it, the JWT verified once and the session checked in the store;
2484
+ otherwise 401 `{ error: "UNAUTHENTICATED", message }`. The session cookie
2485
+ gets the `/qd` calls' Origin rule: from an `Origin` outside
2486
+ `allowedOrigins` (by default the list of the `createAuthRoutes` writing to
2487
+ the same `sessions`) it answers 403 `{ error: "FORBIDDEN", message }`, so
2488
+ another site's form cannot post to a route as the user; a request without
2489
+ `Origin` is accepted unless `Sec-Fetch-Site` names another site, and a
2490
+ bearer token needs none. 4.1's `createRequireAuth` stays for token-keyed
2491
+ sessions.
2377
2492
  - Rate limits: the sign-in routes share `createAuthLimiter({ max: 60 })` (60
2378
2493
  requests per 15 minutes per IP) and the session routes
2379
2494
  `createAuthStatusLimiter()` (120); pass `rateLimit: { signIn, session }`
@@ -2387,7 +2502,14 @@ nothing is cached:
2387
2502
  - The mock provider is mounted only while `isMockOAuthEnabled()`
2388
2503
  (`ENABLE_MOCK_OAUTH=true` and `NODE_ENV` other than `production`), and
2389
2504
  every request checks again. Set `mock({ internalUrl })` where the API
2390
- cannot reach itself at `publicUrl`.
2505
+ cannot reach itself at `publicUrl`. Routes made with nothing that can
2506
+ sign anyone in (only a mock that is off) warn when they are made.
2507
+ - `publicUrl` is where providers send the browser back, so a deployment
2508
+ must set it to the API's public URL. A loopback `publicUrl`
2509
+ (`http://localhost:4000`, the usual fallback for an unset `API_URL`)
2510
+ warns when the routes are made if every allowed origin is a page on
2511
+ another machine, and the first request that arrives for another host
2512
+ (`X-Forwarded-Host`, else `Host`) logs an error naming it, once.
2391
2513
  - One rule names the session cookie, written and read: the routes,
2392
2514
  `setSessionCookie`, `socketAuth` and the HTTP transport give a request
2393
2515
  the same name, and read first the name they would set on it. Without a
@@ -2418,10 +2540,13 @@ nothing is cached:
2418
2540
  session for an app's own sign-in flow (login codes, an embedded activity),
2419
2541
  and `liveSession` reads a token back; both work with `socketAuth`.
2420
2542
 
2421
- From the browser, `./client` speaks to these routes: `signInUrl(provider,
2422
- { apiUrl, returnTo })` is the start route's URL (to navigate to; `returnTo`
2423
- defaults to the current page's origin), and `signOut()` and
2424
- `signOutEverywhere()` post to `logout` and `logout-all`. Each POST sends the
2543
+ From the browser, `./client` speaks to these routes: `authProviders({
2544
+ apiUrl })` lists the sign-ins the API serves (`GET /providers`: a provider
2545
+ `google.optional` built nothing for is not in it, nor the mock where it is
2546
+ off), so a login page shows only those, whatever the web app was built
2547
+ with; `signInUrl(provider, { apiUrl, returnTo })` is the start route's URL
2548
+ (to navigate to; `returnTo` defaults to the current page's origin), and
2549
+ `signOut()` and `signOutEverywhere()` post to `logout` and `logout-all`. Each POST sends the
2425
2550
  session cookie (`credentials: "include"`, so the API's CORS must allow the
2426
2551
  web app's origin with credentials) with `Content-Type: application/json`,
2427
2552
  and the token `setAuthToken` stored, if any, as a bearer token; the stored
@@ -2434,8 +2559,23 @@ signing out (with a token in `auth`, clearing it does that):
2434
2559
 
2435
2560
  ```tsx
2436
2561
  export function SignIn() {
2437
- // the kit's GET /auth/google/start: back to this page's origin with the session cookie
2438
- return <a href={signInUrl("google", { apiUrl: API_URL })}>Sign in with Google</a>;
2562
+ // GET /auth/providers: only the sign-ins this API serves (no Google button without its keys)
2563
+ const { data: providers = [] } = useQuery({
2564
+ queryKey: ["auth", "providers"],
2565
+ queryFn: () => authProviders({ apiUrl: API_URL }),
2566
+ });
2567
+ return (
2568
+ <nav>
2569
+ {providers
2570
+ .filter((provider) => provider.kind !== "guest")
2571
+ .map((provider) => (
2572
+ // the kit's GET /auth/{id}/start: back to this page's origin with the session cookie
2573
+ <a key={provider.id} href={signInUrl(provider.id, { apiUrl: API_URL })}>
2574
+ {`Sign in with ${provider.name}`}
2575
+ </a>
2576
+ ))}
2577
+ </nav>
2578
+ );
2439
2579
  }
2440
2580
 
2441
2581
  export function SignOut() {
@@ -2576,8 +2716,8 @@ it("sends a rename to the other members' boards", async () => {
2576
2716
  real socket (`{ call, socket, hello, close }`); both are keyed by service
2577
2717
  name. `app.frames(match?)` lists every frame the server sent, with its
2578
2718
  socket and user; `frames.waitFor(match)` waits for one. A query of one
2579
- event takes `where`, a predicate over its frames typed by the event
2580
- (`{ event: "qd:presence", where: ({ data }) => data.joined === id }`), and
2719
+ event (`EventQuery`) takes `where`, a predicate over its frames typed by
2720
+ the event (`{ event: "qd:presence", where: ({ data }) => data.joined === id }`), and
2581
2721
  `streamFrames(contract, stream, where?, scope?)` and
2582
2722
  `eventFrames(contract, event, where?)` match one stream's items or one
2583
2723
  event's payloads, typed by the contract, to spread beside `socketId` or
@@ -2615,8 +2755,9 @@ it("lets the owner rename, members read, and nobody else in", async () => {
2615
2755
  `"deny"` (the default for everyone `allow` does not name) means
2616
2756
  `UNAUTHENTICATED` without a principal and `FORBIDDEN` with one. Mutations run
2617
2757
  for real, once per allowed principal: give inputs that can run again, or
2618
- `input` as a function of the cell (`({ name, principal }) => input`, sync or
2619
- async), called before each cell's call, that makes a row of its own (a task
2758
+ `input` as a function of the cell (a `MatrixInputFactory` of the
2759
+ `MatrixCell` `{ name, principal }`, sync or async), called before each
2760
+ cell's call, that makes a row of its own (a task
2620
2761
  to delete, an unused name), so no cell depends on the order of the
2621
2762
  principals.
2622
2763
 
@@ -2689,16 +2830,17 @@ one format and names the method call it happened in:
2689
2830
  [quickdraw:n-plus-one] taskService.board: task.findUnique by id ran 10 times in one call, once per item (N+1); ...
2690
2831
  ```
2691
2832
 
2692
- | Kind | Raised when |
2693
- | -------------------- | ---------------------------------------------------------------------------------------------------- |
2694
- | `n-plus-one` | a call ran 10 statements of one shape (model, operation, `where` keys), outside a `$transaction([])` |
2695
- | `unbounded-read` | a call ran `findMany` with neither `take` nor ids to read (`id`, `{ in }` or `{ equals }`) |
2696
- | `oversized-response` | a reply was larger than `maxResponseBytes` (default 1 MiB) |
2697
- | `nested-write` | a write's `data` wrote a related row, which is not tracked |
2698
- | `ambient-write` | a tracked write ran outside any unit of work |
2699
- | `batch-read` | a write in an array-form `$transaction` read its rows outside the batch |
2700
- | `batch-create-many` | a `createMany` in an array-form `$transaction` could not report its rows |
2701
- | `repeated-call` | one connection repeated a call or was refused `RATE_LIMITED` again and again (below) |
2833
+ | Kind | Raised when |
2834
+ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2835
+ | `n-plus-one` | a call ran 10 statements of one shape (model, operation, `where` keys), outside a `$transaction([])` |
2836
+ | `unbounded-read` | a call ran `findMany` with neither `take` nor ids to read (`id`, `{ in }` or `{ equals }`) |
2837
+ | `oversized-response` | a reply was larger than `maxResponseBytes` (default 1 MiB) |
2838
+ | `nested-write` | a write's `data` wrote a related row, which is not tracked |
2839
+ | `ambient-write` | a tracked write ran outside any unit of work |
2840
+ | `batch-read` | a write in an array-form `$transaction` read its rows outside the batch |
2841
+ | `batch-create-many` | a `createMany` in an array-form `$transaction` could not report its rows |
2842
+ | `repeated-call` | one connection repeated a call or was refused `RATE_LIMITED` again and again (below) |
2843
+ | `tiered-field-in-output` | a method's own output schema declares a field the contract tiers, which only projection outputs strip; raised when the server is made (for an output without JSON Schema, when a reply carries it) |
2702
2844
 
2703
2845
  Updates and deletes by id inside an interactive transaction are not counted
2704
2846
  toward `n-plus-one`: that is how per-row writes are written (see tracked
@@ -2716,7 +2858,9 @@ fails: the call it happened in fails with `INTERNAL` and the error as its
2716
2858
  once the reply was recorded (over a socket or HTTP the reply was already
2717
2859
  sent, so that error is logged, not thrown). Strictness belongs to the app:
2718
2860
  warnings outside its calls (an ambient write while seeding, another app's
2719
- calls) are logged as usual, and `app.close()` ends it.
2861
+ calls) are logged as usual, and `app.close()` ends it. The one raised when
2862
+ the app is made, `tiered-field-in-output`, is thrown from `createTestApp`
2863
+ itself, so a strict app over such a method does not start.
2720
2864
 
2721
2865
  A `repeated-call` warning names a client caught in a loop (a mutation fired
2722
2866
  from an effect that its own result runs again, a refetch that triggers
@@ -2817,6 +2961,13 @@ it("lets a signed-in user through the gate", () => {
2817
2961
 
2818
2962
  In Storybook, a decorator renders every story inside `qd.$Provider`, and a
2819
2963
  story's `beforeEach` sets its session (`qd.$session(...)`) beside its data.
2964
+ A docs page renders its stories side by side, where one mock session would
2965
+ show the last story's in all of them: give each its own with the
2966
+ provider's `session` prop, `<qd.$Provider session={{ userId: null }}>`
2967
+ (a `MockSession`, the type `$session(...)` takes too), laid over the
2968
+ mock's session field by field for that subtree alone (the
2969
+ real `useQuickdraw()` and `usePresence`, and the mock's views and admin
2970
+ grants, read it there).
2820
2971
 
2821
2972
  ### Test databases
2822
2973
 
@@ -2916,7 +3067,14 @@ topic (`watchAccess`) and the field levels; each method's access form, in
2916
3067
  words, and its `rowless`; who may open a collection's scope; a channel's
2917
3068
  access; a stream's computed seed and when its items are checked. A
2918
3069
  contract the services module has no service for says so, and a service
2919
- without a contract is an error. Pass the same flag to `--check`.
3070
+ without a contract is an error. Pass the same flag to `--check`. The
3071
+ services module is imported with everything it imports, as the server
3072
+ loads it: the workspace packages it uses (`@project/db`, `@project/shared`)
3073
+ load from their built output when their `package.json` points there, so
3074
+ build them first (`bun run build`, or `turbo run build --filter` for those
3075
+ packages) in a script that runs the docs on a fresh checkout; the error
3076
+ says so when a built file is missing. The contracts module, which imports
3077
+ only the shared package's own sources, needs no build.
2920
3078
 
2921
3079
  The module may export each contract, or a map of them as given to
2922
3080
  `createQuickdrawClient`. A TypeScript module loads through Node's type
@@ -1,5 +1,5 @@
1
- import { e as StandardSchemaWithJSON, d as StandardSchemaV1, I as InferInput, c as InferOutput, U as RowSchema, Q as QueryDef, P as ProjectionList, p as MutationDef, H as AnyContract, ao as MethodName } from './infer-Zs7dAWlI.js';
2
- import { d as ListSort, c as ListQuery, b as ListPage, 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-C1U6_voe.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-Dw21kmLw.js';
2
+ import { d as ListSort, c as ListQuery, b as ListPage, 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-Dufu8r7a.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 { y as SortDirection, A as AccessLevel } from './infer-Zs7dAWlI.js';
1
+ import { z as SortDirection, A as AccessLevel } from './infer-Dw21kmLw.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,24 @@
1
- import { IncomingHttpHeaders, IncomingMessage } from 'node:http';
1
+ import { IncomingMessage, IncomingHttpHeaders } from 'node:http';
2
2
  import { Socket } from 'socket.io';
3
- import { A as AccessLevel } from './infer-Zs7dAWlI.js';
4
- import { P as Principal, M as MaybePromise } from './types-BBU3rSbi.js';
3
+ import { A as AccessLevel } from './infer-Dw21kmLw.js';
4
+ import { P as Principal, M as MaybePromise } from './types-Bwg3kIEc.js';
5
+
6
+ /**
7
+ * A request as Express leaves it: maybe with `body` from `express.json()`,
8
+ * `cookies` from `cookie-parser`, and Express's own `secure`.
9
+ */
10
+ type HttpRequest = IncomingMessage & {
11
+ readonly body?: unknown;
12
+ readonly cookies?: unknown;
13
+ readonly secure?: unknown;
14
+ };
15
+ /**
16
+ * Where an HTTP call's token came from. `"cookie"`: the session cookie, which
17
+ * a browser sends with a request any page makes, so whoever accepts it checks
18
+ * the request's `Origin` (`socketAuth` does). `"bearer"`: an
19
+ * `Authorization: Bearer` header, which a page sends only on purpose.
20
+ */
21
+ type HttpCredentialSource = "cookie" | "bearer";
5
22
 
6
23
  /** A principal's service-wide grants by service name: `{ taskService: "Admin" }`. */
7
24
  type ServiceGrants = Readonly<Record<string, AccessLevel>>;
@@ -24,6 +41,14 @@ interface SocketAuthenticateRequest extends AuthenticateRequestBase {
24
41
  interface HttpAuthenticateRequest extends AuthenticateRequestBase {
25
42
  readonly transport: "http";
26
43
  readonly req: IncomingMessage;
44
+ /**
45
+ * Where `auth.token` came from (finding F7.1): `"cookie"`, the session
46
+ * cookie, which a browser sends with a request any page makes, so an
47
+ * `authenticate` that accepts it checks the request's `Origin` as
48
+ * `socketAuth` does; `"bearer"`, an `Authorization: Bearer` header, which
49
+ * a page sends only on purpose. Absent without a token.
50
+ */
51
+ readonly credential?: HttpCredentialSource;
27
52
  }
28
53
  /** What `authenticate` is asked about: a socket connecting, or an HTTP call. */
29
54
  type AuthenticateRequest = SocketAuthenticateRequest | HttpAuthenticateRequest;
@@ -44,6 +69,10 @@ interface ServerAuth<P extends Principal = Principal> {
44
69
  * caller in anonymously, so only `"public"` methods pass. Throw a
45
70
  * `QuickdrawError("UNAUTHENTICATED", ...)` for a refusal that is the
46
71
  * caller's doing (it logs at debug; anything else thrown logs at error).
72
+ * On an HTTP call a thrown `QuickdrawError("FORBIDDEN", ...)` is answered
73
+ * as it is, with 403 (logged at debug): a credential the request may not
74
+ * use from where it came, as `socketAuth` answers a session cookie sent
75
+ * from a page outside `allowedOrigins`.
47
76
  */
48
77
  readonly authenticate?: (request: AuthenticateRequest) => MaybePromise<AuthenticateResult<P>>;
49
78
  /**
@@ -77,4 +106,4 @@ interface ServiceAccessSource {
77
106
  */
78
107
  declare function recordSocketSession(socket: object, sessionId: string): void;
79
108
 
80
- export { type AuthenticateResult as A, type HttpAuthenticateRequest as H, type ServerAuth as S, type ServiceGrants as a, type AuthenticateRequest as b, type ServiceAccessSource as c, type SocketAuthenticateRequest as d, recordSocketSession as r };
109
+ export { type AuthenticateResult as A, type HttpRequest as H, type ServerAuth as S, type ServiceGrants as a, type AuthenticateRequest as b, type HttpAuthenticateRequest as c, type HttpCredentialSource as d, type ServiceAccessSource as e, type SocketAuthenticateRequest as f, recordSocketSession as r };
@@ -1,10 +1,10 @@
1
- import { ad as Version, a5 as Revision } from './envelope-B6TUGKsk.js';
1
+ import { ad as Version, a5 as Revision } from './envelope-nf5eGB-a.js';
2
2
  import { L as Logger } from './logger-BY28acW-.js';
3
- import { A as AccessLevel, H as AnyContract, x as CollectionWhere, O as OrderBy, k as MethodKind, d as StandardSchemaV1, af as EventName, ag as EventPayloadOf, a9 as ChannelName, aa as ChannelPayloadOf, ay as StreamName, al as IsScopedStream, ax as StreamItemOf, ad as ContractMap, ao as MethodName, ak as InputOf, aq as OutputOf } from './infer-Zs7dAWlI.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-BBU3rSbi.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';
5
5
 
6
6
  /** What a development warning is about. */
7
- type DevWarningKind = "n-plus-one" | "unbounded-read" | "oversized-response" | "nested-write" | "ambient-write" | "batch-read" | "batch-create-many" | "repeated-call";
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";
8
8
  /** One development warning. */
9
9
  interface DevWarning {
10
10
  readonly kind: DevWarningKind;
@@ -340,6 +340,20 @@ type AnyHandler = (args: {
340
340
  readonly ctx: AnyContext;
341
341
  readonly db: unknown;
342
342
  }) => unknown;
343
+ /**
344
+ * A method's own output schema, compiled from its JSON Schema when the
345
+ * service is defined (`pipeline/schemaOutput.ts`).
346
+ */
347
+ interface SchemaOutput {
348
+ /** The value reduced to what the schema declares; the value itself when nothing is dropped. */
349
+ pick(value: unknown): unknown;
350
+ /**
351
+ * Each key the schema declares, at any depth, with the first path that
352
+ * declares it (`email`, `user.email`, `[].email`; `{}` stands for any key
353
+ * of a record): what the `tiered-field-in-output` warning reads.
354
+ */
355
+ keyPaths(): ReadonlyMap<string, string>;
356
+ }
343
357
  /** One method of a defined service, checked and ready to dispatch. */
344
358
  interface ServiceMethod {
345
359
  readonly name: string;
@@ -358,6 +372,14 @@ interface ServiceMethod {
358
372
  * its field tiers are stripped per caller.
359
373
  */
360
374
  readonly projection: ProjectedOutput | undefined;
375
+ /**
376
+ * A schema output, compiled from its JSON Schema when the service was
377
+ * defined (`pipeline/schemaOutput.ts`): the handler's result is reduced to
378
+ * what the schema declares before the output check, on every transport.
379
+ * `undefined` for a projection output, and for a schema without JSON
380
+ * Schema (Zod 3), which is sent as the handler returns it.
381
+ */
382
+ readonly schemaOutput: SchemaOutput | undefined;
361
383
  readonly access: AccessForm;
362
384
  readonly handler: AnyHandler;
363
385
  readonly share: ShareMode | undefined;
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-V3KNCTXJ.js";
4
4
 
5
5
  // src/version.ts
6
- var QUICKDRAW_VERSION = "5.0.0-rc.4";
6
+ var QUICKDRAW_VERSION = "5.0.0-rc.6";
7
7
 
8
8
  // src/contract/names.ts
9
9
  function entityRoom(service, id, level) {
@@ -127,4 +127,4 @@ export {
127
127
  MAX_SUBSCRIBE_IDS,
128
128
  MAX_SCOPE_LENGTH
129
129
  };
130
- //# sourceMappingURL=chunk-Y5YAG75H.js.map
130
+ //# sourceMappingURL=chunk-4HOXH4V3.js.map