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

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 (72) hide show
  1. package/README.md +110 -25
  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-C2sBNmyT.d.ts} +4 -4
  6. package/dist/{chunk-RDCIZR4I.js → chunk-HKXLRZAO.js} +3 -3
  7. package/dist/{chunk-TDK6S6V7.js → chunk-NKVWV3FL.js} +1 -1
  8. package/dist/chunk-NKVWV3FL.js.map +1 -0
  9. package/dist/{chunk-IJTZQX6Z.js → chunk-NTKYU2M5.js} +160 -108
  10. package/dist/chunk-NTKYU2M5.js.map +1 -0
  11. package/dist/{chunk-QMCIKLWC.js → chunk-OM6LDFJL.js} +1 -1
  12. package/dist/{chunk-QMCIKLWC.js.map → chunk-OM6LDFJL.js.map} +1 -1
  13. package/dist/{chunk-7MPXURIO.js → chunk-RSYYHO3Q.js} +62 -17
  14. package/dist/chunk-RSYYHO3Q.js.map +1 -0
  15. package/dist/{chunk-Y5YAG75H.js → chunk-RVGJG5P2.js} +2 -2
  16. package/dist/{chunk-Y5YAG75H.js.map → chunk-RVGJG5P2.js.map} +1 -1
  17. package/dist/{chunk-DMUGWMNJ.js → chunk-TSQGR7GH.js} +1 -1
  18. package/dist/{chunk-DMUGWMNJ.js.map → chunk-TSQGR7GH.js.map} +1 -1
  19. package/dist/{chunk-7EWHLDGN.js → chunk-UZVC74SC.js} +138 -31
  20. package/dist/chunk-UZVC74SC.js.map +1 -0
  21. package/dist/{chunk-B4IOFSM6.js → chunk-X2YCUIEK.js} +16 -5
  22. package/dist/chunk-X2YCUIEK.js.map +1 -0
  23. package/dist/{chunk-OZ7DJUJY.js → chunk-Y6IH3B4W.js} +1 -1
  24. package/dist/chunk-Y6IH3B4W.js.map +1 -0
  25. package/dist/{chunk-4PK6O7VL.js → chunk-ZHPUY3QZ.js} +97 -35
  26. package/dist/chunk-ZHPUY3QZ.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 +14 -7
  30. package/dist/client/index.js +78 -52
  31. package/dist/client/index.js.map +1 -1
  32. package/dist/{clientTypes-CKqjWKdP.d.ts → clientTypes-BPdAdy5t.d.ts} +87 -9
  33. package/dist/{connection-GcBHwKgF.d.ts → connection-Dld74yBn.d.ts} +9 -2
  34. package/dist/{createServer-6ZLgE-Vs.d.ts → createServer-BAHtmsBo.d.ts} +6 -6
  35. package/dist/{createTestApp-BTFzRxV8.d.ts → createTestApp-BhDUhx8B.d.ts} +6 -6
  36. package/dist/{envelope-B6TUGKsk.d.ts → envelope-VWu-kXQf.d.ts} +6 -1
  37. package/dist/{http-GTJIh3Hm.d.ts → http-DL5mLeFE.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 +140 -8
  45. package/dist/prisma/index.js.map +1 -1
  46. package/dist/server/auth/index.d.ts +17 -18
  47. package/dist/server/auth/index.js +26 -4
  48. package/dist/server/auth/index.js.map +1 -1
  49. package/dist/server/index.d.ts +45 -16
  50. package/dist/server/index.js +74 -19
  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-IJTZQX6Z.js.map +0 -1
  70. package/dist/chunk-OZ7DJUJY.js.map +0 -1
  71. package/dist/chunk-TDK6S6V7.js.map +0 -1
  72. /package/dist/{chunk-RDCIZR4I.js.map → chunk-HKXLRZAO.js.map} +0 -0
package/README.md CHANGED
@@ -753,6 +753,18 @@ tracked client as `db`; the server finds the rest on it.
753
753
  recorded with its row ids, merged per row, and handed to the flush sinks
754
754
  once the response has been sent, with one revision per flush. A handler
755
755
  may return `db.task.update(...)` without awaiting it.
756
+ - A write that changed nothing is not recorded, so it sends no frame, no
757
+ collection delta, no topic change and no `refreshEntry`: an
758
+ `updateMany`, `updateManyAndReturn` or `deleteMany` that matched no row;
759
+ a `data` (or an upsert's `update`) with nothing to write, `{}`; and an
760
+ update that sets only columns the framework reads before writing (scope,
761
+ `where`, membership, owner and access columns) to the values they held.
762
+ An upsert with `update: {}` ("make sure this row exists") reads the row
763
+ first in its place: one statement when the row is there, and the upsert
764
+ after it when it is not. A write that sets any other column is recorded
765
+ whatever it wrote, since its old value was never read; the `@updatedAt`
766
+ column Prisma moves on an unchanged write is not signalled, as no write's
767
+ is on its own.
756
768
  - Writes inside `db.$transaction` join the unit only when it commits; a
757
769
  rollback drops them. Prefer the interactive form
758
770
  (`db.$transaction(async (tx) => ...)`): an array-form
@@ -882,7 +894,19 @@ export const taskService = qd.defineService(task, {
882
894
  rows (a `Date` is fine where the wire has a string, extra columns are
883
895
  dropped); with `map`, it returns what `map` takes.
884
896
  - 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.
897
+ are stripped from that caller's copy, after any shared run. Only a
898
+ projection output is stripped (`"entity"`, a named projection, and
899
+ `nullable(...)` or `listOf(...)` of one): a method whose output is a
900
+ schema of its own sends what its handler returns to every caller its
901
+ access admits, so a tiered key in that schema (`email` in a hand-written
902
+ `{ id, name, email }`) reaches callers below its level. When a server is
903
+ made, each such method raises the development warning
904
+ `[quickdraw:tiered-field-in-output]` (thrown by
905
+ `createTestApp({ strictWarnings: true })`, so the test app fails to
906
+ start): answer `"entity"` or a projection, or leave the key out. Not
907
+ warned: a method whose access admits no caller below the field's level
908
+ (`{ service: "Admin" }` with the Admin bypass on, `{ entry: L }` with `L`
909
+ at the field's level or above), and an output without JSON Schema (Zod 3).
886
910
  - `affects` names rows of other services a write changes too
887
911
  (`{ service, id: column }`, or `{ service, id: (row) => ids, columns }`);
888
912
  they are sent again after the flush, one hop.
@@ -1009,7 +1033,13 @@ export const taskService = qd.defineService(task, {
1009
1033
  authorized like a subscribe to that scope, or `service`, which changes
1010
1034
  whenever any row of the service does, or a row of a model it lists in
1011
1035
  `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
1036
+ `watch: "service"` to be invalidated by it. A query over some of those
1037
+ models names them, `watch: { service: ["gameScore"] }` (the service's
1038
+ `model` and models in its `writes`; `defineService` refuses others), and
1039
+ is invalidated only after a flush that wrote one of them: high scores are
1040
+ not read again when a chat membership the same service writes changes.
1041
+ It is the same topic: its `qd:changed` frame names the models that
1042
+ changed it (`models`). The service topic is closed
1013
1043
  (`FORBIDDEN`) unless the service declares `watchAccess` (`"public"`,
1014
1044
  `"authenticated"` or `{ service: level }`). A watcher that loses access
1015
1045
  leaves the topic after one last `qd:changed`.
@@ -1041,7 +1071,9 @@ over three transports (design: sections 3, 8 and 10):
1041
1071
  principal comes from the session cookie (`__Host-session` over HTTPS,
1042
1072
  `session` over plain HTTP or with `COOKIE_DOMAIN`, as the auth routes name
1043
1073
  it) or an `Authorization: Bearer` token through the same
1044
- `authenticate`; the reply is `{ ok: true, d }` or
1074
+ `authenticate`, which is told which one it was (`request.credential`):
1075
+ `socketAuth` answers a cookie sent from a page outside `allowedOrigins`
1076
+ with `FORBIDDEN`, as it refuses such a socket; the reply is `{ ok: true, d }` or
1045
1077
  `{ ok: false, e: { code, message, data? } }` with the code's HTTP status.
1046
1078
  Works on Express 4 and 5, and on a bare Node server. Move it with
1047
1079
  `http: { path }`, turn it off with `http: false`, or mount
@@ -1211,6 +1243,19 @@ export function TaskDetail({ id }: { readonly id: string }) {
1211
1243
  }
1212
1244
  ```
1213
1245
 
1246
+ - A row that does not exist is refused the way a row the reader may not
1247
+ see is: `FORBIDDEN`, for a subscribe and for a method whose access names
1248
+ the row (`{ entry }`), since a policy that cannot find the row grants
1249
+ nothing (fail closed), and telling the two apart would tell a stranger
1250
+ which ids exist (a service-wide `Admin` grant, which needs no row to
1251
+ pass, gets `NOT_FOUND`). So a page cannot tell "deleted" from "no access" by the
1252
+ refusal: it learns of a delete while it holds the row, live: the `r`
1253
+ frame sets `useEntity`'s `isRemoved` (as above), and a collection's
1254
+ `removed` delta takes the item out of `items`. A page opened after the
1255
+ delete (a link, a reload, a reconnect after the row went) gets only
1256
+ `FORBIDDEN`; where the difference matters, say "not found or not shared
1257
+ with you", or ask a method of the parent row (a list of the project's
1258
+ tasks) that can answer without leaking.
1214
1259
  - A field the contract's `fields` map tiers (`notes` here) is optional in
1215
1260
  every row type a reader gets (`useEntity`, collection items, `"entity"`
1216
1261
  outputs, `EntityOf`, `ItemOf`), because a reader below its level receives
@@ -1260,7 +1305,14 @@ A create shows its row before the server answers with `cache.addItem(collection,
1260
1305
  scope, item)`: the item appears in the scope at once, in its place by the
1261
1306
  collection's `order` (give it the order's fields; without them it goes
1262
1307
  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
1308
+ A refused call removes it, unless it was added with `{ onRefused: "keep" }`
1309
+ (`cache.addItem(collection, scope, item, { onRefused: "keep" })`, or the
1310
+ same for `addEntity`): it then leaves `items` for `useCollection`'s
1311
+ `refused`, each `{ item, error, dismiss(), retry() }`, until the app
1312
+ dismisses it or sends the same call again with `retry()` (which shows it
1313
+ `pending` again, and never rejects: a second refusal shows in `refused`
1314
+ again), so a chat shows a failed message with "retry" without a copy of
1315
+ its own. The reply's `id` (the created row's) and values
1264
1316
  replace the item's own, and once the scope's own copy arrives (its `added`
1265
1317
  delta, or a load) that copy shows in its place: never both, never a gap.
1266
1318
  `cache.addEntity(row)` adds a row to every collection of entity rows whose
@@ -1397,8 +1449,10 @@ The methods most services write by hand, as one-line opt-ins (design:
1397
1449
  section 12). Each kit's contract half comes from the package root and makes
1398
1450
  ordinary contract entries; its handlers come from `./server`. Lint's
1399
1451
  `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 ...`
1452
+ `list`, `create`, `getTask`, ...) in a service that uses no kit, and one
1453
+ that duplicates what a kit spread beside it serves (a `getNote` beside
1454
+ `crud.handlers(note, { access: { get } })`); one that must stay
1455
+ hand-written says why in a `// quickdraw: hand-written because ...`
1402
1456
  comment above it.
1403
1457
 
1404
1458
  ### Read/write kit
@@ -1943,9 +1997,14 @@ before?, after }, ctx, db)` after each `adminCreate`, `adminUpdate` and
1943
1997
  transaction's tracked client; a throw undoes the write and fails the
1944
1998
  call): what an admin edit must set off, such as a game reloading its
1945
1999
  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.
2000
+ every field. `onCommitted({ method, id, before?, after }, ctx)` runs once
2001
+ the write has committed (after `onWrite`'s transaction), in a detached
2002
+ unit of work of its own: the reply does not wait for it, its writes flush
2003
+ on their own, and a throw is logged, the write standing. Apply an edit to
2004
+ what runs in memory (a game's simulation) there, so an edit that rolled
2005
+ back is never applied. Without either hook the kit opens no transaction.
2006
+ Each handler `admin.handlers` returns resolves with its method's output
2007
+ type, so a wrapper reads the row and returns it with no cast.
1949
2008
  - `adminSubscribers({ id })` counts the sockets subscribed to a row per
1950
2009
  access level (`{ id, count, levels, complete }`; behind a Redis adapter
1951
2010
  the counts are this server's and `complete` is `false`), and
@@ -2158,8 +2217,10 @@ input, { enabled?, onJoined? })` (from `./client`) runs the joining call
2158
2217
  (`enterBoard` above) on every `qd:hello` and when its input changes by
2159
2218
  value, never on a re-render, and shows `status` (`idle` with no socket to
2160
2219
  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.
2220
+ a refusal stands until the next hello or `retry()` (which runs the call
2221
+ again at once on the current socket, and does nothing while there is
2222
+ none or `enabled` is false), `RATE_LIMITED` is tried again after its
2223
+ backoff, and it never leaves the room itself.
2163
2224
  `connection.onHello(listener)` is the same hook without React.
2164
2225
 
2165
2226
  Code that is not a handler (a game loop, a timer, a job) reaches rooms
@@ -2351,8 +2412,16 @@ nothing is cached:
2351
2412
  any page's handshake). A handshake without `Origin` is refused unless it
2352
2413
  is a browser's same-origin request (`Sec-Fetch-Site: same-origin`) or
2353
2414
  `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.
2415
+ An HTTP call (`/qd/...`) that sends the cookie is checked the same way
2416
+ and answered `FORBIDDEN` (403) from an `Origin` outside `allowedOrigins`,
2417
+ besides needing its JSON content type. One without `Origin` is accepted:
2418
+ a browser sends `Origin` with every POST, so it comes from curl or a
2419
+ server rendering a page with the user's forwarded cookie (refused only
2420
+ when `Sec-Fetch-Site` names another site). Bearer tokens need no Origin
2421
+ on either transport. An app's own `authenticate` learns where an HTTP
2422
+ call's token came from in `request.credential` (`"cookie"` or
2423
+ `"bearer"`), and a `QuickdrawError("FORBIDDEN")` it throws on HTTP is
2424
+ answered as it is.
2356
2425
  - `socketAuth({ devCredentials })` signs a socket in by the user id its
2357
2426
  handshake names (`auth: { userId }`, no token), as the function answers
2358
2427
  (the principal, or `null` to refuse): for a game editor or load-test bots
@@ -2689,16 +2758,17 @@ one format and names the method call it happened in:
2689
2758
  [quickdraw:n-plus-one] taskService.board: task.findUnique by id ran 10 times in one call, once per item (N+1); ...
2690
2759
  ```
2691
2760
 
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) |
2761
+ | Kind | Raised when |
2762
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
2763
+ | `n-plus-one` | a call ran 10 statements of one shape (model, operation, `where` keys), outside a `$transaction([])` |
2764
+ | `unbounded-read` | a call ran `findMany` with neither `take` nor ids to read (`id`, `{ in }` or `{ equals }`) |
2765
+ | `oversized-response` | a reply was larger than `maxResponseBytes` (default 1 MiB) |
2766
+ | `nested-write` | a write's `data` wrote a related row, which is not tracked |
2767
+ | `ambient-write` | a tracked write ran outside any unit of work |
2768
+ | `batch-read` | a write in an array-form `$transaction` read its rows outside the batch |
2769
+ | `batch-create-many` | a `createMany` in an array-form `$transaction` could not report its rows |
2770
+ | `repeated-call` | one connection repeated a call or was refused `RATE_LIMITED` again and again (below) |
2771
+ | `tiered-field-in-output` | a method's own output schema names a field the contract tiers, which only projection outputs strip; raised when the server is made |
2702
2772
 
2703
2773
  Updates and deletes by id inside an interactive transaction are not counted
2704
2774
  toward `n-plus-one`: that is how per-row writes are written (see tracked
@@ -2716,7 +2786,9 @@ fails: the call it happened in fails with `INTERNAL` and the error as its
2716
2786
  once the reply was recorded (over a socket or HTTP the reply was already
2717
2787
  sent, so that error is logged, not thrown). Strictness belongs to the app:
2718
2788
  warnings outside its calls (an ambient write while seeding, another app's
2719
- calls) are logged as usual, and `app.close()` ends it.
2789
+ calls) are logged as usual, and `app.close()` ends it. The one raised when
2790
+ the app is made, `tiered-field-in-output`, is thrown from `createTestApp`
2791
+ itself, so a strict app over such a method does not start.
2720
2792
 
2721
2793
  A `repeated-call` warning names a client caught in a loop (a mutation fired
2722
2794
  from an effect that its own result runs again, a refetch that triggers
@@ -2817,6 +2889,12 @@ it("lets a signed-in user through the gate", () => {
2817
2889
 
2818
2890
  In Storybook, a decorator renders every story inside `qd.$Provider`, and a
2819
2891
  story's `beforeEach` sets its session (`qd.$session(...)`) beside its data.
2892
+ A docs page renders its stories side by side, where one mock session would
2893
+ show the last story's in all of them: give each its own with the
2894
+ provider's `session` prop, `<qd.$Provider session={{ userId: null }}>`,
2895
+ laid over the mock's session field by field for that subtree alone (the
2896
+ real `useQuickdraw()` and `usePresence`, and the mock's views and admin
2897
+ grants, read it there).
2820
2898
 
2821
2899
  ### Test databases
2822
2900
 
@@ -2916,7 +2994,14 @@ topic (`watchAccess`) and the field levels; each method's access form, in
2916
2994
  words, and its `rowless`; who may open a collection's scope; a channel's
2917
2995
  access; a stream's computed seed and when its items are checked. A
2918
2996
  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`.
2997
+ without a contract is an error. Pass the same flag to `--check`. The
2998
+ services module is imported with everything it imports, as the server
2999
+ loads it: the workspace packages it uses (`@project/db`, `@project/shared`)
3000
+ load from their built output when their `package.json` points there, so
3001
+ build them first (`bun run build`, or `turbo run build --filter` for those
3002
+ packages) in a script that runs the docs on a fresh checkout; the error
3003
+ says so when a built file is missing. The contracts module, which imports
3004
+ only the shared package's own sources, needs no build.
2920
3005
 
2921
3006
  The module may export each contract, or a map of them as given to
2922
3007
  `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-VWu-kXQf.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;
@@ -22,11 +22,11 @@ import {
22
22
  reorderInput,
23
23
  unknownKeys,
24
24
  withId
25
- } from "./chunk-TDK6S6V7.js";
25
+ } from "./chunk-NKVWV3FL.js";
26
26
  import {
27
27
  ACCESS_LEVELS,
28
28
  isAccessLevel
29
- } from "./chunk-Y5YAG75H.js";
29
+ } from "./chunk-RVGJG5P2.js";
30
30
  import {
31
31
  isStandardSchema
32
32
  } from "./chunk-47AEXVDM.js";
@@ -513,4 +513,4 @@ export {
513
513
  sharingSpecOf,
514
514
  sharing
515
515
  };
516
- //# sourceMappingURL=chunk-RDCIZR4I.js.map
516
+ //# sourceMappingURL=chunk-HKXLRZAO.js.map
@@ -1301,4 +1301,4 @@ export {
1301
1301
  adminSpecOf,
1302
1302
  admin
1303
1303
  };
1304
- //# sourceMappingURL=chunk-TDK6S6V7.js.map
1304
+ //# sourceMappingURL=chunk-NKVWV3FL.js.map